diff --git a/AUTHORS b/AUTHORS
index 3b1e5a31..6953eeaa 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -277,6 +277,7 @@
 Chris Tserng <tserng@amazon.com>
 Chris Vasselli <clindsay@gmail.com>
 Chris Ye <hawkoyates@gmail.com>
+Christian Liebel <christianliebel@gmail.com>
 Christoph Staengle <christoph142@gmx.com>
 Christophe Dumez <ch.dumez@samsung.com>
 Christopher Dale <chrelad@gmail.com>
diff --git a/DEPS b/DEPS
index b230434..2486608 100644
--- a/DEPS
+++ b/DEPS
@@ -300,23 +300,23 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '3d94d1f51451feb30d4e8450d3512062403c1fd7',
+  'src_internal_revision': '30981f06168449b7c9142d22aa6a5b04c008e02c',
   # 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': '69f5d3d5cd7a5d6c59416c7e56735966bb69d70d',
+  'skia_revision': '618beab475b6ee5b33a4c3040dc330359b960eab',
   # 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': '5172b5da4316bd2e96fa1d3b962c6cf6782109be',
+  'v8_revision': '7264a6ecc7eb4ccbcef3acf035ab962f987fa4ea',
   # 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': '84492ff334a4051d760d0d0c63ffb3fb5694f7da',
+  'angle_revision': '10d56e63f489477d325fd9667861c06b4c5887bc',
   # 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': '8dd40811c5715061393f1b36999139ef0813c291',
+  'swiftshader_revision': '3239872f9c5f1f7a1d029733dd1036d1247cfb4f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # 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 BoringSSL
   # and whatever else without interference from each other.
-  'boringssl_revision': 'f10c1dc37174843c504a80e94c252e35b7b1eb61',
+  'boringssl_revision': '604868d748c06d2c5c03505915e125ec5131eca0',
   # 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.
@@ -372,11 +372,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'a16df6024bef64291769b0bc3cad19bec4d34178',
+  'catapult_revision': 'c46e0e5bd633c5329040345a87aace0b2a0de1b3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling chromium_variations
   # and whatever else without interference from each other.
-  'chromium_variations_revision': 'ef4ec64a119b1c436a5b4be38da0ce3d679b5ed4',
+  'chromium_variations_revision': 'c514979254d611ff61da404ed4f5d720efa11a5b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -396,7 +396,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': '7084bf9f43136499038fb62beb7914e434ff4248',
+  'devtools_frontend_revision': 'b20e8d5ddd78f6d04a0b1fd4147aaa821f1bb6a2',
   # 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.
@@ -500,7 +500,7 @@
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
-  'libcxx_revision':       '9be1056e883d3fe5cd6666ac69702a6c5e9a39df',
+  'libcxx_revision':       '6ae6f38d10eda881c16d91932348fc6d4ee98332',
 
   # GN CIPD package version.
   'gn_version': 'git_revision:20806f79c6b4ba295274e3a589d85db41a02fdaa',
@@ -1287,7 +1287,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '4fc73bc9ae2bff63c2115ab8b02284a7eb9fbeee',
+    '123f9cd86c36186e08fafb2445dd5fa0e5688a94',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1430,7 +1430,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'O6xtklrnuuDKGxYwysxYzU7fNkVdoulFOyXlzjOHDhQC',
+          'version': 'kybJUIH8jna1eCue2pU_s_jKcjfJPx_omp68wERCkBEC',
       },
     ],
     'condition': 'checkout_android and non_git_source',
@@ -1760,13 +1760,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '46f5de67e137de4101c1337dd9b95252d14523e9',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'c36eb432d9887c0872ba39e582a97dd3b76c0c22',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '15d5840612c59d0ff69bf9324966f2037e060c38',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '660f3660a28aaf564aeff5015f013cec4f17f72c',
     'condition': 'checkout_src_internal',
   },
 
@@ -2252,7 +2252,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '4eb5915c04991f3c8db021120a5638c3c0742604',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '2e577c61f31b9bca076afe1585b1e3b0192deef6',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -2475,10 +2475,10 @@
       'bucket': 'chromium-ads-detection',
       'objects': [
           {
-              'object_name': 'e4d1c702ca1b5497a3abcdd9495a5d0758f19ffc',
-              'sha256sum': 'ae2fd01d2908591e0f39343a5b4a78baa8e7d6cac9d78ba79c502fe0a15ce3ee',
-              'size_bytes': 70106,
-              'generation': 1695223938564350,
+              'object_name': '7f7f432c27d97dee184dcd3ea20f731674c008be849c0136f9c5358e359f3ea9',
+              'sha256sum': '7f7f432c27d97dee184dcd3ea20f731674c008be849c0136f9c5358e359f3ea9',
+              'size_bytes': 74272,
+              'generation': 1722882673054280,
               'output_file': 'UnindexedRules',
           },
       ],
@@ -2540,16 +2540,16 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@a0dffec9be81ca97ea8e9eb14e162ac285271a32',
-  'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@79c4235085c5eb86ed78b034d94e03f7b3b5daef',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@3b92cef97febae53dc21de79c51ec3ddf2d4390e',
+  'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@12a17b7ce41436427e358608183100b1103274da',
   'src/third_party/spirv-cross/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3',
-  'src/third_party/spirv-headers/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Headers@efb6b4099ddb8fa60f62956dee592c4b94ec6a49',
-  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@2a67ced4331d22f2fc1b17f2d54b2330c5ad8765',
+  'src/third_party/spirv-headers/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Headers@2a9b6f951c7d6b04b6c21fe1bf3f475b68b84801',
+  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@d160e170d74ff45cb2a88dfb365bdfd896016f7c',
   'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@c6391a7b8cd57e79ce6b6c832c8e3043c4d9967b',
-  'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@c758bac8bf1580b5018adafd3a2ec709237b0134',
+  'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@1108bba6c97174d172d45470a7470a3d6a564647',
   'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@4c63e845962ff3b197855f3ae4907a47d0863f5a',
-  'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@fbb4db92c6b2ac09003b2b8e5ceb978f4f2dda71',
-  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@6f3d6d7f55bdd92c4c3e117206eea57cbba6b62c',
+  'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@ea5774a13e3017b6d5d79af6fba9f0d72ca5c61a',
+  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@99de3c17fbc2db6b6da0347916c9e01a383c2758',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21',
@@ -4382,7 +4382,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '2091388e503d97e226e4033bd616b11f5c2e19c8',
+        'cb9d0f308d6dd05d93f35f9e1fd6098fb968839a',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 73002ad..916cc98 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -3727,6 +3727,7 @@
     "auth/views/fingerprint_view_unittest.cc",
     "auth/views/pin_container_view_unittest.cc",
     "auth/views/pin_keyboard_view_unittest.cc",
+    "auth/views/pin_status_view_unittest.cc",
     "birch/birch_icon_cache_unittest.cc",
     "birch/birch_item_remover_unittest.cc",
     "birch/birch_item_unittest.cc",
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 750657c..ea7759b 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -5481,7 +5481,7 @@
         You must be at least 18 years old to use Help me write and Help me read.
       </message>
       <message name="IDS_ASH_MAGIC_BOOST_DISCLAIMER_PARAGRAPH_TWO" desc="Text shown as the second paragraph of the Magic Boost disclamier view's body.">
-        When you turn on Help me write and Help me read, the input text and document content is sent to Google AI servers to generate writing suggestions, generate summaries, answer questions, and improve the product, subject to <ph name="LINK_TO_SERVICE_TERMS">$1<ex>Google's Terms of Service</ex></ph>. Don't include anything personal, sensitive, or confidential.
+        When you turn on Help me write and Help me read, the input text, document content, and web page URL are sent to Google AI servers to generate writing suggestions, generate summaries, answer questions, and improve the product, subject to <ph name="LINK_TO_SERVICE_TERMS">$1<ex>Google's Terms of Service</ex></ph>. Don't include anything personal, sensitive, or confidential.
       </message>
       <message name="IDS_ASH_MAGIC_BOOST_DISCLAIMER_PARAGRAPH_THREE" desc="Text shown as the third paragraph of the Magic Boost disclamier view's body.">
         Generative AI is experimental, in early development, and currently has limited availability.
diff --git a/ash/ash_strings_grd/IDS_ASH_MAGIC_BOOST_DISCLAIMER_PARAGRAPH_TWO.png.sha1 b/ash/ash_strings_grd/IDS_ASH_MAGIC_BOOST_DISCLAIMER_PARAGRAPH_TWO.png.sha1
index eb8c03db..75dd9863 100644
--- a/ash/ash_strings_grd/IDS_ASH_MAGIC_BOOST_DISCLAIMER_PARAGRAPH_TWO.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_MAGIC_BOOST_DISCLAIMER_PARAGRAPH_TWO.png.sha1
@@ -1 +1 @@
-37e82541444155f848fd100437e2fbcd6f3cbf5c
\ No newline at end of file
+c9b1468f602f9509bb6e9c2cb10f37ff0375293e
\ No newline at end of file
diff --git a/ash/auth/views/pin_status_view_unittest.cc b/ash/auth/views/pin_status_view_unittest.cc
new file mode 100644
index 0000000..10e5a23
--- /dev/null
+++ b/ash/auth/views/pin_status_view_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/auth/views/pin_status_view.h"
+
+#include <memory>
+#include <string>
+
+#include "ash/test/ash_test_base.h"
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/memory/raw_ptr.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "chromeos/ash/components/cryptohome/auth_factor.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+class PinStatusUnitTest : public AshTestBase {
+ public:
+  PinStatusUnitTest()
+      : AshTestBase(std::make_unique<base::test::TaskEnvironment>(
+            base::test::TaskEnvironment::MainThreadType::UI,
+            base::test::TaskEnvironment::TimeSource::MOCK_TIME)) {}
+  PinStatusUnitTest(const PinStatusUnitTest&) = delete;
+  PinStatusUnitTest& operator=(const PinStatusUnitTest&) = delete;
+  ~PinStatusUnitTest() override = default;
+
+ protected:
+  // AshTestBase:
+  void SetUp() override {
+    AshTestBase::SetUp();
+
+    widget_ = CreateFramelessTestWidget();
+    widget_->SetFullscreen(true);
+    widget_->Show();
+
+    pin_status_view_ =
+        widget_->SetContentsView(std::make_unique<PinStatusView>());
+  }
+
+  void TearDown() override {
+    pin_status_view_ = nullptr;
+    widget_.reset();
+    AshTestBase::TearDown();
+  }
+
+  std::unique_ptr<views::Widget> widget_;
+  raw_ptr<PinStatusView> pin_status_view_;
+};
+
+// Testing PinStatus with nullptr.
+TEST_F(PinStatusUnitTest, NullPinStatus) {
+  pin_status_view_->SetPinStatus(nullptr);
+  EXPECT_FALSE(pin_status_view_->GetVisible());
+}
+
+// Testing PinStatus with max time.
+TEST_F(PinStatusUnitTest, MaxPinStatus) {
+  const std::u16string status_message = u"Too many PIN attempts";
+
+  cryptohome::PinStatus pin_status(base::TimeDelta::Max());
+
+  pin_status_view_->SetPinStatus(
+      std::make_unique<cryptohome::PinStatus>(pin_status));
+
+  EXPECT_EQ(pin_status_view_->GetCurrentText(), status_message);
+  EXPECT_TRUE(pin_status_view_->GetVisible());
+}
+
+// Testing PinStatus with 30sec.
+TEST_F(PinStatusUnitTest, ShortDelayPinStatus) {
+  const std::u16string status_message =
+      u"Too many PIN attempts. Wait 30 seconds and try again";
+
+  cryptohome::PinStatus pin_status(base::Seconds(30));
+
+  pin_status_view_->SetPinStatus(
+      std::make_unique<cryptohome::PinStatus>(pin_status));
+
+  EXPECT_EQ(pin_status_view_->GetCurrentText(), status_message);
+  EXPECT_TRUE(pin_status_view_->GetVisible());
+
+  task_environment()->FastForwardBy(base::Seconds(5));
+  const std::u16string updated_status_message =
+      u"Too many PIN attempts. Wait 25 seconds and try again";
+  EXPECT_EQ(pin_status_view_->GetCurrentText(), updated_status_message);
+  EXPECT_TRUE(pin_status_view_->GetVisible());
+
+  task_environment()->FastForwardBy(base::Seconds(25));
+  EXPECT_THAT(pin_status_view_->GetCurrentText(), testing::IsEmpty());
+  EXPECT_EQ(pin_status_view_->GetVisible(), false);
+}
+
+}  // namespace ash
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 5ceb21f..9f7effb 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -2785,6 +2785,11 @@
              "ScalableShelfPods",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables the scanner update.
+BASE_FEATURE(kScannerUpdate,
+             "ScannerUpdate",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables sea pen feature in the personalization app.
 BASE_FEATURE(kSeaPen, "SeaPen", base::FEATURE_ENABLED_BY_DEFAULT);
 BASE_FEATURE(kFeatureManagementSeaPen,
@@ -4757,6 +4762,10 @@
   return base::FeatureList::IsEnabled(kScalableShelfPods);
 }
 
+bool IsScannerEnabled() {
+  return base::FeatureList::IsEnabled(kScannerUpdate);
+}
+
 bool IsSeaPenDemoModeEnabled() {
   return IsSeaPenEnabled() && base::FeatureList::IsEnabled(kSeaPenDemoMode);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index c42347b2..6f73979 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -898,6 +898,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kScalableIphTrackingOnly);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kScalableIphClientConfig);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kScalableShelfPods);
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kScannerUpdate);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kSeamlessRefreshRateSwitching);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeaPen);
@@ -1432,6 +1433,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsScalableIphTrackingOnlyEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsScalableIphClientConfigEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsScalableShelfPodsEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsScannerEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenDemoModeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenTextInputEnabled();
diff --git a/ash/dbus/display_service_provider.cc b/ash/dbus/display_service_provider.cc
index 791e1ae..f78705e 100644
--- a/ash/dbus/display_service_provider.cc
+++ b/ash/dbus/display_service_provider.cc
@@ -10,6 +10,7 @@
 #include "ash/wm/screen_dimmer.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/trace_event/trace_event.h"
 #include "dbus/message.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 #include "ui/base/user_activity/user_activity_detector.h"
@@ -22,6 +23,8 @@
     dbus::ExportedObject::ResponseSender response_sender,
     std::unique_ptr<dbus::Response> response,
     bool status) {
+  TRACE_EVENT1("ui", "OnDisplayOwnershipChanged", "status", status);
+
   dbus::MessageWriter writer(response.get());
   writer.AppendBool(status);
   std::move(response_sender).Run(std::move(response));
@@ -161,6 +164,7 @@
 void DisplayServiceProvider::TakeDisplayOwnership(
     dbus::MethodCall* method_call,
     dbus::ExportedObject::ResponseSender response_sender) {
+  TRACE_EVENT0("ui", "DisplayServiceProvider::TakeDisplayOwnership");
   impl_->TakeDisplayOwnership(
       base::BindOnce(&OnDisplayOwnershipChanged, std::move(response_sender),
                      dbus::Response::FromMethodCall(method_call)));
@@ -169,6 +173,7 @@
 void DisplayServiceProvider::ReleaseDisplayOwnership(
     dbus::MethodCall* method_call,
     dbus::ExportedObject::ResponseSender response_sender) {
+  TRACE_EVENT0("ui", "DisplayServiceProvider::ReleaseDisplayOwnership");
   impl_->ReleaseDisplayOwnership(
       base::BindOnce(&OnDisplayOwnershipChanged, std::move(response_sender),
                      dbus::Response::FromMethodCall(method_call)));
diff --git a/ash/display/window_tree_host_manager.cc b/ash/display/window_tree_host_manager.cc
index 03ac840..0a27edb 100644
--- a/ash/display/window_tree_host_manager.cc
+++ b/ash/display/window_tree_host_manager.cc
@@ -342,9 +342,6 @@
 }
 
 void WindowTreeHostManager::Shutdown() {
-  for (auto& observer : observers_)
-    observer.OnWindowTreeHostManagerShutdown();
-
   effective_resolution_UMA_timer_->Reset();
 
   cursor_window_controller_.reset();
@@ -415,14 +412,6 @@
   }
 }
 
-void WindowTreeHostManager::AddObserver(Observer* observer) {
-  observers_.AddObserver(observer);
-}
-
-void WindowTreeHostManager::RemoveObserver(Observer* observer) {
-  observers_.RemoveObserver(observer);
-}
-
 // static
 int64_t WindowTreeHostManager::GetPrimaryDisplayId() {
   CHECK_NE(display::kInvalidDisplayId, primary_display_id);
diff --git a/ash/display/window_tree_host_manager.h b/ash/display/window_tree_host_manager.h
index da86aa0f..1ece0f5 100644
--- a/ash/display/window_tree_host_manager.h
+++ b/ash/display/window_tree_host_manager.h
@@ -17,7 +17,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "base/observer_list.h"
 #include "base/timer/timer.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_occlusion_tracker.h"
@@ -54,15 +53,6 @@
       public ui::ImeKeyEventDispatcher,
       public AshWindowTreeHostDelegate {
  public:
-  // TODO(oshima): Remove this observer.
-  class ASH_EXPORT Observer {
-   public:
-    virtual ~Observer() {}
-
-    // Invoked in WindowTreeHostManager::Shutdown().
-    virtual void OnWindowTreeHostManagerShutdown() {}
-  };
-
   WindowTreeHostManager();
 
   WindowTreeHostManager(const WindowTreeHostManager&) = delete;
@@ -95,10 +85,6 @@
   // Initializes all WindowTreeHosts.
   void InitHosts();
 
-  // Add/Remove observers.
-  void AddObserver(Observer* observer);
-  void RemoveObserver(Observer* observer);
-
   // Returns the root window for primary display.
   aura::Window* GetPrimaryRootWindow();
 
@@ -208,8 +194,6 @@
   base::flat_map<int64_t, std::unique_ptr<RoundedDisplayProvider>>
       rounded_display_providers_map_;
 
-  base::ObserverList<Observer, true>::Unchecked observers_;
-
   // Store the primary window tree host temporarily while replacing
   // display.
   raw_ptr<AshWindowTreeHost> primary_tree_host_for_replace_;
diff --git a/ash/style/tab_slider_button.cc b/ash/style/tab_slider_button.cc
index 0c049ef..5be2ada 100644
--- a/ash/style/tab_slider_button.cc
+++ b/ash/style/tab_slider_button.cc
@@ -243,7 +243,7 @@
                       tooltip_text.empty() ? text : tooltip_text),
       image_view_(AddChildView(std::make_unique<views::ImageView>())),
       label_(AddChildView(std::make_unique<views::Label>(text))) {
-  SetLayoutManager(std::make_unique<views::BoxLayout>(
+  auto* layout_manager = SetLayoutManager(std::make_unique<views::BoxLayout>(
       horizontal ? views::BoxLayout::Orientation::kHorizontal
                  : views::BoxLayout::Orientation::kVertical,
       /*inside_border_insets=*/
@@ -267,6 +267,16 @@
   // Force the label to use requested colors.
   label_->SetAutoColorReadabilityEnabled(false);
   TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2, *label_);
+
+  if (horizontal) {
+    layout_manager->set_main_axis_alignment(
+        views::BoxLayout::MainAxisAlignment::kCenter);
+
+    // Keep `image_view_` to the left side of `label_` in RTL.
+    if (base::i18n::IsRTL()) {
+      ReorderChildView(label_, 0);
+    }
+  }
 }
 
 IconLabelSliderButton::~IconLabelSliderButton() = default;
diff --git a/ash/system/focus_mode/sounds/youtube_music/youtube_music_client.cc b/ash/system/focus_mode/sounds/youtube_music/youtube_music_client.cc
index d5c24b9..75730e8 100644
--- a/ash/system/focus_mode/sounds/youtube_music/youtube_music_client.cc
+++ b/ash/system/focus_mode/sounds/youtube_music/youtube_music_client.cc
@@ -221,6 +221,7 @@
   request_sender->StartRequestWithAuthRetry(
       std::make_unique<google_apis::youtube_music::PlaybackQueueNextRequest>(
           request_sender,
+          google_apis::youtube_music::PlaybackQueueNextRequestPayload(),
           base::BindOnce(&YouTubeMusicClient::OnPlaybackQueueNextRequestDone,
                          weak_factory_.GetWeakPtr(), base::Time::Now()),
           playback_queue_id));
diff --git a/ash/system/palette/common_palette_tool.cc b/ash/system/palette/common_palette_tool.cc
index c1b0854..602f611 100644
--- a/ash/system/palette/common_palette_tool.cc
+++ b/ash/system/palette/common_palette_tool.cc
@@ -13,7 +13,6 @@
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/view_click_listener.h"
 #include "base/check.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -21,20 +20,6 @@
 
 namespace ash {
 
-namespace {
-
-void AddHistogramTimes(PaletteToolId id, base::TimeDelta duration) {
-  if (id == PaletteToolId::LASER_POINTER) {
-    UMA_HISTOGRAM_CUSTOM_TIMES("Ash.Shelf.Palette.InLaserPointerMode", duration,
-                               base::Milliseconds(100), base::Hours(1), 50);
-  } else if (id == PaletteToolId::MAGNIFY) {
-    UMA_HISTOGRAM_CUSTOM_TIMES("Ash.Shelf.Palette.InMagnifyMode", duration,
-                               base::Milliseconds(100), base::Hours(1), 50);
-  }
-}
-
-}  // namespace
-
 CommonPaletteTool::CommonPaletteTool(Delegate* delegate)
     : PaletteTool(delegate) {}
 
@@ -46,12 +31,6 @@
 
 void CommonPaletteTool::OnEnable() {
   PaletteTool::OnEnable();
-  start_time_ = base::TimeTicks::Now();
-}
-
-void CommonPaletteTool::OnDisable() {
-  PaletteTool::OnDisable();
-  AddHistogramTimes(GetToolId(), base::TimeTicks::Now() - start_time_);
 }
 
 void CommonPaletteTool::OnViewClicked(views::View* sender) {
@@ -60,10 +39,6 @@
   // enabled. Then, to open the bubble again we have to click on the palette
   // tray twice, and the first click will disable any active tools.
   DCHECK(!enabled());
-
-  delegate()->RecordPaletteOptionsUsage(
-      PaletteToolIdToPaletteTrayOptions(GetToolId()),
-      PaletteInvocationMethod::MENU);
   delegate()->EnableTool(GetToolId());
 }
 
diff --git a/ash/system/palette/common_palette_tool.h b/ash/system/palette/common_palette_tool.h
index 9ecb60b..551e072 100644
--- a/ash/system/palette/common_palette_tool.h
+++ b/ash/system/palette/common_palette_tool.h
@@ -10,7 +10,6 @@
 #include "ash/system/palette/palette_tool.h"
 #include "ash/system/tray/view_click_listener.h"
 #include "base/memory/raw_ptr.h"
-#include "base/time/time.h"
 
 namespace gfx {
 struct VectorIcon;
@@ -33,7 +32,6 @@
   // PaletteTool:
   void OnViewDestroyed() override;
   void OnEnable() override;
-  void OnDisable() override;
 
   // ViewClickListener:
   void OnViewClicked(views::View* sender) override;
@@ -49,11 +47,6 @@
   views::View* CreateDefaultView(const std::u16string& name);
 
   raw_ptr<HoverHighlightView, DanglingUntriaged> highlight_view_ = nullptr;
-
- private:
-  // `start_time_` is initialized when the tool becomes active.
-  // Used for recording UMA metrics.
-  base::TimeTicks start_time_;
 };
 
 }  // namespace ash
diff --git a/ash/system/palette/mock_palette_tool_delegate.h b/ash/system/palette/mock_palette_tool_delegate.h
index 568fc175..47350b7c 100644
--- a/ash/system/palette/mock_palette_tool_delegate.h
+++ b/ash/system/palette/mock_palette_tool_delegate.h
@@ -21,14 +21,6 @@
   MOCK_METHOD(void, HidePalette, (), (override));
   MOCK_METHOD(void, HidePaletteImmediately, (), (override));
   MOCK_METHOD(aura::Window*, GetWindow, (), (override));
-  MOCK_METHOD(void,
-              RecordPaletteOptionsUsage,
-              (PaletteTrayOptions option, PaletteInvocationMethod method),
-              (override));
-  MOCK_METHOD(void,
-              RecordPaletteModeCancellation,
-              (PaletteModeCancelType type),
-              (override));
 };
 
 }  // namespace ash
diff --git a/ash/system/palette/palette_tool.h b/ash/system/palette/palette_tool.h
index e95baa6..7163c40 100644
--- a/ash/system/palette/palette_tool.h
+++ b/ash/system/palette/palette_tool.h
@@ -51,13 +51,6 @@
     // Returns the root window.
     virtual aura::Window* GetWindow() = 0;
 
-    // Record usage of each pen palette option.
-    virtual void RecordPaletteOptionsUsage(PaletteTrayOptions option,
-                                           PaletteInvocationMethod method) = 0;
-
-    // Record mode cancellation of pen palette.
-    virtual void RecordPaletteModeCancellation(PaletteModeCancelType type) = 0;
-
    protected:
     virtual ~Delegate() {}
   };
diff --git a/ash/system/palette/palette_tool_manager.cc b/ash/system/palette/palette_tool_manager.cc
index 5019945e..f8283cc 100644
--- a/ash/system/palette/palette_tool_manager.cc
+++ b/ash/system/palette/palette_tool_manager.cc
@@ -41,8 +41,6 @@
 
   if (previous_tool) {
     previous_tool->OnDisable();
-    RecordPaletteModeCancellation(PaletteToolIdToPaletteModeCancelType(
-        previous_tool->GetToolId(), true /*is_switched*/));
   }
 
   active_tools_[new_tool->GetGroup()] = new_tool;
@@ -132,17 +130,6 @@
   return delegate_->GetWindow();
 }
 
-void PaletteToolManager::RecordPaletteOptionsUsage(
-    PaletteTrayOptions option,
-    PaletteInvocationMethod method) {
-  return delegate_->RecordPaletteOptionsUsage(option, method);
-}
-
-void PaletteToolManager::RecordPaletteModeCancellation(
-    PaletteModeCancelType type) {
-  return delegate_->RecordPaletteModeCancellation(type);
-}
-
 PaletteTool* PaletteToolManager::FindToolById(PaletteToolId tool_id) const {
   for (const std::unique_ptr<PaletteTool>& tool : tools_) {
     if (tool->GetToolId() == tool_id)
diff --git a/ash/system/palette/palette_tool_manager.h b/ash/system/palette/palette_tool_manager.h
index eb8f3aaa..ed50c1c 100644
--- a/ash/system/palette/palette_tool_manager.h
+++ b/ash/system/palette/palette_tool_manager.h
@@ -51,13 +51,6 @@
     // Return the window associated with this palette.
     virtual aura::Window* GetWindow() = 0;
 
-    // Record usage of each pen palette option.
-    virtual void RecordPaletteOptionsUsage(PaletteTrayOptions option,
-                                           PaletteInvocationMethod method) = 0;
-
-    // Record mode cancellation of pen palette.
-    virtual void RecordPaletteModeCancellation(PaletteModeCancelType type) = 0;
-
    protected:
     virtual ~Delegate() {}
   };
@@ -111,9 +104,6 @@
   void HidePalette() override;
   void HidePaletteImmediately() override;
   aura::Window* GetWindow() override;
-  void RecordPaletteOptionsUsage(PaletteTrayOptions option,
-                                 PaletteInvocationMethod method) override;
-  void RecordPaletteModeCancellation(PaletteModeCancelType type) override;
 
   PaletteTool* FindToolById(PaletteToolId tool_id) const;
 
diff --git a/ash/system/palette/palette_tool_manager_unittest.cc b/ash/system/palette/palette_tool_manager_unittest.cc
index e3f7225..cf50e234 100644
--- a/ash/system/palette/palette_tool_manager_unittest.cc
+++ b/ash/system/palette/palette_tool_manager_unittest.cc
@@ -59,9 +59,6 @@
   void HidePaletteImmediately() override {}
   void OnActiveToolChanged() override { ++tool_changed_count_; }
   aura::Window* GetWindow() override { NOTREACHED(); }
-  void RecordPaletteOptionsUsage(PaletteTrayOptions option,
-                                 PaletteInvocationMethod method) override {}
-  void RecordPaletteModeCancellation(PaletteModeCancelType type) override {}
 
   // PaletteTool::Delegate:
   void EnableTool(PaletteToolId tool_id) override {}
diff --git a/ash/system/palette/palette_tray.cc b/ash/system/palette/palette_tray.cc
index 273239d0..2e70387 100644
--- a/ash/system/palette/palette_tray.cc
+++ b/ash/system/palette/palette_tray.cc
@@ -153,8 +153,6 @@
  private:
   void ButtonPressed(PaletteTrayOptions option,
                      base::RepeatingClosure callback) {
-    palette_tray_->RecordPaletteOptionsUsage(option,
-                                             PaletteInvocationMethod::MENU);
     std::move(callback).Run();
     palette_tray_->HidePalette();
   }
@@ -389,10 +387,6 @@
 }
 
 void PaletteTray::ClickedOutsideBubble(const ui::LocatedEvent& event) {
-  if (num_actions_in_bubble_ == 0) {
-    RecordPaletteOptionsUsage(PaletteTrayOptions::PALETTE_CLOSED_NO_ACTION,
-                              PaletteInvocationMethod::MENU);
-  }
   HidePalette();
 }
 
@@ -500,32 +494,6 @@
   HidePalette();
 }
 
-void PaletteTray::RecordPaletteOptionsUsage(PaletteTrayOptions option,
-                                            PaletteInvocationMethod method) {
-  DCHECK_NE(option, PaletteTrayOptions::PALETTE_OPTIONS_COUNT);
-
-  if (method == PaletteInvocationMethod::SHORTCUT) {
-    UMA_HISTOGRAM_ENUMERATION("Ash.Shelf.Palette.Usage.Shortcut", option,
-                              PaletteTrayOptions::PALETTE_OPTIONS_COUNT);
-  } else if (is_bubble_auto_opened_) {
-    UMA_HISTOGRAM_ENUMERATION("Ash.Shelf.Palette.Usage.AutoOpened", option,
-                              PaletteTrayOptions::PALETTE_OPTIONS_COUNT);
-  } else {
-    UMA_HISTOGRAM_ENUMERATION("Ash.Shelf.Palette.Usage", option,
-                              PaletteTrayOptions::PALETTE_OPTIONS_COUNT);
-  }
-}
-
-void PaletteTray::RecordPaletteModeCancellation(PaletteModeCancelType type) {
-  if (type == PaletteModeCancelType::PALETTE_MODE_CANCEL_TYPE_COUNT) {
-    return;
-  }
-
-  UMA_HISTOGRAM_ENUMERATION(
-      "Ash.Shelf.Palette.ModeCancellation", type,
-      PaletteModeCancelType::PALETTE_MODE_CANCEL_TYPE_COUNT);
-}
-
 void PaletteTray::OnProjectorSessionActiveStateChanged(bool active) {
   is_palette_visibility_paused_ = active;
   if (active) {
@@ -674,10 +642,6 @@
 
 void PaletteTray::OnPaletteTrayPressed(const ui::Event& event) {
   if (bubble_) {
-    if (num_actions_in_bubble_ == 0) {
-      RecordPaletteOptionsUsage(PaletteTrayOptions::PALETTE_CLOSED_NO_ACTION,
-                                PaletteInvocationMethod::MENU);
-    }
     HidePalette();
     return;
   }
@@ -703,8 +667,6 @@
       palette_tool_manager_->GetActiveTool(PaletteGroup::MODE);
   if (active_tool_id != PaletteToolId::NONE) {
     palette_tool_manager_->DeactivateTool(active_tool_id);
-    RecordPaletteModeCancellation(PaletteToolIdToPaletteModeCancelType(
-        active_tool_id, false /*is_switched*/));
     return true;
   }
 
diff --git a/ash/system/palette/palette_tray.h b/ash/system/palette/palette_tray.h
index 7de009d..be7a328 100644
--- a/ash/system/palette/palette_tray.h
+++ b/ash/system/palette/palette_tray.h
@@ -116,9 +116,6 @@
   // PaletteToolManager::Delegate:
   void HidePalette() override;
   void HidePaletteImmediately() override;
-  void RecordPaletteOptionsUsage(PaletteTrayOptions option,
-                                 PaletteInvocationMethod method) override;
-  void RecordPaletteModeCancellation(PaletteModeCancelType type) override;
 
   // ProjectorSessionObserver:
   void OnProjectorSessionActiveStateChanged(bool active) override;
diff --git a/ash/user_education/welcome_tour/welcome_tour_scrim.cc b/ash/user_education/welcome_tour/welcome_tour_scrim.cc
index e421ae3..e03ae7f 100644
--- a/ash/user_education/welcome_tour/welcome_tour_scrim.cc
+++ b/ash/user_education/welcome_tour/welcome_tour_scrim.cc
@@ -7,6 +7,7 @@
 #include <array>
 #include <vector>
 
+#include "ash/display/window_tree_host_manager.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
@@ -306,10 +307,6 @@
   // Observe `shell` so that scrims can be dynamically created/destroyed when
   // root windows are added/removed.
   shell_observation_.Observe(shell);
-
-  // Observe the window tree host manager so that scrims can be destroyed when
-  // the window tree host manager is shutdown.
-  window_tree_host_manager_observation_.Observe(window_tree_host_mgr);
 }
 
 WelcomeTourScrim::~WelcomeTourScrim() {
@@ -325,22 +322,16 @@
   Reset(root_window);
 }
 
-void WelcomeTourScrim::OnWindowTreeHostManagerShutdown() {
-  // Cache `shell` and associated window tree host manager.
-  auto* shell = Shell::Get();
-  CHECK(shell);
-  auto* window_tree_host_mgr = shell->window_tree_host_manager();
+void WelcomeTourScrim::OnShellDestroying() {
+  auto* window_tree_host_mgr = Shell::Get()->window_tree_host_manager();
   CHECK(window_tree_host_mgr);
 
-  // Reset observation.
-  CHECK(window_tree_host_manager_observation_.IsObservingSource(
-      window_tree_host_mgr));
-  window_tree_host_manager_observation_.Reset();
-
   // Destroy scrims for every root window.
   for (aura::Window* root_window : window_tree_host_mgr->GetAllRootWindows()) {
     Reset(root_window);
   }
+
+  shell_observation_.Reset();
 }
 
 void WelcomeTourScrim::Init(aura::Window* root_window) {
diff --git a/ash/user_education/welcome_tour/welcome_tour_scrim.h b/ash/user_education/welcome_tour/welcome_tour_scrim.h
index 6643189..219f652 100644
--- a/ash/user_education/welcome_tour/welcome_tour_scrim.h
+++ b/ash/user_education/welcome_tour/welcome_tour_scrim.h
@@ -8,7 +8,6 @@
 #include <map>
 #include <memory>
 
-#include "ash/display/window_tree_host_manager.h"
 #include "ash/shell_observer.h"
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
@@ -26,8 +25,7 @@
 // scrims are automatically removed. Only a single `WelcomeTourScrim` instance
 // may exist at a time, and the `WelcomeTourController` is responsible for
 // ensuring existence if and only if the Welcome Tour is in progress.
-class ASH_EXPORT WelcomeTourScrim : public ShellObserver,
-                                    public WindowTreeHostManager::Observer {
+class ASH_EXPORT WelcomeTourScrim : public ShellObserver {
  public:
   // Names for layers so they are easy to distinguish in debugging/testing.
   static constexpr char kLayerName[] = "WelcomeTourScrim";
@@ -44,9 +42,7 @@
   // ShellObserver:
   void OnRootWindowAdded(aura::Window* root_window) override;
   void OnRootWindowWillShutdown(aura::Window* root_window) override;
-
-  // WindowTreeHostManager::Observer:
-  void OnWindowTreeHostManagerShutdown() override;
+  void OnShellDestroying() override;
 
   // Initializes the scrim for the specified `root_window`.
   void Init(aura::Window* root_window);
@@ -61,12 +57,6 @@
   // Used to observe `Shell` for the addition/destruction of root windows so
   // that scrims can be created/destroyed appropriately.
   base::ScopedObservation<Shell, ShellObserver> shell_observation_{this};
-
-  // Used to observe the window tree host manager for shutdown so that scrims
-  // can be destroyed appropriately.
-  base::ScopedObservation<WindowTreeHostManager,
-                          WindowTreeHostManager::Observer>
-      window_tree_host_manager_observation_{this};
 };
 
 }  // namespace ash
diff --git a/ash/webui/graduation/graduation_ui.cc b/ash/webui/graduation/graduation_ui.cc
index 2482be2..90bd811f 100644
--- a/ash/webui/graduation/graduation_ui.cc
+++ b/ash/webui/graduation/graduation_ui.cc
@@ -37,6 +37,8 @@
   source->AddResourcePaths(
       base::make_span(kAshGraduationResources, kAshGraduationResourcesSize));
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
+      {"backButtonLabel", IDS_GRADUATION_APP_BACK_BUTTON_LABEL},
+      {"doneButtonLabel", IDS_GRADUATION_APP_DONE_BUTTON_LABEL},
       {"webviewLoadingMessage", IDS_GRADUATION_APP_WEBVIEW_LOADING_MESSAGE}};
 
   source->AddLocalizedStrings(kLocalizedStrings);
diff --git a/ash/webui/graduation/resources/index.html b/ash/webui/graduation/resources/index.html
index afaf417..66903ab 100644
--- a/ash/webui/graduation/resources/index.html
+++ b/ash/webui/graduation/resources/index.html
@@ -4,14 +4,20 @@
     body {
       margin: 0;
     }
+
+    html {
+      background-color: var(--cros-sys-app_base_shaded);
+    }
   </style>
   <head>
     <title>Graduation</title>
     <meta charset="utf-8">
+    <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
     <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
+    <link rel="stylesheet" href="chrome://theme/colors.css?sets=legacy,sys">
     <script type="module" src="/js/graduation_ui.js"></script>
   </head>
-  <body>
+  <body class="jelly-enabled">
     <graduation-ui></graduation-ui>
   </body>
 </html>
diff --git a/ash/webui/graduation/resources/js/graduation_ui.html b/ash/webui/graduation/resources/js/graduation_ui.html
index 5ebec666..84053ed 100644
--- a/ash/webui/graduation/resources/js/graduation_ui.html
+++ b/ash/webui/graduation/resources/js/graduation_ui.html
@@ -1,19 +1,36 @@
-<style include="cr-shared-style">
+<style include="cr-shared-style cros-color-overrides">
   html,
   body {
     height: 100%;
   }
 
   #graduationDiv {
+    background-color: var(--cros-sys-app_base_shaded);
     height: 100vh;
   }
 
+  .back-button-container {
+    align-content: center;
+    height: 10%;
+    margin-inline-start: 40px;
+  }
+
+  #backButton {
+    background-color: transparent;
+    border-radius: 18px;
+  }
+
   #webview {
+    border-radius: 16px;
     display: flex;
-    height: 100%;
+    height: 75%;
+    margin: 0 20px;
+    overflow: hidden;
   }
 
   #webview[hidden],
+  #backButton[hidden],
+  #doneButton[hidden],
   .spinner-container[hidden] {
     display: none;
   }
@@ -33,13 +50,30 @@
     align-items: center;
     display: flex;
     flex-direction: column;
-    height: 100%;
+    height: 75%;
     justify-content: center;
     width: 100%;
   }
+
+  .done-button-container {
+    align-content: center;
+    float: inline-end;
+    height: 15%;
+    margin-inline-end: 40px;
+  }
+
+  #doneButton {
+    border-radius: 18px;
+  }
 </style>
 <div id="graduationDiv" role="application">
-  <!-- TODO(b/361797263): Add Back and Done buttons -->
+  <div class="back-button-container">
+    <cr-button id="backButton" class="cancel-button" on-click="onBackClicked_"
+        hidden$="[[webviewLoading]]">
+      <iron-icon icon="[[getBackButtonIcon_()]]"></iron-icon>
+      $i18n{backButtonLabel}
+    </cr-button>
+  </div>
   <webview id="webview" hidden$="[[webviewLoading]]"></webview>
   <div class="spinner-container" hidden$="[[!webviewLoading]]">
     <paper-spinner-lite class="spinner" active="[[webviewLoading]]">
@@ -48,4 +82,10 @@
       $i18n{webviewLoadingMessage}
     </div>
   </div>
+  <div class="done-button-container">
+    <cr-button id="doneButton" class="action-button" on-click="onDoneClicked_"
+        hidden$="[[!takeoutFlowCompleted]]">
+      $i18n{doneButtonLabel}
+    </cr-button>
+  </div>
 </div>
diff --git a/ash/webui/graduation/resources/js/graduation_ui.ts b/ash/webui/graduation/resources/js/graduation_ui.ts
index 10a40cc..b8d2ce1 100644
--- a/ash/webui/graduation/resources/js/graduation_ui.ts
+++ b/ash/webui/graduation/resources/js/graduation_ui.ts
@@ -2,21 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
 import 'chrome://resources/ash/common/cr_elements/cr_shared_style.css.js';
+import 'chrome://resources/ash/common/cr_elements/cros_color_overrides.css.js';
+import 'chrome://resources/ash/common/cr_elements/icons.html.js';
+import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 import '../strings.m.js';
 
+import {isRTL} from '//resources/js/util.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './graduation_ui.html.js';
 
 /**
- * The URL of the banner shown in Takeout indicating that the user has
+ * The base URL of the banner shown in Takeout indicating that the user has
  * completed the final step of the flow.
+ * May be suffixed by a year, for example: "-2024.png".
  */
-const TAKEOUT_COMPLETED_BANNER_URL: string =
-    'https://www.gstatic.com/ac/takeout/migration/migration-banner.png';
+const TAKEOUT_COMPLETED_BANNER_BASE_URL: string =
+    'https://www.gstatic.com/ac/takeout/migration/migration-banner';
 
 export interface GraduationUi {
   $: {
@@ -52,36 +58,47 @@
 
   webviewLoading: boolean;
   takeoutFlowCompleted: boolean;
+  private webview: chrome.webviewTag.WebView;
 
   override ready() {
     super.ready();
     const webviewUrl = loadTimeData.getString('webviewUrl');
-    const webview =
+    this.webview =
         this.shadowRoot!.querySelector<chrome.webviewTag.WebView>('webview')!;
 
-    webview.addEventListener('contentload', () => {
+    this.webview.addEventListener('contentload', () => {
       this.webviewLoading = false;
     });
 
-    webview.addEventListener('loadabort', () => {
+    this.webview.addEventListener('loadabort', () => {
       this.webviewLoading = false;
       /** TODO(b/357877855) Trigger error screen. */
     });
 
     /**
-     * The flow is marked as completed when the image shown at the end of the
+     * The done button is made visible when the image shown at the end of the
      * Takeout flow is displayed to the user.
-     * TODO(b/361797263): Enable Done button when `takeoutFlowCompleted` is
-     * true.
      */
-    webview.request.onCompleted.addListener((details: any) => {
+    this.webview.request.onCompleted.addListener((details: any) => {
       if (details.statusCode === 200 &&
-          details.url === TAKEOUT_COMPLETED_BANNER_URL) {
+          details.url.startsWith(TAKEOUT_COMPLETED_BANNER_BASE_URL)) {
         this.takeoutFlowCompleted = true;
       }
     }, {urls: ['<all_urls>']});
 
-    webview.src = webviewUrl.toString();
+    this.webview.src = webviewUrl.toString();
+  }
+
+  private getBackButtonIcon_(): string {
+    return isRTL() ? 'cr:chevron-right' : 'cr:chevron-left';
+  }
+
+  private onBackClicked_(): void {
+    /** TODO(b/357877542): Trigger navigation to the welcome screen. */
+  }
+
+  private onDoneClicked_(): void {
+    window.close();
   }
 }
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 47871db..c96ab867 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -901,6 +901,7 @@
     "types/expected_macros.h",
     "types/fixed_array.h",
     "types/id_type.h",
+    "types/is_arc_pointer.h",
     "types/is_complete.h",
     "types/is_instantiation.h",
     "types/optional_ref.h",
@@ -3858,6 +3859,7 @@
       "apple/dispatch_source_mach_unittest.cc",
       "apple/foundation_util_unittest.mm",
       "strings/sys_string_conversions_apple_unittest.mm",
+      "types/is_arc_pointer_unittest.mm",
     ]
   }
 
diff --git a/base/check_op.h b/base/check_op.h
index 98cb19e..bab474a 100644
--- a/base/check_op.h
+++ b/base/check_op.h
@@ -15,6 +15,7 @@
 #include "base/dcheck_is_on.h"
 #include "base/memory/raw_ptr_exclusion.h"
 #include "base/strings/to_string.h"
+#include "base/types/is_arc_pointer.h"
 #include "base/types/supports_ostream_operator.h"
 
 // This header defines the (DP)CHECK_EQ etc. macros.
@@ -79,7 +80,7 @@
 
 template <typename T>
   requires(base::internal::SupportsOstreamOperator<const T&> &&
-           !std::is_function_v<std::remove_pointer_t<T>>)
+           !std::is_function_v<T> && !std::is_pointer_v<T>)
 inline char* CheckOpValueStr(const T& v) {
   auto f = [](std::ostream& s, const void* p) {
     s << *reinterpret_cast<const T*>(p);
@@ -100,6 +101,27 @@
 
 #undef SUPPORTS_BUILTIN_ADDRESSOF
 
+// Even if the pointer type supports operator<<, print the pointer by
+// value. This is especially useful for `char*` and `unsigned char*`,
+// which would otherwise print the pointed-to data.
+template <typename T>
+  requires(std::is_pointer_v<T> &&
+           !std::is_function_v<std::remove_pointer_t<T>>)
+inline char* CheckOpValueStr(const T& v) {
+#if defined(__OBJC__)
+  const void* vp;
+  if constexpr (base::IsArcPointer<T>) {
+    vp = const_cast<const void*>((__bridge const volatile void*)(v));
+  } else {
+    vp = const_cast<const void*>(reinterpret_cast<const volatile void*>(v));
+  }
+#else
+  const void* vp =
+      const_cast<const void*>(reinterpret_cast<const volatile void*>(v));
+#endif
+  return CheckOpValueStr(vp);
+}
+
 // Overload for types that have no operator<< but do have .ToString() defined.
 template <typename T>
   requires(!base::internal::SupportsOstreamOperator<const T&> &&
diff --git a/base/check_unittest.cc b/base/check_unittest.cc
index 51eb453..d801f88 100644
--- a/base/check_unittest.cc
+++ b/base/check_unittest.cc
@@ -256,6 +256,23 @@
   EXPECT_DCHECK("Check failed: sv == s (1 vs. 3)", DCHECK_EQ(sv, s));
 }
 
+TEST(CheckDeathTest, CheckOpPointers) {
+  uint8_t arr[] = {3, 2, 1, 0};
+  uint8_t* arr_start = &arr[0];
+  // Print pointers and not the binary data in `arr`.
+#if BUILDFLAG(IS_WIN)
+  EXPECT_CHECK(
+      "=~Check failed: arr_start != arr_start \\([0-9A-F]+ vs. "
+      "[0-9A-F]+\\)",
+      CHECK_NE(arr_start, arr_start));
+#else
+  EXPECT_CHECK(
+      "=~Check failed: arr_start != arr_start \\(0x[0-9a-f]+ vs. "
+      "0x[0-9a-f]+\\)",
+      CHECK_NE(arr_start, arr_start));
+#endif
+}
+
 TEST(CheckTest, CheckStreamsAreLazy) {
   int called_count = 0;
   int not_called_count = 0;
diff --git a/base/containers/flat_tree.h b/base/containers/flat_tree.h
index f8a531c..26564d73 100644
--- a/base/containers/flat_tree.h
+++ b/base/containers/flat_tree.h
@@ -2,17 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
-#pragma allow_unsafe_buffers
-#endif
-
 #ifndef BASE_CONTAINERS_FLAT_TREE_H_
 #define BASE_CONTAINERS_FLAT_TREE_H_
 
 #include <algorithm>
 #include <array>
 #include <compare>
+#include <concepts>
 #include <functional>
 #include <initializer_list>
 #include <iterator>
@@ -22,6 +18,7 @@
 
 #include "base/check.h"
 #include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "base/memory/raw_ptr_exclusion.h"
 #include "base/ranges/algorithm.h"
 
@@ -56,15 +53,15 @@
 // elements.
 //
 // Reference: https://en.cppreference.com/w/cpp/container/array/to_array
-template <typename U, typename T, size_t N, size_t... I>
-constexpr std::array<U, N> ToArrayImpl(const T (&data)[N],
-                                       std::index_sequence<I...>) {
-  return {{data[I]...}};
-}
-
 template <typename U, typename T, size_t N>
+  requires(std::constructible_from<U, T>)
 constexpr std::array<U, N> ToArray(const T (&data)[N]) {
-  return ToArrayImpl<U>(data, std::make_index_sequence<N>());
+  auto impl = [&]<size_t... I>(std::index_sequence<I...>) {
+    // SAFETY: `impl` is called with `make_index_sequence<N>`, so the largest
+    // `I` will be `N - 1`.
+    return std::array<U, N>({UNSAFE_BUFFERS(data[I])...});
+  };
+  return impl(std::make_index_sequence<N>());
 }
 
 // Helper that calls `container.reserve(std::size(source))`.
@@ -251,7 +248,16 @@
   // This method inserts the values from the range [first, last) into the
   // current tree.
   template <class InputIterator>
+    requires(std::input_iterator<InputIterator>)
   void insert(InputIterator first, InputIterator last);
+  template <class InputIteratorPtr>
+  UNSAFE_BUFFER_USAGE void insert(InputIteratorPtr* first,
+                                  InputIteratorPtr* last);
+
+  // Inserts the all values from the `range` into the current tree.
+  template <class Range>
+    requires(std::ranges::input_range<Range>)
+  void insert_range(Range&& range);
 
   template <class... Args>
   std::pair<iterator, bool> emplace(Args&&... args);
@@ -750,23 +756,36 @@
 }
 
 template <class Key, class GetKeyFromValue, class KeyCompare, class Container>
+template <class InputIteratorPtr>
+UNSAFE_BUFFER_USAGE void
+flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::insert(
+    InputIteratorPtr* input_begin,
+    InputIteratorPtr* input_end) {
+  // SAFETY: The caller must ensure the pointers are a valid pair.
+  auto s = UNSAFE_BUFFERS(base::span(input_begin, input_end));
+  insert(s.begin(), s.end());
+}
+
+template <class Key, class GetKeyFromValue, class KeyCompare, class Container>
 template <class InputIterator>
+  requires(std::input_iterator<InputIterator>)
 void flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::insert(
-    InputIterator first,
-    InputIterator last) {
-  if (first == last)
+    InputIterator input_begin,
+    InputIterator input_end) {
+  if (input_begin == input_end) {
     return;
+  }
 
   // Dispatch to single element insert if the input range contains a single
   // element.
-  if (std::forward_iterator<InputIterator> && std::next(first) == last) {
-    insert(end(), *first);
+  if (std::next(input_begin) == input_end) {
+    insert(end(), *input_begin);
     return;
   }
 
   // Provide a convenience lambda to obtain an iterator pointing past the last
   // old element. This needs to be dymanic due to possible re-allocations.
-  auto middle = [this, size = size()] {
+  auto prior_end = [this, size = size()] {
     return std::next(begin(), static_cast<difference_type>(size));
   };
 
@@ -775,23 +794,32 @@
 
   // Loop over the input range while appending new values and overwriting
   // existing ones, if applicable. Keep track of the first insertion point.
-  for (; first != last; ++first) {
-    std::pair<iterator, bool> result = append_unique(begin(), middle(), *first);
-    if (result.second) {
+  for (auto it = input_begin; it != input_end; ++it) {
+    auto [inserted_at, inserted] = append_unique(begin(), prior_end(), *it);
+    if (inserted) {
       pos_first_new =
-          std::min(pos_first_new, std::distance(begin(), result.first));
+          std::min(pos_first_new, std::distance(begin(), inserted_at));
     }
   }
 
   // The new elements might be unordered and contain duplicates, so post-process
   // the just inserted elements and merge them with the rest, inserting them at
   // the previously found spot.
-  sort_and_unique(middle(), end());
-  std::inplace_merge(std::next(begin(), pos_first_new), middle(), end(),
+  sort_and_unique(prior_end(), end());
+  std::inplace_merge(std::next(begin(), pos_first_new), prior_end(), end(),
                      value_comp());
 }
 
 template <class Key, class GetKeyFromValue, class KeyCompare, class Container>
+template <class Range>
+  requires(std::ranges::input_range<Range>)
+void flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::insert_range(
+    Range&& range) {
+  // SAFETY: A range should return a valid begin/end even if they are pointers.
+  UNSAFE_BUFFERS(insert(std::ranges::begin(range), std::ranges::end(range)));
+}
+
+template <class Key, class GetKeyFromValue, class KeyCompare, class Container>
 template <class... Args>
 auto flat_tree<Key, GetKeyFromValue, KeyCompare, Container>::emplace(
     Args&&... args) -> std::pair<iterator, bool> {
diff --git a/base/containers/flat_tree_unittest.cc b/base/containers/flat_tree_unittest.cc
index 2964fed..8c629e31 100644
--- a/base/containers/flat_tree_unittest.cc
+++ b/base/containers/flat_tree_unittest.cc
@@ -804,7 +804,7 @@
   {
     IntIntMap cont;
     IntPair int_pairs[] = {{3, 1}, {1, 1}, {4, 1}, {2, 1}};
-    cont.insert(std::begin(int_pairs), std::end(int_pairs));
+    UNSAFE_BUFFERS(cont.insert(std::begin(int_pairs), std::end(int_pairs)));
     EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
                                   IntPair(4, 1)));
   }
@@ -812,7 +812,7 @@
   {
     IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
     std::vector<IntPair> int_pairs;
-    cont.insert(std::begin(int_pairs), std::end(int_pairs));
+    UNSAFE_BUFFERS(cont.insert(std::begin(int_pairs), std::end(int_pairs)));
     EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
                                   IntPair(4, 1)));
   }
@@ -820,7 +820,7 @@
   {
     IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
     IntPair int_pairs[] = {{1, 1}};
-    cont.insert(std::begin(int_pairs), std::end(int_pairs));
+    UNSAFE_BUFFERS(cont.insert(std::begin(int_pairs), std::end(int_pairs)));
     EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
                                   IntPair(4, 1)));
   }
@@ -828,7 +828,7 @@
   {
     IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
     IntPair int_pairs[] = {{5, 1}};
-    cont.insert(std::begin(int_pairs), std::end(int_pairs));
+    UNSAFE_BUFFERS(cont.insert(std::begin(int_pairs), std::end(int_pairs)));
     EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
                                   IntPair(4, 1), IntPair(5, 1)));
   }
@@ -836,7 +836,7 @@
   {
     IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
     IntPair int_pairs[] = {{3, 2}, {1, 2}, {4, 2}, {2, 2}};
-    cont.insert(std::begin(int_pairs), std::end(int_pairs));
+    UNSAFE_BUFFERS(cont.insert(std::begin(int_pairs), std::end(int_pairs)));
     EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
                                   IntPair(4, 1)));
   }
@@ -845,7 +845,71 @@
     IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
     IntPair int_pairs[] = {{3, 2}, {1, 2}, {4, 2}, {2, 2}, {7, 2}, {6, 2},
                            {8, 2}, {5, 2}, {5, 3}, {6, 3}, {7, 3}, {8, 3}};
-    cont.insert(std::begin(int_pairs), std::end(int_pairs));
+    UNSAFE_BUFFERS(cont.insert(std::begin(int_pairs), std::end(int_pairs)));
+    EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+                                  IntPair(4, 1), IntPair(5, 2), IntPair(6, 2),
+                                  IntPair(7, 2), IntPair(8, 2)));
+  }
+}
+
+// template <class Range>
+//   void insert_range(Range range);
+
+TEST(FlatTree, InsertRange) {
+  struct GetKeyFromIntIntPair {
+    const int& operator()(const std::pair<int, int>& p) const {
+      return p.first;
+    }
+  };
+
+  using IntIntMap = flat_tree<int, GetKeyFromIntIntPair, std::less<int>,
+                              std::vector<IntPair>>;
+
+  {
+    IntIntMap cont;
+    IntPair int_pairs[] = {{3, 1}, {1, 1}, {4, 1}, {2, 1}};
+    cont.insert_range(int_pairs);
+    EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+                                  IntPair(4, 1)));
+  }
+
+  {
+    IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+    std::vector<IntPair> int_pairs;
+    cont.insert_range(int_pairs);
+    EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+                                  IntPair(4, 1)));
+  }
+
+  {
+    IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+    IntPair int_pairs[] = {{1, 1}};
+    cont.insert_range(int_pairs);
+    EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+                                  IntPair(4, 1)));
+  }
+
+  {
+    IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+    IntPair int_pairs[] = {{5, 1}};
+    cont.insert_range(int_pairs);
+    EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+                                  IntPair(4, 1), IntPair(5, 1)));
+  }
+
+  {
+    IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+    IntPair int_pairs[] = {{3, 2}, {1, 2}, {4, 2}, {2, 2}};
+    cont.insert_range(int_pairs);
+    EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
+                                  IntPair(4, 1)));
+  }
+
+  {
+    IntIntMap cont({{1, 1}, {2, 1}, {3, 1}, {4, 1}});
+    IntPair int_pairs[] = {{3, 2}, {1, 2}, {4, 2}, {2, 2}, {7, 2}, {6, 2},
+                           {8, 2}, {5, 2}, {5, 3}, {6, 3}, {7, 3}, {8, 3}};
+    cont.insert_range(int_pairs);
     EXPECT_THAT(cont, ElementsAre(IntPair(1, 1), IntPair(2, 1), IntPair(3, 1),
                                   IntPair(4, 1), IntPair(5, 2), IntPair(6, 2),
                                   IntPair(7, 2), IntPair(8, 2)));
diff --git a/base/containers/id_map.h b/base/containers/id_map.h
index f84c7c5..071be24 100644
--- a/base/containers/id_map.h
+++ b/base/containers/id_map.h
@@ -256,6 +256,7 @@
     using inner_iterator = typename HashTable::iterator;
     inner_iterator iter_;
 
+    KeyIterator() = default;
     KeyIterator(inner_iterator iter) : iter_(iter) {}
     KeyType operator*() const { return iter_->first; }
     KeyIterator& operator++() {
diff --git a/base/types/is_arc_pointer.h b/base/types/is_arc_pointer.h
new file mode 100644
index 0000000..62f8154
--- /dev/null
+++ b/base/types/is_arc_pointer.h
@@ -0,0 +1,23 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TYPES_IS_ARC_POINTER_H_
+#define BASE_TYPES_IS_ARC_POINTER_H_
+
+namespace base {
+
+// Detects whether T is a pointer managed by Objective-C Automatic
+// Reference Counting. Per
+// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#bridged-casts,
+// a __bridge cast to void* is only allowed if the argument has
+// retainable object pointer type.
+#if defined(__OBJC__)
+template <typename T>
+concept IsArcPointer =
+    requires(const T& v) { (__bridge const volatile void*)(v); };
+#endif
+
+}  // namespace base
+
+#endif  // BASE_TYPES_IS_ARC_POINTER_H_
diff --git a/base/types/is_arc_pointer_unittest.mm b/base/types/is_arc_pointer_unittest.mm
new file mode 100644
index 0000000..bbb21f9a
--- /dev/null
+++ b/base/types/is_arc_pointer_unittest.mm
@@ -0,0 +1,24 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/types/is_arc_pointer.h"
+
+#import <Foundation/Foundation.h>
+
+namespace base {
+namespace {
+
+// Some basic pointers that should not be ARC managed.
+static_assert(!IsArcPointer<int*>);
+static_assert(!IsArcPointer<void*>);
+
+// Objective-C object pointers
+static_assert(IsArcPointer<id>);
+static_assert(IsArcPointer<NSString*>);
+
+// Block pointers
+static_assert(IsArcPointer<void (^)()>);
+
+}  // namespace
+}  // namespace base
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index c0c5d06a..c9680e1 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # Used to cause full rebuilds on libc++ rolls. This should be kept in sync
   # with the libcxx_revision vars in //DEPS.
-  libcxx_revision = "9be1056e883d3fe5cd6666ac69702a6c5e9a39df"
+  libcxx_revision = "6ae6f38d10eda881c16d91932348fc6d4ee98332"
 }
diff --git a/cc/test/pixel_test.cc b/cc/test/pixel_test.cc
index dbaa6920..8d4c888 100644
--- a/cc/test/pixel_test.cc
+++ b/cc/test/pixel_test.cc
@@ -62,6 +62,10 @@
   // floating point badness in texcoords.
   renderer_settings_.dont_round_texture_sizes_for_pixel_tests = true;
 
+  // Copy requests force full damage, but OutputSurface-based readback can test
+  // incremental damage cases.
+  renderer_settings_.partial_swap_enabled = true;
+
   // Check if the graphics backend needs to initialize Vulkan, Dawn.
   bool init_vulkan = false;
   bool init_dawn = false;
@@ -122,25 +126,35 @@
     const gfx::Rect* copy_rect) {
   base::RunLoop run_loop;
 
-  std::unique_ptr<viz::CopyOutputRequest> request =
-      std::make_unique<viz::CopyOutputRequest>(
-          viz::CopyOutputRequest::ResultFormat::RGBA,
-          viz::CopyOutputRequest::ResultDestination::kSystemMemory,
-          base::BindOnce(&PixelTest::ReadbackResult, base::Unretained(this),
-                         run_loop.QuitClosure()));
-  if (copy_rect) {
-    request->set_area(*copy_rect);
+  const bool use_copy_request = target != pass_list->back().get();
+  if (use_copy_request) {
+    std::unique_ptr<viz::CopyOutputRequest> request =
+        std::make_unique<viz::CopyOutputRequest>(
+            viz::CopyOutputRequest::ResultFormat::RGBA,
+            viz::CopyOutputRequest::ResultDestination::kSystemMemory,
+            base::BindOnce(&PixelTest::ReadbackResult, base::Unretained(this),
+                           run_loop.QuitClosure()));
+    if (copy_rect) {
+      request->set_area(*copy_rect);
+    }
+    target->copy_requests.push_back(std::move(request));
   }
-  target->copy_requests.push_back(std::move(request));
 
   float device_scale_factor = 1.f;
   renderer_->DrawFrame(pass_list, device_scale_factor, device_viewport_size_,
                        display_color_spaces_,
                        std::move(surface_damage_rect_list_));
 
-  // Call SwapBuffersSkipped(), so the renderer can have a chance to release
-  // resources.
-  renderer_->SwapBuffersSkipped();
+  if (use_copy_request) {
+    // Call SwapBuffersSkipped(), so the renderer can have a chance to release
+    // resources.
+    renderer_->SwapBuffersSkipped();
+  } else {
+    renderer_->SwapBuffers(viz::DirectRenderer::SwapFrameData());
+    output_surface_->ReadbackForTesting(
+        base::BindOnce(&PixelTest::ReadbackResult, base::Unretained(this),
+                       run_loop.QuitClosure()));
+  }
 
   // Wait for the readback to complete.
   run_loop.Run();
@@ -149,20 +163,20 @@
 bool PixelTest::RunPixelTest(viz::AggregatedRenderPassList* pass_list,
                              const base::FilePath& ref_file,
                              const PixelComparator& comparator) {
-  return RunPixelTestWithReadbackTarget(pass_list, pass_list->back().get(),
-                                        ref_file, comparator);
+  return RunPixelTestWithCopyOutputRequest(pass_list, pass_list->back().get(),
+                                           ref_file, comparator);
 }
 
-bool PixelTest::RunPixelTestWithReadbackTarget(
+bool PixelTest::RunPixelTestWithCopyOutputRequest(
     viz::AggregatedRenderPassList* pass_list,
     viz::AggregatedRenderPass* target,
     const base::FilePath& ref_file,
     const PixelComparator& comparator) {
-  return RunPixelTestWithReadbackTargetAndArea(pass_list, target, ref_file,
-                                               comparator, nullptr);
+  return RunPixelTestWithCopyOutputRequestAndArea(pass_list, target, ref_file,
+                                                  comparator, nullptr);
 }
 
-bool PixelTest::RunPixelTestWithReadbackTargetAndArea(
+bool PixelTest::RunPixelTestWithCopyOutputRequestAndArea(
     viz::AggregatedRenderPassList* pass_list,
     viz::AggregatedRenderPass* target,
     const base::FilePath& ref_file,
diff --git a/cc/test/pixel_test.h b/cc/test/pixel_test.h
index c56369d..ad155509 100644
--- a/cc/test/pixel_test.h
+++ b/cc/test/pixel_test.h
@@ -74,12 +74,13 @@
                     SkBitmap ref_bitmap,
                     const PixelComparator& comparator);
 
-  bool RunPixelTestWithReadbackTarget(viz::AggregatedRenderPassList* pass_list,
-                                      viz::AggregatedRenderPass* target,
-                                      const base::FilePath& ref_file,
-                                      const PixelComparator& comparator);
+  bool RunPixelTestWithCopyOutputRequest(
+      viz::AggregatedRenderPassList* pass_list,
+      viz::AggregatedRenderPass* target,
+      const base::FilePath& ref_file,
+      const PixelComparator& comparator);
 
-  bool RunPixelTestWithReadbackTargetAndArea(
+  bool RunPixelTestWithCopyOutputRequestAndArea(
       viz::AggregatedRenderPassList* pass_list,
       viz::AggregatedRenderPass* target,
       const base::FilePath& ref_file,
diff --git a/cc/test/pixel_test_output_surface.cc b/cc/test/pixel_test_output_surface.cc
index badb974..cd7a373 100644
--- a/cc/test/pixel_test_output_surface.cc
+++ b/cc/test/pixel_test_output_surface.cc
@@ -12,6 +12,7 @@
 #include "components/viz/service/display/output_surface_frame.h"
 #include "gpu/command_buffer/common/swap_buffers_complete_params.h"
 #include "ui/gfx/buffer_format_util.h"
+#include "ui/gfx/geometry/skia_conversions.h"
 #include "ui/gfx/geometry/transform.h"
 #include "ui/gfx/presentation_feedback.h"
 #include "ui/gfx/swap_result.h"
@@ -60,4 +61,11 @@
   return gfx::OVERLAY_TRANSFORM_NONE;
 }
 
+void PixelTestOutputSurface::ReadbackForTesting(
+    base::OnceCallback<void(std::unique_ptr<viz::CopyOutputResult>)> callback) {
+  SkBitmap bitmap = software_device()->ReadbackForTesting();
+  std::move(callback).Run(std::make_unique<viz::CopyOutputSkBitmapResult>(
+      gfx::Rect(gfx::SkISizeToSize(bitmap.dimensions())), std::move(bitmap)));
+}
+
 }  // namespace cc
diff --git a/cc/test/pixel_test_output_surface.h b/cc/test/pixel_test_output_surface.h
index cb7a4a2c..9c4fd4c 100644
--- a/cc/test/pixel_test_output_surface.h
+++ b/cc/test/pixel_test_output_surface.h
@@ -30,6 +30,9 @@
       viz::UpdateVSyncParametersCallback callback) override;
   void SetDisplayTransformHint(gfx::OverlayTransform transform) override {}
   gfx::OverlayTransform GetDisplayTransform() override;
+  void ReadbackForTesting(
+      base::OnceCallback<void(std::unique_ptr<viz::CopyOutputResult>)> callback)
+      override;
 
  private:
   void SwapBuffersCallback();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
index 6941354..defd4f7 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
@@ -20,6 +20,7 @@
 import android.util.Size;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
@@ -410,7 +411,15 @@
                 resources.getDimension(R.dimen.tab_grid_thumbnail_favicon_background_down_shift),
                 context.getColor(R.color.modern_grey_800_alpha_38));
 
-        mCurrentTabModelFilterSupplier.addObserver(mOnTabModelFilterChanged);
+        // Run this immediately if non-null as in the TabListEditor context we might try to load
+        // tabs thumbnails before the post task normally run by ObservableSupplier#addObserver is
+        // run.
+        @Nullable
+        TabModelFilter currentFilter =
+                mCurrentTabModelFilterSupplier.addObserver(mOnTabModelFilterChanged);
+        if (currentFilter != null) {
+            mOnTabModelFilterChanged.onResult(currentFilter);
+        }
     }
 
     private void onTabModelFilterChanged(TabModelFilter filter) {
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 8ce1b6e..69ecbde 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
@@ -1218,14 +1218,32 @@
 
         // 2. Rebuild the strip.
         computeAndUpdateTabOrders(!closingLastTab, false);
+    }
 
-        mUpdateHost.requestUpdate();
+    /**
+     * Called when a multiple tabs are being closed. When called, the closing tabs will not be part
+     * of the model.
+     *
+     * @param tabs The list of tabs that are being closed.
+     */
+    public void multipleTabsClosed(List<Tab> tabs) {
+        // 1. Find out if we're closing the last tab.  This determines if we resize immediately.
+        // We know mStripTabs.length >= 1 because findTabById did not return null.
+        boolean closingLastTab = false;
+        for (Tab tab : tabs) {
+            if (mStripTabs[mStripTabs.length - 1].getTabId() == tab.getId()) {
+                closingLastTab = true;
+                break;
+            }
+        }
+
+        // 2. Rebuild the strip.
+        computeAndUpdateTabOrders(!closingLastTab, false);
     }
 
     /** Called when all tabs are closed at once. */
     public void willCloseAllTabs() {
         computeAndUpdateTabOrders(true, false);
-        mUpdateHost.requestUpdate();
     }
 
     /**
@@ -2567,6 +2585,17 @@
                 shouldShowTrailingMargins);
     }
 
+    /**
+     * Rebuilds the list of {@link StripLayoutTab}s based on the {@link TabModel}. Reuses strip tabs
+     * that still exist in the model. Sets tabs at their new position and animates any width
+     * changes, unless a multi-step close is running. Requests a layout update.
+     *
+     * @param delayResize Whether or not the resultant width changes should be delayed (for the
+     *     multi-step close animation.
+     * @param deferAnimations Whether or not the resultant width changes should automatically run,
+     *     or returned as a list to be kicked off simultaneously with other animations.
+     * @return The list of width {@link Animator}s to run, if any.
+     */
     private List<Animator> computeAndUpdateTabOrders(boolean delayResize, boolean deferAnimations) {
         final int count = mModel.getCount();
         StripLayoutTab[] tabs = new StripLayoutTab[count];
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 6524104..fa44104 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
@@ -1252,12 +1252,13 @@
 
                     @Override
                     public void willCloseTab(Tab tab, boolean didCloseAlone) {
-                        getStripLayoutHelper(tab.isIncognito()).willCloseTab(time(), tab);
+                        getStripLayoutHelper(tab.isIncognitoBranded()).willCloseTab(time(), tab);
                     }
 
                     @Override
                     public void tabRemoved(Tab tab) {
-                        getStripLayoutHelper(tab.isIncognito()).tabClosed(time(), tab.getId());
+                        getStripLayoutHelper(tab.isIncognitoBranded())
+                                .tabClosed(time(), tab.getId());
                         updateModelSwitcherButton();
                     }
 
@@ -1265,7 +1266,7 @@
                     public void didMoveTab(Tab tab, int newIndex, int curIndex) {
                         // For right-direction move, layout helper re-ordering logic
                         // expects destination index = position + 1
-                        getStripLayoutHelper(tab.isIncognito())
+                        getStripLayoutHelper(tab.isIncognitoBranded())
                                 .tabMoved(
                                         time(),
                                         tab.getId(),
@@ -1275,7 +1276,7 @@
 
                     @Override
                     public void tabClosureUndone(Tab tab) {
-                        getStripLayoutHelper(tab.isIncognito())
+                        getStripLayoutHelper(tab.isIncognitoBranded())
                                 .tabClosureCancelled(time(), tab.getId());
                         updateModelSwitcherButton();
                     }
@@ -1289,13 +1290,23 @@
 
                     @Override
                     public void tabPendingClosure(Tab tab) {
-                        getStripLayoutHelper(tab.isIncognito()).tabClosed(time(), tab.getId());
+                        getStripLayoutHelper(tab.isIncognitoBranded())
+                                .tabClosed(time(), tab.getId());
+                        updateModelSwitcherButton();
+                    }
+
+                    @Override
+                    public void multipleTabsPendingClosure(List<Tab> tabs, boolean isAllTabs) {
+                        if (tabs.isEmpty()) return;
+                        getStripLayoutHelper(tabs.get(0).isIncognitoBranded())
+                                .multipleTabsClosed(tabs);
                         updateModelSwitcherButton();
                     }
 
                     @Override
                     public void onFinishingTabClosure(Tab tab) {
-                        getStripLayoutHelper(tab.isIncognito()).tabClosed(time(), tab.getId());
+                        getStripLayoutHelper(tab.isIncognitoBranded())
+                                .tabClosed(time(), tab.getId());
                         updateModelSwitcherButton();
                     }
 
@@ -1308,7 +1319,7 @@
                     @Override
                     public void didSelectTab(Tab tab, @TabSelectionType int type, int lastId) {
                         if (tab.getId() == lastId) return;
-                        getStripLayoutHelper(tab.isIncognito())
+                        getStripLayoutHelper(tab.isIncognitoBranded())
                                 .tabSelected(time(), tab.getId(), lastId, false);
                     }
 
@@ -1316,7 +1327,7 @@
                     public void didAddTab(
                             Tab tab, int type, int creationState, boolean markedForSelection) {
                         boolean onStartup = type == TabLaunchType.FROM_RESTORE;
-                        getStripLayoutHelper(tab.isIncognito())
+                        getStripLayoutHelper(tab.isIncognitoBranded())
                                 .tabCreated(
                                         time(),
                                         tab.getId(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorProfileSupplier.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorProfileSupplier.java
index e9059c3..c286d3d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorProfileSupplier.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorProfileSupplier.java
@@ -96,6 +96,8 @@
         if (profile == null) {
             throw new IllegalStateException("Null is not a valid value to set for the profile.");
         }
+        // TODO(365814339): Convert to checked exception once all callsites are fixed.
+        assert !profile.shutdownStarted() : "Attempting to set an already destroyed Profile";
         super.set(profile);
     }
 
@@ -108,6 +110,8 @@
             // to be notified when the profile becomes available.
             throw new IllegalStateException("Attempting to read a null profile from the supplier");
         }
+        // TODO(365814339): Convert to checked exception once all callsites are fixed.
+        assert !profile.shutdownStarted() : "Attempting to access an already destroyed Profile";
         return profile;
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
index ebba86e..fce51da 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
@@ -682,11 +682,14 @@
                 2,
                 focusListener.getTimesUnfocused());
         Assert.assertFalse("oldTab should not have focus.", focusListener.hasFocus());
-        Assert.assertTrue(
-                "Keyboard should show",
-                sActivityTestRule
-                        .getKeyboardDelegate()
-                        .isKeyboardShowing(sActivityTestRule.getActivity(), urlBar));
+        CriteriaHelper.pollUiThread(
+                () -> {
+                    boolean keyboardVisible =
+                            sActivityTestRule
+                                    .getKeyboardDelegate()
+                                    .isKeyboardShowing(sActivityTestRule.getActivity(), urlBar);
+                    Criteria.checkThat(keyboardVisible, Matchers.is(true));
+                });
 
         // Check refocus doesn't happen again on the closure being finalized.
         ThreadUtils.runOnUiThreadBlocking(() -> model.commitAllTabClosures());
@@ -701,11 +704,14 @@
                 focusListener.getTimesUnfocused());
         Assert.assertFalse("oldTab should remain unfocused.", focusListener.hasFocus());
 
-        Assert.assertTrue(
-                "Keyboard should show",
-                sActivityTestRule
-                        .getKeyboardDelegate()
-                        .isKeyboardShowing(sActivityTestRule.getActivity(), urlBar));
+        CriteriaHelper.pollUiThread(
+                () -> {
+                    boolean keyboardVisible =
+                            sActivityTestRule
+                                    .getKeyboardDelegate()
+                                    .isKeyboardShowing(sActivityTestRule.getActivity(), urlBar);
+                    Criteria.checkThat(keyboardVisible, Matchers.is(true));
+                });
 
         // Ensure the keyboard is hidden so we are in a clean-slate for next test.
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> urlBar.clearFocus());
@@ -714,11 +720,14 @@
                 .runOnMainSync(() -> oldTab.getView().requestFocus());
         UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
 
-        Assert.assertFalse(
-                "Keyboard should no longer show",
-                sActivityTestRule
-                        .getKeyboardDelegate()
-                        .isKeyboardShowing(sActivityTestRule.getActivity(), urlBar));
+        CriteriaHelper.pollUiThread(
+                () -> {
+                    boolean keyboardVisible =
+                            sActivityTestRule
+                                    .getKeyboardDelegate()
+                                    .isKeyboardShowing(sActivityTestRule.getActivity(), urlBar);
+                    Criteria.checkThat(keyboardVisible, Matchers.is(false));
+                });
     }
 
     @Test
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
index 9bdc6e0..79015d2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
@@ -3470,7 +3470,7 @@
                 "Tab strip should match tab model.",
                 expectedNumTabs,
                 mStripLayoutHelper.getStripLayoutTabsForTesting().length);
-        verify(mUpdateHost, times(7)).requestUpdate();
+        verify(mUpdateHost, times(6)).requestUpdate();
     }
 
     @Test
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 7dc5392..0029e73 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1807,7 +1807,7 @@
     "//chrome/browser/ui/autofill/payments",
     "//chrome/browser/ui/autofill/payments:impl",
     "//chrome/browser/ui/bluetooth:impl",
-    "//chrome/browser/ai",
+    "//chrome/browser/ai:impl",
     "//chrome/browser/autofill",
     "//chrome/browser/commerce:impl",
     "//chrome/browser/devtools",
@@ -1908,6 +1908,7 @@
     "//chrome/app/vector_icons",
     "//chrome/browser/accessibility:utils",
     "//chrome/browser/ai",
+    "//chrome/browser/ai:impl",
     "//chrome/browser/autofill",
     "//chrome/browser/bitmap_fetcher",
     "//chrome/browser/breadcrumbs",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index e1cf47ee..34288c40 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -11921,6 +11921,12 @@
          autofill::features::kAutofillEnablePaymentSettingsServerCardSave)},
 #endif  // BUILDFLAG(IS_ANDROID)
 
+    {"cert-verification-network-time",
+     flag_descriptions::kCertVerificationNetworkTimeName,
+     flag_descriptions::kCertVerificationNetworkTimeDescription,
+     kOsMac | kOsWin | kOsLinux | kOsAndroid,
+     FEATURE_VALUE_TYPE(features::kCertVerificationNetworkTime)},
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/ai/BUILD.gn b/chrome/browser/ai/BUILD.gn
index 7a8836c..a400ecc 100644
--- a/chrome/browser/ai/BUILD.gn
+++ b/chrome/browser/ai/BUILD.gn
@@ -2,38 +2,70 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+assert(is_win || is_mac || is_linux || is_chromeos || is_android)
+
 source_set("ai") {
   sources = [
     "ai_context_bound_object.h",
-    "ai_context_bound_object_set.cc",
     "ai_context_bound_object_set.h",
-    "ai_manager_keyed_service.cc",
     "ai_manager_keyed_service.h",
-    "ai_manager_keyed_service_factory.cc",
     "ai_manager_keyed_service_factory.h",
-    "ai_rewriter.cc",
     "ai_rewriter.h",
-    "ai_summarizer.cc",
     "ai_summarizer.h",
-    "ai_text_session.cc",
     "ai_text_session.h",
-    "ai_utils.cc",
     "ai_utils.h",
-    "ai_writer.cc",
     "ai_writer.h",
   ]
-
-  public_deps = [ "//chrome/browser:browser_public_dependencies" ]
-
-  deps = [
+  public_deps = [
     "//base",
     "//chrome/browser/profiles:profile",
-    "//components/keyed_service/core",
     "//components/optimization_guide/core",
     "//content/public/browser",
-    "//mojo/public/cpp/bindings",
+  ]
+}
 
-    # TODO: Use //chrome/browser instead after fixing cyclic dependency.
-    "//components/power_bookmarks/core",
+source_set("impl") {
+  sources = [
+    "ai_context_bound_object_set.cc",
+    "ai_manager_keyed_service.cc",
+    "ai_manager_keyed_service_factory.cc",
+    "ai_rewriter.cc",
+    "ai_summarizer.cc",
+    "ai_text_session.cc",
+    "ai_utils.cc",
+    "ai_writer.cc",
+  ]
+  public_deps = [ "//chrome/browser:browser_public_dependencies" ]
+  deps = [ ":ai" ]
+}
+
+source_set("test_support") {
+  testonly = true
+  sources = [
+    "ai_test_utils.cc",
+    "ai_test_utils.h",
+  ]
+  public_deps = [
+    "//chrome/browser/optimization_guide:test_support",
+    "//chrome/test:test_support",
+    "//testing/gmock:gmock",
+    "//testing/gtest:gtest",
+  ]
+  deps = [ ":ai" ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "ai_manager_keyed_service_unittest.cc",
+    "ai_rewriter_unittest.cc",
+    "ai_summarizer_unittest.cc",
+    "ai_text_session_unittest.cc",
+    "ai_writer_unittest.cc",
+  ]
+  deps = [
+    ":ai",
+    ":test_support",
+    "//components/optimization_guide/core:test_support",
   ]
 }
diff --git a/chrome/browser/ai/ai_manager_keyed_service.h b/chrome/browser/ai/ai_manager_keyed_service.h
index c822326b..8959022c 100644
--- a/chrome/browser/ai/ai_manager_keyed_service.h
+++ b/chrome/browser/ai/ai_manager_keyed_service.h
@@ -12,7 +12,6 @@
 #include "chrome/browser/ai/ai_context_bound_object_set.h"
 #include "chrome/browser/ai/ai_summarizer.h"
 #include "chrome/browser/ai/ai_text_session.h"
-#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "content/public/browser/browser_context.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
diff --git a/chrome/browser/ai/ai_test_utils.cc b/chrome/browser/ai/ai_test_utils.cc
index 5dee88c..ccdfb38 100644
--- a/chrome/browser/ai/ai_test_utils.cc
+++ b/chrome/browser/ai/ai_test_utils.cc
@@ -6,12 +6,7 @@
 
 #include "chrome/browser/ai/ai_manager_keyed_service.h"
 #include "chrome/browser/ai/ai_manager_keyed_service_factory.h"
-#include "chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
-#include "chrome/test/base/chrome_render_view_host_test_harness.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/mojom/ai/model_streaming_responder.mojom.h"
 
 AITestUtils::MockModelStreamingResponder::MockModelStreamingResponder() =
     default;
diff --git a/chrome/browser/ash/drive/drive_integration_service.cc b/chrome/browser/ash/drive/drive_integration_service.cc
index 2a83761d..cfbe93c2 100644
--- a/chrome/browser/ash/drive/drive_integration_service.cc
+++ b/chrome/browser/ash/drive/drive_integration_service.cc
@@ -1055,8 +1055,7 @@
 
   if (!remount_delay) {
     if (failed_to_mount && !is_online_) {
-      logger_.Log(logging::LOGGING_WARNING,
-                  "DriveFs failed to start; will retry when online");
+      LOG(WARNING) << "DriveFs failed to start; will retry when online";
       remount_when_online_ = true;
       return;
     }
@@ -1066,8 +1065,7 @@
     ++drivefs_total_failures_count_;
     if (drivefs_total_failures_count_ > 10) {
       mount_failed_ = true;
-      logger_.Log(logging::LOGGING_ERROR,
-                  "DriveFs is too crashy. Leaving it alone.");
+      LOG(ERROR) << "DriveFs is too crashy. Leaving it alone";
       RecordBulkPinningMountFailureReason(
           profile_, BulkPinningMountFailureReason::kMoreThanTenTotalFailures);
       for (Observer& observer : observers_) {
@@ -1078,8 +1076,7 @@
     }
     if (drivefs_consecutive_failures_count_ > 3) {
       mount_failed_ = true;
-      logger_.Log(logging::LOGGING_ERROR,
-                  "DriveFs keeps failing at start. Giving up.");
+      LOG(ERROR) << "DriveFs keeps failing at start. Giving up";
       RecordBulkPinningMountFailureReason(
           profile_, BulkPinningMountFailureReason::kThreeConsecutiveFailures);
       for (Observer& observer : observers_) {
@@ -1090,8 +1087,7 @@
     }
     remount_delay =
         Seconds(5 * (1 << (drivefs_consecutive_failures_count_ - 1)));
-    logger_.Log(logging::LOGGING_WARNING, "DriveFs died, retry in %d seconds",
-                static_cast<int>(remount_delay.value().InSeconds()));
+    LOG(WARNING) << "DriveFs died, retry in " << remount_delay.value();
   }
 
   SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
diff --git a/chrome/browser/extensions/web_accessible_resources_browsertest.cc b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
index f88cac2..f012d828 100644
--- a/chrome/browser/extensions/web_accessible_resources_browsertest.cc
+++ b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
@@ -372,7 +372,7 @@
 
     // Test extension resource accessibility.
     auto server_redirect = [&](int expect_net_error, const char* resource,
-                               int histogram_count) {
+                               bool is_accessible) {
       GURL gurl = embedded_test_server()->GetURL(
           "example.com",
           base::StringPrintf(
@@ -387,13 +387,12 @@
       EXPECT_EQ(expect_net_error == net::OK,
                 observer.last_navigation_succeeded());
       EXPECT_EQ(expect_net_error, observer.last_net_error_code());
-      histogram_tester.ExpectBucketCount(kHistogramName, false,
-                                         histogram_count);
+      histogram_tester.ExpectBucketCount(kHistogramName, is_accessible, 1);
     };
 
     // Test cases.
-    server_redirect(net::OK, "web_accessible_resource.html", 0);
-    server_redirect(net::OK, "resource.html", 1);
+    server_redirect(net::OK, "web_accessible_resource.html", true);
+    server_redirect(net::OK, "resource.html", false);
   }
 };
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index fa6bae3..048dc73 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1488,6 +1488,11 @@
     "expiry_milestone": 132
   },
   {
+    "name": "cert-verification-network-time",
+    "owners": [ "carlosil@chromium.org", "chrome-secure-web-and-net@google.com" ],
+    "expiry_milestone": 134
+  },
+  {
     "name": "chrome-cart-dom-based-heuristics",
     "owners": [ "yuezhanggg@chromium.org", "wychen@chromium.org"],
     "expiry_milestone": 122
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index d844ccd..b90702f 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -135,6 +135,13 @@
     "Use the Cdm Storage Database over the MediaLicenseDatabase for Cdm "
     "storage operations.";
 
+const char kCertVerificationNetworkTimeName[] =
+    "Network Time for Certificate Verification";
+const char kCertVerificationNetworkTimeDescription[] =
+    "Use time fetched from the network for certificate verification decisions. "
+    "If certificate verification fails with the network time, it will fall back"
+    " to system time.";
+
 const char kClassifyUrlOnProcessResponseEventName[] =
     "Classify Url on process response event";
 const char kClassifyUrlOnProcessResponseEventDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index ae07648a..c34274c88 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -118,6 +118,9 @@
 extern const char kCdmStorageDatabaseMigrationName[];
 extern const char kCdmStorageDatabaseMigrationDescription[];
 
+extern const char kCertVerificationNetworkTimeName[];
+extern const char kCertVerificationNetworkTimeDescription[];
+
 extern const char kClassifyUrlOnProcessResponseEventName[];
 extern const char kClassifyUrlOnProcessResponseEventDescription[];
 
diff --git a/chrome/browser/navigation_predictor/navigation_predictor.cc b/chrome/browser/navigation_predictor/navigation_predictor.cc
index fa38dac3..825e423 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor.cc
@@ -651,7 +651,7 @@
     std::vector<blink::mojom::AnchorElementPositionUpdatePtr> elements) {
   if (!base::FeatureList::IsEnabled(
           blink::features::kNavigationPredictorNewViewportFeatures)) {
-    mojo::ReportBadMessage(
+    ReportBadMessageAndDeleteThis(
         "ReportAnchorElementsPositionUpdate should only be called with "
         "kNavigationPredictorNewViewportFeatures enabled.");
     return;
diff --git a/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/ProfileKeyedMap.java b/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/ProfileKeyedMap.java
index 94eed22..cc4cb98 100644
--- a/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/ProfileKeyedMap.java
+++ b/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/ProfileKeyedMap.java
@@ -112,10 +112,9 @@
     public T getForProfile(Profile profile, Function<Profile, T> factory) {
         profile = getProfileToUse(profile, mProfileSelection);
 
-        if (profile.shutdownStarted()) {
-            throw new IllegalStateException(
-                    "Attempting to access profile keyed data on destroyed Profile");
-        }
+        // TODO(365814339): Convert to checked exception once all callsites are fixed.
+        assert !profile.shutdownStarted()
+                : "Attempting to access profile keyed data on destroyed Profile";
 
         T obj = mData.get(profile);
         if (obj == null) {
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit.cc
index fd62787..a51b0aca 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit.cc
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit.cc
@@ -576,6 +576,9 @@
   AttemptFastKillForDiscard(web_contents(), discard_reason);
 
   web_contents()->Discard();
+  tab_strip_model_->UpdateWebContentsStateAt(
+      tab_strip_model_->GetIndexOfWebContents(web_contents()),
+      TabChangeType::kAll);
 
   SetState(LifecycleUnitState::DISCARDED,
            DiscardReasonToStateChangeReason(discard_reason));
diff --git a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
index e61e612a..6deecb36 100644
--- a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
@@ -178,10 +178,13 @@
 
 }  // namespace
 
-class TabManagerTest : public InProcessBrowserTest {
+class TabManagerTest : public InProcessBrowserTest,
+                       public ::testing::WithParamInterface<bool> {
  public:
   TabManagerTest()
       : scoped_set_clocks_for_testing_(&test_clock_, &test_tick_clock_) {
+    scoped_feature_list_.InitWithFeatureState(features::kWebContentsDiscard,
+                                              GetParam());
     // Start with a non-null TimeTicks, as there is no discard protection for
     // a tab with a null focused timestamp.
     test_tick_clock_.Advance(kShortDelay);
@@ -232,6 +235,7 @@
   base::SimpleTestClock test_clock_;
   base::SimpleTestTickClock test_tick_clock_;
   ScopedSetClocksForTesting scoped_set_clocks_for_testing_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 class TabManagerTestWithTwoTabs : public TabManagerTest {
@@ -259,7 +263,7 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(TabManagerTest, TabManagerBasics) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, TabManagerBasics) {
   ASSERT_TRUE(embedded_test_server()->Start());
   const GURL url1 = embedded_test_server()->GetURL("a.com", "/title1.html");
   const GURL url2 = embedded_test_server()->GetURL("a.com", "/title2.html");
@@ -377,7 +381,7 @@
   EXPECT_TRUE(chrome::CanGoForward(browser()));
 }
 
-IN_PROC_BROWSER_TEST_F(TabManagerTest, InvalidOrEmptyURL) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, InvalidOrEmptyURL) {
   // Open two tabs. Wait for the foreground one to load but do not wait for the
   // background one.
   NavigateToURLWithDisposition(browser(), GURL(chrome::kChromeUIAboutURL),
@@ -403,7 +407,7 @@
 
 // Makes sure that the TabDiscardDoneCB callback is called after
 // DiscardTabImpl() returns.
-IN_PROC_BROWSER_TEST_F(TabManagerTest, TabDiscardDoneCallback) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, TabDiscardDoneCallback) {
   // Open two tabs.
   NavigateToURLWithDisposition(browser(), GURL(chrome::kChromeUIAboutURL),
                                WindowOpenDisposition::CURRENT_TAB,
@@ -428,7 +432,7 @@
 }
 
 // Makes sure that PDF pages are protected.
-IN_PROC_BROWSER_TEST_F(TabManagerTest, ProtectPDFPages) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, ProtectPDFPages) {
   // Start the embedded test server so we can get served the required PDF page.
   ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
   embedded_test_server()->StartAcceptingConnections();
@@ -453,7 +457,7 @@
 // Makes sure that recently opened or used tabs are protected.
 // These protections only apply on non-Ash desktop platforms. Check
 // TabLifecycleUnit::CanDiscard for more details.
-IN_PROC_BROWSER_TEST_F(TabManagerTest,
+IN_PROC_BROWSER_TEST_P(TabManagerTest,
                        ProtectRecentlyUsedTabsFromUrgentDiscarding) {
   TabManager* tab_manager = g_browser_process->GetTabManager();
 
@@ -503,7 +507,7 @@
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Makes sure that tabs using media devices are protected.
-IN_PROC_BROWSER_TEST_F(TabManagerTest, ProtectVideoTabs) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, ProtectVideoTabs) {
   // Open 2 tabs, the second one being in the background.
   ASSERT_TRUE(
       ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIAboutURL)));
@@ -552,7 +556,7 @@
 #define MAYBE_ProtectDevToolsTabsFromDiscarding \
   ProtectDevToolsTabsFromDiscarding
 #endif
-IN_PROC_BROWSER_TEST_F(TabManagerTest,
+IN_PROC_BROWSER_TEST_P(TabManagerTest,
                        MAYBE_ProtectDevToolsTabsFromDiscarding) {
   // Get two tabs open, the second one being the foreground tab.
   GURL test_page(ui_test_utils::GetTestUrl(
@@ -595,7 +599,7 @@
 #else
 #define MAYBE_AutoDiscardable AutoDiscardable
 #endif
-IN_PROC_BROWSER_TEST_F(TabManagerTest, MAYBE_AutoDiscardable) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, MAYBE_AutoDiscardable) {
   // Get two tabs open.
   NavigateToURLWithDisposition(browser(), GURL(chrome::kChromeUIAboutURL),
                                WindowOpenDisposition::CURRENT_TAB,
@@ -624,7 +628,7 @@
   EXPECT_TRUE(IsTabDiscarded(GetWebContentsAt(0)));
 }
 
-IN_PROC_BROWSER_TEST_F(TabManagerTestWithTwoTabs,
+IN_PROC_BROWSER_TEST_P(TabManagerTestWithTwoTabs,
                        UrgentFastShutdownSingleTabProcess) {
   // The Tab Manager should be able to fast-kill a process for the discarded tab
   // on all platforms, as each tab will be running in a separate process by
@@ -637,7 +641,7 @@
   observer.Wait();
 }
 
-IN_PROC_BROWSER_TEST_F(TabManagerTest, UrgentFastShutdownSharedTabProcess) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, UrgentFastShutdownSharedTabProcess) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
   // Set max renderers to 1 before opening tabs to force running out of
@@ -658,7 +662,7 @@
       tab_manager()->DiscardTabImpl(LifecycleUnitDiscardReason::URGENT));
 }
 
-IN_PROC_BROWSER_TEST_F(TabManagerTest, UrgentFastShutdownWithUnloadHandler) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, UrgentFastShutdownWithUnloadHandler) {
   ASSERT_TRUE(embedded_test_server()->Start());
   // Disable the protection of recent tabs.
   OpenTwoTabs(embedded_test_server()->GetURL("a.com", "/title1.html"),
@@ -682,7 +686,7 @@
 #endif  // BUILDFLAG(IS_CHROMEOS)
 }
 
-IN_PROC_BROWSER_TEST_F(TabManagerTest,
+IN_PROC_BROWSER_TEST_P(TabManagerTest,
                        UrgentFastShutdownWithBeforeunloadHandler) {
   ASSERT_TRUE(embedded_test_server()->Start());
   // Disable the protection of recent tabs.
@@ -704,7 +708,7 @@
 // - Discard(kUrgent): ACTIVE->DISCARDED
 // - Navigate: DISCARDED->ACTIVE
 //             window.document.wasDiscarded is true
-IN_PROC_BROWSER_TEST_F(TabManagerTestWithTwoTabs, TabUrgentDiscardAndNavigate) {
+IN_PROC_BROWSER_TEST_P(TabManagerTestWithTwoTabs, TabUrgentDiscardAndNavigate) {
   const char kDiscardedStateJS[] = "window.document.wasDiscarded;";
 
   GURL test_page(ui_test_utils::GetTestUrl(
@@ -731,7 +735,7 @@
   EXPECT_EQ(true, content::EvalJs(GetWebContentsAt(0), kDiscardedStateJS));
 }
 
-IN_PROC_BROWSER_TEST_F(TabManagerTest, DiscardedTabHasNoProcess) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, DiscardedTabHasNoProcess) {
   GURL test_page(ui_test_utils::GetTestUrl(
       base::FilePath(), base::FilePath(FILE_PATH_LITERAL("simple.html"))));
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_page));
@@ -743,19 +747,24 @@
   ASSERT_TRUE(process);
   EXPECT_TRUE(process->IsInitializedAndNotDead());
   EXPECT_NE(base::kNullProcessHandle, process->GetProcess().Handle());
-  int renderer_id = process->GetID();
+  const int initial_renderer_id = process->GetID();
 
   // Discard the tab. This simulates a tab discard.
   TabLifecycleUnitExternal::FromWebContents(web_contents)
       ->DiscardTab(LifecycleUnitDiscardReason::URGENT);
-  content::WebContents* new_web_contents = tsm()->GetActiveWebContents();
-  EXPECT_NE(new_web_contents, web_contents);
-  web_contents = new_web_contents;
-  content::RenderProcessHost* new_process =
-      web_contents->GetPrimaryMainFrame()->GetProcess();
-  EXPECT_NE(new_process, process);
-  EXPECT_NE(new_process->GetID(), renderer_id);
-  process = new_process;
+
+  // Replacing the WebContents for the discard operation should result in
+  // assignment of a new RenderProcessHost.
+  if (!base::FeatureList::IsEnabled(features::kWebContentsDiscard)) {
+    content::WebContents* new_web_contents = tsm()->GetActiveWebContents();
+    EXPECT_NE(new_web_contents, web_contents);
+    web_contents = new_web_contents;
+    content::RenderProcessHost* new_process =
+        web_contents->GetPrimaryMainFrame()->GetProcess();
+    EXPECT_NE(new_process, process);
+    EXPECT_NE(new_process->GetID(), initial_renderer_id);
+    process = new_process;
+  }
 
   // The renderer process should be dead after a discard.
   EXPECT_EQ(process, web_contents->GetPrimaryMainFrame()->GetProcess());
@@ -772,7 +781,7 @@
   EXPECT_NE(base::kNullProcessHandle, process->GetProcess().Handle());
 }
 
-IN_PROC_BROWSER_TEST_F(TabManagerTest,
+IN_PROC_BROWSER_TEST_P(TabManagerTest,
                        TabManagerWasDiscardedCrossSiteSubFrame) {
   const char kDiscardedStateJS[] = "window.document.wasDiscarded;";
   // Navigate to a page with a cross-site frame.
@@ -871,7 +880,7 @@
 };
 
 // Tests that `window.document.wasDiscarded` is updated for a fenced frame.
-IN_PROC_BROWSER_TEST_F(TabManagerFencedFrameTest, TabManagerWasDiscarded) {
+IN_PROC_BROWSER_TEST_P(TabManagerFencedFrameTest, TabManagerWasDiscarded) {
   const char kDiscardedStateJS[] = "window.document.wasDiscarded;";
 
   // Navigate to a page with a fenced frame.
@@ -962,7 +971,7 @@
 #else
 #define MAYBE_DiscardTabsWithMinimizedWindow DiscardTabsWithMinimizedWindow
 #endif
-IN_PROC_BROWSER_TEST_F(TabManagerTest, MAYBE_DiscardTabsWithMinimizedWindow) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, MAYBE_DiscardTabsWithMinimizedWindow) {
   // Do not override the focused TabStripModel.
   GetTabLifecycleUnitSource()->SetFocusedTabStripModelForTesting(nullptr);
 
@@ -1007,7 +1016,7 @@
 #else
 #define MAYBE_DiscardTabsWithOccludedWindow DiscardTabsWithOccludedWindow
 #endif
-IN_PROC_BROWSER_TEST_F(TabManagerTest, MAYBE_DiscardTabsWithOccludedWindow) {
+IN_PROC_BROWSER_TEST_P(TabManagerTest, MAYBE_DiscardTabsWithOccludedWindow) {
   // Occluded browser.
   EnsureTabsInBrowser(browser(), 2);
   browser()->window()->SetBounds(gfx::Rect(10, 10, 10, 10));
@@ -1032,6 +1041,32 @@
       IsTabDiscarded(browser()->tab_strip_model()->GetWebContentsAt(1)));
 }
 
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    TabManagerTest,
+    ::testing::Values(false, true),
+    [](const ::testing::TestParamInfo<TabManagerTest::ParamType>& info) {
+      return info.param ? "RetainedWebContents" : "UnretainedWebContents";
+    });
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    TabManagerTestWithTwoTabs,
+    ::testing::Values(false, true),
+    [](const ::testing::TestParamInfo<TabManagerTestWithTwoTabs::ParamType>&
+           info) {
+      return info.param ? "RetainedWebContents" : "UnretainedWebContents";
+    });
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    TabManagerFencedFrameTest,
+    ::testing::Values(false, true),
+    [](const ::testing::TestParamInfo<TabManagerFencedFrameTest::ParamType>&
+           info) {
+      return info.param ? "RetainedWebContents" : "UnretainedWebContents";
+    });
+
 }  // namespace resource_coordinator
 
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
diff --git a/chrome/browser/resources/ash/settings/system_preferences_page/system_preferences_page.html b/chrome/browser/resources/ash/settings/system_preferences_page/system_preferences_page.html
index fb58bd4..b8a195c 100644
--- a/chrome/browser/resources/ash/settings/system_preferences_page/system_preferences_page.html
+++ b/chrome/browser/resources/ash/settings/system_preferences_page/system_preferences_page.html
@@ -37,7 +37,8 @@
 
   <!-- Date and Time subpages -->
   <template is="dom-if" route-path="/dateTime/timeZone">
-    <os-settings-subpage page-title="$i18n{timeZoneSubpageTitle}">
+    <os-settings-subpage page-title="$i18n{timeZoneSubpageTitle}"
+        learn-more-url="$i18n{timeZoneSettingsLearnMoreURL}">
       <timezone-subpage prefs="{{prefs}}"
           active-time-zone-display-name="{{activeTimeZoneDisplayName_}}">
       </timezone-subpage>
diff --git a/chrome/browser/resources/lens/overlay/lens_overlay_app.html b/chrome/browser/resources/lens/overlay/lens_overlay_app.html
index 4c1711c4..5bc5c887 100644
--- a/chrome/browser/resources/lens/overlay/lens_overlay_app.html
+++ b/chrome/browser/resources/lens/overlay/lens_overlay_app.html
@@ -16,6 +16,11 @@
     position: absolute;
   }
 
+  .button-container.button-container {
+    transition: opacity 150ms, visibility 150ms;
+  }
+
+  :host([should-fade-out-buttons]) .button-container,
   :host(:not([initial-flash-animation-has-ended])) .button-container {
     opacity: 0;
     visibility: hidden;
diff --git a/chrome/browser/resources/lens/overlay/lens_overlay_app.ts b/chrome/browser/resources/lens/overlay/lens_overlay_app.ts
index 11e179b..c8d74a9 100644
--- a/chrome/browser/resources/lens/overlay/lens_overlay_app.ts
+++ b/chrome/browser/resources/lens/overlay/lens_overlay_app.ts
@@ -81,6 +81,12 @@
         value: loadTimeData.getBoolean('enableOverlayTranslateButton'),
         readOnly: true,
       },
+      shouldFadeOutButtons: {
+        type: Boolean,
+        computed: 'computeShouldFadeOutButtons(isTranslateModeActive, ' +
+            'isTextLayerHighlightingText, isPointerDown)',
+        reflectToAttribute: true,
+      },
       theme: {
         type: Object,
         value: getFallbackTheme,
@@ -98,6 +104,17 @@
   private isClosing: boolean = false;
   // Whether more options menu should be shown.
   private moreOptionsMenuVisible: boolean = false;
+  // Whether the translate mode on the lens overlay has been activated. Updated
+  // in response to events dispatched from the translate button.
+  private isTranslateModeActive: boolean = false;
+  // Whether the text layer is highlighting text. Updated in response to events
+  // dispatched from the text layer.
+  private isTextLayerHighlightingText: boolean = false;
+  // Whether the user is pressing down on the selection overlay. Updated in
+  // response to events dispatched from the selection overlay.
+  private isPointerDown: boolean = false;
+  // Whether the button containers should be faded out.
+  private shouldFadeOutButtons: boolean = false;
   // The overlay theme.
   private theme: OverlayTheme;
 
@@ -136,6 +153,14 @@
         document, 'set-cursor-tooltip', (e: CustomEvent<CursorTooltipData>) => {
           this.$.cursorTooltip.setTooltip(e.detail.tooltipType);
         });
+    this.eventTracker_.add(
+        document, 'translate-mode-state-changed', (e: CustomEvent) => {
+          this.isTranslateModeActive = e.detail.translateModeEnabled;
+        });
+    this.eventTracker_.add(
+        document, 'text-selection-state-changed', (e: CustomEvent) => {
+          this.isTextLayerHighlightingText = e.detail.highlightingText;
+        });
   }
 
   override disconnectedCallback() {
@@ -241,11 +266,13 @@
 
   private handleSelectionOverlayClicked() {
     this.$.cursorTooltip.setPauseTooltipChanges(true);
+    this.isPointerDown = true;
   }
 
   private handlePointerReleased() {
     this.$.initialGradient.triggerHideScrimAnimation();
     this.$.cursorTooltip.setPauseTooltipChanges(false);
+    this.isPointerDown = false;
   }
 
   private onScreenshotRendered() {
@@ -257,6 +284,11 @@
     this.$.initialGradient.setScrimVisible();
   }
 
+  private computeShouldFadeOutButtons(): boolean {
+    return !this.isTranslateModeActive &&
+        (this.isTextLayerHighlightingText || this.isPointerDown);
+  }
+
   private async showTextCopiedToast() {
     if (this.$.copyToast.open) {
       // If toast already open, wait after hiding so that animation is
diff --git a/chrome/browser/resources/lens/overlay/text_layer.ts b/chrome/browser/resources/lens/overlay/text_layer.ts
index 6f40b925..a5f3d39 100644
--- a/chrome/browser/resources/lens/overlay/text_layer.ts
+++ b/chrome/browser/resources/lens/overlay/text_layer.ts
@@ -357,6 +357,7 @@
     this.selectionStartIndex = wordIndex;
     this.selectionEndIndex = wordIndex;
     this.isSelectingText = true;
+    this.dispatchTextHighlightState();
     return true;
   }
 
@@ -535,11 +536,13 @@
         'hide-selected-text-context-menu', {bubbles: true, composed: true}));
     this.dispatchEvent(new CustomEvent(
         'hide-detected-text-context-menu', {bubbles: true, composed: true}));
+    this.dispatchTextHighlightState();
   }
 
   private selectWords(selectionStartIndex: number, selectionEndIndex: number) {
     this.selectionStartIndex = selectionStartIndex;
     this.selectionEndIndex = selectionEndIndex;
+    this.dispatchTextHighlightState();
   }
 
   private onTextReceived(text: Text) {
@@ -1192,6 +1195,17 @@
     return parseInt(wordIndexString) ?? null;
   }
 
+  private dispatchTextHighlightState() {
+    this.dispatchEvent(new CustomEvent('text-selection-state-changed', {
+      bubbles: true,
+      composed: true,
+      detail: {
+        highlightingText:
+            this.selectionStartIndex !== -1 && this.selectionEndIndex !== -1,
+      },
+    }));
+  }
+
   // Testing method to get the words on the page.
   getWordNodesForTesting() {
     return this.shadowRoot!.querySelectorAll('.word');
diff --git a/chrome/browser/resources/privacy_sandbox/BUILD.gn b/chrome/browser/resources/privacy_sandbox/BUILD.gn
index 3f8a6c76..e5f0fdc07 100644
--- a/chrome/browser/resources/privacy_sandbox/BUILD.gn
+++ b/chrome/browser/resources/privacy_sandbox/BUILD.gn
@@ -30,7 +30,6 @@
     "privacy_sandbox_dialog_app.ts",
     "privacy_sandbox_combined_dialog_app.ts",
     "privacy_sandbox_notice_dialog_app.ts",
-    "privacy_sandbox_privacy_policy_step.ts",
     "privacy_sandbox_notice_restricted_dialog_app.ts",
     "privacy_sandbox_dialog_consent_step.ts",
     "privacy_sandbox_dialog_notice_step.ts",
diff --git a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_combined_dialog_app.html b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_combined_dialog_app.html
index a8c9b43..6a19fdf 100644
--- a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_combined_dialog_app.html
+++ b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_combined_dialog_app.html
@@ -25,8 +25,7 @@
 
   <privacy-sandbox-dialog-consent-step id="[[stepEnum_.CONSENT]]" slot="view"
       fill-content
-      on-consent-resolved="onConsentStepResolved_"
-      on-privacy-policy-page="openPrivacyPolicyPage_">
+      on-consent-resolved="onConsentStepResolved_">
   </privacy-sandbox-dialog-consent-step>
 
   <div id="[[stepEnum_.SAVING]]" class="saving-step" slot="view">
@@ -38,8 +37,4 @@
       fill-content>
   </privacy-sandbox-dialog-notice-step>
 
-  <privacy-sandbox-privacy-policy-step id="[[stepEnum_.PRIVACYPOLICY]]" slot="view"
-      fill-content>
-  </privacy-sandbox-privacy-policy-step>
-
 </cr-view-manager>
diff --git a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_combined_dialog_app.ts b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_combined_dialog_app.ts
index 5702838e..f6609855 100644
--- a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_combined_dialog_app.ts
+++ b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_combined_dialog_app.ts
@@ -9,7 +9,6 @@
 import './shared_style.css.js';
 import './privacy_sandbox_dialog_consent_step.js';
 import './privacy_sandbox_dialog_notice_step.js';
-import './privacy_sandbox_privacy_policy_step.js';
 
 import type {CrViewManagerElement} from 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.js';
 import {assert} from 'chrome://resources/js/assert.js';
@@ -24,7 +23,6 @@
   CONSENT = 'consent',
   SAVING = 'saving',
   NOTICE = 'notice',
-  PRIVACYPOLICY = 'privacy-policy',
 }
 
 export interface PrivacySandboxCombinedDialogAppElement {
@@ -97,10 +95,6 @@
         });
   }
 
-  private openPrivacyPolicyPage_() {
-    this.navigateToStep_(PrivacySandboxCombinedDialogStep.PRIVACYPOLICY);
-  }
-
   private navigateToStep_(step: PrivacySandboxCombinedDialogStep):
       Promise<void> {
     assert(step !== this.step_);
diff --git a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_consent_step.html b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_consent_step.html
index c56420eb..2877cac4 100644
--- a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_consent_step.html
+++ b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_consent_step.html
@@ -1,23 +1,67 @@
-<style include="cr-shared-style shared-style"></style>
+<style include="cr-shared-style shared-style">
+  .iframe {
+    height: calc(100vh - var(--privacy-sandbox-dialog-buttons-row-height));
+    width: 100vw;
+  }
+  .iframe.hidden {
+    position: absolute;
+    z-index: -1;
+  }
 
-<div class="content-area custom-scrollbar" scrollable tabindex="-1">
-  <div class="header">
-    <img class="logo" alt="">
-    <h1>$i18n{m1ConsentTitle}</h1>
-  </div>
-  <div class="section cr-secondary-text">
-    $i18n{m1ConsentDescription1}
-  </div>
-  <div class="section cr-secondary-text">
-    $i18n{m1ConsentDescription2}
-  </div>
-  <div class="banner">
-    <img class="topics-banner" alt="">
-  </div>
-  <div class="section cr-secondary-text">
-    $i18n{m1ConsentDescription3}
-  </div>
-  <privacy-sandbox-dialog-learn-more class="learn-more-section"
+  .iframe.visible {
+    position: relative;
+    z-index: auto;
+  }
+
+  .back-button {
+    --cr-icon-button-size: 20px;
+    --cr-icon-button-icon-size: 20px;
+  }
+
+  .back-button.hidden {
+    pointer-events: none;
+    z-index: -1;
+    position: absolute;
+  }
+
+  .back-button.visible {
+    pointer-events: auto;
+    z-index: auto;
+    position: relative;
+  }
+
+  .content-area {
+    background-color: white;
+  }
+
+  .buttons-container {
+    background-color: white;
+  }
+
+  .iframe-container {
+    display: none;
+  }
+</style>
+
+<div id="consentNotice">
+  <div class="content-area custom-scrollbar" scrollable tabindex="-1">
+    <div class="header">
+      <img class="logo" alt="">
+      <h1>$i18n{m1ConsentTitle}</h1>
+    </div>
+    <div class="section cr-secondary-text">
+      $i18n{m1ConsentDescription1}
+    </div>
+    <div class="section cr-secondary-text">
+      $i18n{m1ConsentDescription2}
+    </div>
+    <div class="banner">
+      <img class="topics-banner" alt="">
+    </div>
+    <div class="section cr-secondary-text">
+      $i18n{m1ConsentDescription3}
+    </div>
+    <privacy-sandbox-dialog-learn-more class="learn-more-section"
       expanded="{{expanded_}}"
       title="$i18n{m1ConsentLearnMoreExpandLabel}">
       <ul class="cr-secondary-text">
@@ -25,24 +69,35 @@
         <li>$i18nRaw{m1ConsentLearnMoreBullet2}</li>
         <li>$i18nRaw{m1ConsentLearnMoreBullet3}</li>
       </ul>
-      <div id="privacyPolicyDiv" class="cr-secondary-text">$i18nRaw{m1ConsentLearnMoreLink}</div>
-  </privacy-sandbox-dialog-learn-more>
-  <div class="section cr-secondary-text" id="lastTextElement">
-    $i18n{m1ConsentDescription4}
+      <div id="privacyPolicyDiv" class="cr-secondary-text">
+        $i18nRaw{m1ConsentLearnMoreLink}</div>
+    </privacy-sandbox-dialog-learn-more>
+    <div class="section cr-secondary-text" id="lastTextElement">
+      $i18n{m1ConsentDescription4}
+    </div>
+    <div class="buttons-container">
+      <cr-button id="declineButton" on-click="onConsentDeclined_">
+        $i18n{m1ConsentDeclineButton}
+      </cr-button>
+      <cr-button id="confirmButton" on-click="onConsentAccepted_">
+        $i18n{m1ConsentAcceptButton}
+      </cr-button>
+    </div>
   </div>
-  <div class="buttons-container">
-    <cr-button id="declineButton" on-click="onConsentDeclined_">
-      $i18n{m1ConsentDeclineButton}
-    </cr-button>
-    <cr-button id="confirmButton" on-click="onConsentAccepted_">
-      $i18n{m1ConsentAcceptButton}
+  <div id="showMoreOverlay" hidden="[[wasScrolledToBottom]]">
+    <cr-button id="moreButton" on-click="onConsentMoreClicked"
+        class="action-button" aria-hidden="true" tabindex="-1">
+      $i18n{m1DialogMoreButton}
+      <cr-icon icon="cr:expand-more"></cr-icon>
     </cr-button>
   </div>
 </div>
-<div id="showMoreOverlay" hidden="[[wasScrolledToBottom]]">
-  <cr-button id="moreButton" on-click="onConsentMoreClicked"
-      class="action-button" aria-hidden="true" tabindex="-1">
-    $i18n{m1DialogMoreButton}
-    <cr-icon icon="cr:expand-more"></cr-icon>
-  </cr-button>
+<!-- TODO(crbug.com/358087159): Once UX mocks are approved,
+ fix the spacing and visual of the back button, this is currently a
+ placeholder for functionality. -->
+<div class="iframe-container">
+  <cr-icon-button id="backButton" class="icon-arrow-back back-button hidden"
+    on-click="onBackToNotice_"></cr-icon-button>
+  <iframe id="privacyPolicy" class="iframe hidden"
+    tabindex="-1" frameBorder="0"></iframe>
 </div>
diff --git a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_consent_step.ts b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_consent_step.ts
index a86c444..55e3dc5 100644
--- a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_consent_step.ts
+++ b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_consent_step.ts
@@ -38,6 +38,29 @@
     };
   }
 
+  private privacyPolicyPageClickStartTime_: number;
+  private privacyPolicyPageLoadEndTime_: number;
+
+  override ready() {
+    super.ready();
+
+    window.addEventListener('message', event => {
+      if (event.data.id === 'privacy-policy-loaded') {
+        this.privacyPolicyPageLoadEndTime_ = performance.now();
+        // TODO(crbug.com/358087159): Replace the console logs below with
+        // histograms for how long it takes to open the privacy policy page.
+        // Tracks when the privacy policy page is loaded after the link is
+        // clicked.
+        if (this.privacyPolicyPageClickStartTime_) {
+          // const duration = this.privacyPolicyPageLoadEndTime_ -
+          // this.privacyPolicyPageClickStartTime_; console.log(`Load time:
+          // ${duration} milliseconds`);
+        }
+        return;
+      }
+    });
+  }
+
   private onConsentAccepted_() {
     this.promptActionOccurred(PrivacySandboxPromptAction.CONSENT_ACCEPTED);
     this.dispatchEvent(
@@ -50,9 +73,60 @@
         new CustomEvent('consent-resolved', {bubbles: true, composed: true}));
   }
 
+  // TODO(crbug.com/358087159): Add metrics to check if privacy policy link ever
+  // fails to load.
   private onPrivacyPolicyLinkClicked_() {
-    this.dispatchEvent(new CustomEvent(
-        'privacy-policy-page', {bubbles: true, composed: true}));
+    this.privacyPolicyPageClickStartTime_ = performance.now();
+    // TODO(crbug.com/358087159): Replace the console logs below with histograms
+    // for how long it takes to open the privacy policy page. Tracks when the
+    // privacy policy page is loaded before the link is clicked.
+    if (this.privacyPolicyPageLoadEndTime_ &&
+        this.privacyPolicyPageLoadEndTime_ >
+            this.privacyPolicyPageClickStartTime_) {
+      // const duration = this.privacyPolicyPageLoadEndTime_ -
+      // this.privacyPolicyPageClickStartTime_; console.log(`Load time:
+      // ${duration} milliseconds`);
+      //  This is not really 0, but it just means it already pre-loaded.
+    } else if (
+        this.privacyPolicyPageLoadEndTime_ &&
+        this.privacyPolicyPageLoadEndTime_ <=
+            this.privacyPolicyPageClickStartTime_) {
+      // const duration = 0;
+      // console.log(`Load time: ${duration} milliseconds`);
+    }
+    // Move the consent notice to the back.
+    this.shadowRoot!.querySelector<HTMLElement>(
+                        '#consentNotice')!.style.display = 'none';
+
+    // Move the privacy policy iframe to the front.
+    const iframeContent =
+        this.shadowRoot!.querySelector<HTMLElement>('#privacyPolicy');
+    iframeContent!.classList.add('visible');
+    iframeContent!.classList.remove('hidden');
+
+    // Move the back button to the front.
+    const backButton =
+        this.shadowRoot!.querySelector<HTMLElement>('#backButton');
+    backButton!.classList.add('visible');
+    backButton!.classList.remove('hidden');
+  }
+
+  private onBackToNotice_() {
+    // Move the privacy policy iframe to the back.
+    const iframeContent =
+        this.shadowRoot!.querySelector<HTMLElement>('#privacyPolicy');
+    iframeContent!.classList.add('hidden');
+    iframeContent!.classList.remove('visible');
+
+    // Move the back button to the back.
+    const backButton =
+        this.shadowRoot!.querySelector<HTMLElement>('#backButton');
+    backButton!.classList.add('hidden');
+    backButton!.classList.remove('visible');
+
+    // Move the consent notice to the front.
+    this.shadowRoot!.querySelector<HTMLElement>(
+                        '#consentNotice')!.style.display = 'inline-block';
   }
 }
 
diff --git a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_mixin.ts b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_mixin.ts
index 26c79ad..c4940a46 100644
--- a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_mixin.ts
+++ b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_dialog_mixin.ts
@@ -39,6 +39,17 @@
             this.onContentSizeChanging_(/*expanding=*/ true);
             this.promptActionOccurred(
                 PrivacySandboxPromptAction.CONSENT_MORE_INFO_OPENED);
+            // If the iframe hasn't been loaded yet, load it the first time the
+            // learn more expand section is clicked.
+            if (this.shadowRoot!
+                    .querySelector<HTMLIFrameElement>('#privacyPolicy')!.src ===
+                '') {
+              this.shadowRoot!.querySelector<HTMLElement>(
+                                  '.iframe-container')!.style.display = 'block';
+              this.shadowRoot!
+                  .querySelector<HTMLIFrameElement>('#privacyPolicy')!.src =
+                  'chrome-untrusted://privacy-sandbox-dialog/privacy-policy';
+            }
           }
           if (!newValue && oldValue) {
             this.onContentSizeChanging_(/*expanding=*/ false);
diff --git a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy.html b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy.html
index 79ab123..e3a6fa4 100644
--- a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy.html
+++ b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy.html
@@ -1 +1,11 @@
-<embed src="$i18n{privacyPolicyURL}" height="100%" width="100%"></embed>
+<embed onload="loading()" src="$i18n{privacyPolicyURL}"
+  height="100%" width="100%"></embed>
+<script>
+  function loading(){
+    window.parent.postMessage(
+      {
+        id: 'privacy-policy-loaded',
+      },
+      'chrome://privacy-sandbox-dialog');
+  }
+</script>
diff --git a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy_step.html b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy_step.html
deleted file mode 100644
index 9c804c4..0000000
--- a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy_step.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<style include="cr-shared-style shared-style">
-  .iframe-content {
-    border-color: var(--scrollable-border-color);
-    border-style: solid;
-    border-width: 1px 0;
-    height: calc(100vh - var(--privacy-sandbox-dialog-buttons-row-height) - 2px);
-    width: 100%;
-  }
-</style>
-
-<iframe class="iframe-content" frameBorder="0"
-src="chrome-untrusted://privacy-sandbox-dialog/privacy-policy"></iframe>
diff --git a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy_step.ts b/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy_step.ts
deleted file mode 100644
index 86750df..0000000
--- a/chrome/browser/resources/privacy_sandbox/privacy_sandbox_privacy_policy_step.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'chrome://resources/cr_elements/cr_shared_style.css.js';
-import 'chrome://resources/cr_elements/cr_button/cr_button.js';
-import 'chrome://resources/cr_elements/icons.html.js';
-import 'chrome://resources/cr_elements/cr_icon/cr_icon.js';
-import './strings.m.js';
-import './shared_style.css.js';
-
-import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {getTemplate} from './privacy_sandbox_privacy_policy_step.html.js';
-
-export class PrivacySandboxPrivacyPolicyStepElement extends PolymerElement {
-  static get is() {
-    return 'privacy-sandbox-privacy-policy-step';
-  }
-
-  static get template() {
-    return getTemplate();
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    'privacy-sandbox-privacy-policy-step':
-        PrivacySandboxPrivacyPolicyStepElement;
-  }
-}
-
-customElements.define(
-    PrivacySandboxPrivacyPolicyStepElement.is,
-    PrivacySandboxPrivacyPolicyStepElement);
diff --git a/chrome/browser/ui/android/fakepdf/BUILD.gn b/chrome/browser/ui/android/fakepdf/BUILD.gn
deleted file mode 100644
index 4ec348f..0000000
--- a/chrome/browser/ui/android/fakepdf/BUILD.gn
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2024 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/android/rules.gni")
-
-android_library("java") {
-  sources = [
-    "java/src/org/chromium/chrome/browser/fakepdf/PdfDocument.java",
-    "java/src/org/chromium/chrome/browser/fakepdf/PdfDocumentListener.java",
-    "java/src/org/chromium/chrome/browser/fakepdf/PdfDocumentRequest.java",
-    "java/src/org/chromium/chrome/browser/fakepdf/PdfViewSettings.java",
-    "java/src/org/chromium/chrome/browser/fakepdf/PdfViewerFragment.java",
-    "java/src/org/chromium/chrome/browser/fakepdf/ProjectorContext.java",
-  ]
-  deps = [
-    "//third_party/androidx:androidx_annotation_annotation_java",
-    "//third_party/androidx:androidx_core_core_java",
-    "//third_party/androidx:androidx_fragment_fragment_java",
-  ]
-  resources_package = "org.chromium.chrome.browser.fakepdf"
-}
diff --git a/chrome/browser/ui/android/fakepdf/DIR_METADATA b/chrome/browser/ui/android/fakepdf/DIR_METADATA
deleted file mode 100644
index c4752f7..0000000
--- a/chrome/browser/ui/android/fakepdf/DIR_METADATA
+++ /dev/null
@@ -1,6 +0,0 @@
-buganizer_public: {
-  component_id: 1279895
-}
-team_email: "clank-large-form-factors@google.com"
-os: ANDROID
-
diff --git a/chrome/browser/ui/android/fakepdf/OWNERS b/chrome/browser/ui/android/fakepdf/OWNERS
deleted file mode 100644
index 6a9494f..0000000
--- a/chrome/browser/ui/android/fakepdf/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-shuyng@google.com
-qinmin@chromium.org
-twellington@chromium.org
diff --git a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfDocument.java b/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfDocument.java
deleted file mode 100644
index b653550..0000000
--- a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfDocument.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.fakepdf;
-
-/** Fake PDF viewer API. Will be removed once real APIs become available. */
-public class PdfDocument {
-    private boolean mSearchVisible;
-
-    public PdfDocument() {}
-
-    public boolean isSearchVisible() {
-        return mSearchVisible;
-    }
-
-    public void setIsSearchVisible(boolean value) {
-        mSearchVisible = value;
-    }
-}
diff --git a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfDocumentListener.java b/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfDocumentListener.java
deleted file mode 100644
index 5817ebd7..0000000
--- a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfDocumentListener.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.fakepdf;
-
-/** Fake PDF viewer API. Will be removed once real APIs become available. */
-public interface PdfDocumentListener {
-    default void onDocumentLoaded(PdfDocument document) {}
-
-    default void onDocumentLoadFailed(RuntimeException throwable) {}
-}
diff --git a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfDocumentRequest.java b/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfDocumentRequest.java
deleted file mode 100644
index 29c8c392..0000000
--- a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfDocumentRequest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.fakepdf;
-
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-import androidx.core.util.Preconditions;
-
-import java.io.File;
-
-/** Fake PDF viewer API. Will be removed once real APIs become available. */
-public class PdfDocumentRequest {
-    private final Uri mUri;
-    private final File mFile;
-
-    public PdfDocumentRequest(@NonNull Builder builder) {
-        Preconditions.checkNotNull(builder);
-        this.mUri = builder.mUri;
-        this.mFile = builder.mFile;
-    }
-
-    public Uri getUri() {
-        return this.mUri;
-    }
-
-    public File getFile() {
-        return this.mFile;
-    }
-
-    public static class Builder {
-        private Uri mUri;
-        private File mFile;
-        private PdfViewSettings mPdfViewSettings;
-
-        public Builder() {}
-
-        @NonNull
-        public Builder setUri(Uri uri) {
-            this.mUri = uri;
-            return this;
-        }
-
-        @NonNull
-        public Builder setFile(File file) {
-            this.mFile = file;
-            return this;
-        }
-
-        @NonNull
-        public Builder setPdfViewSettings(PdfViewSettings settings) {
-            this.mPdfViewSettings = settings;
-            return this;
-        }
-
-        @NonNull
-        public PdfDocumentRequest build() {
-            return new PdfDocumentRequest(this);
-        }
-    }
-}
diff --git a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfViewSettings.java b/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfViewSettings.java
deleted file mode 100644
index 50d3aefe..0000000
--- a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfViewSettings.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.fakepdf;
-
-/** Fake PDF viewer API. Will be removed once real APIs become available. */
-public class PdfViewSettings {
-    private final boolean mOverrideDefaultUrlClickBehavior;
-
-    public PdfViewSettings(boolean overrideDefaultUrlClickBehavior) {
-        this.mOverrideDefaultUrlClickBehavior = overrideDefaultUrlClickBehavior;
-    }
-
-    public boolean getOverrideDefaultUrlClickBehavior() {
-        return this.mOverrideDefaultUrlClickBehavior;
-    }
-}
diff --git a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfViewerFragment.java b/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfViewerFragment.java
deleted file mode 100644
index 6971dcc46..0000000
--- a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/PdfViewerFragment.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.fakepdf;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-
-import java.net.URL;
-
-/** Fake PDF viewer API. Will be removed once real APIs become available. */
-public class PdfViewerFragment extends Fragment {
-    public PdfViewerFragment() {}
-
-    public void loadRequest(
-            @NonNull PdfDocumentRequest request, @NonNull PdfDocumentListener documentListener) {}
-
-    public void addPdfEventsListener(@NonNull PdfEventsListener eventsListener) {}
-
-    public void removePdfEventsListener(@NonNull PdfEventsListener listener) {}
-
-    public void show(
-            @NonNull FragmentManager manager, @NonNull String tag, @NonNull int containerViewId) {}
-
-    public interface PdfEventsListener {
-        default void onHyperlinkClicked(URL url) {}
-    }
-}
diff --git a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/ProjectorContext.java b/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/ProjectorContext.java
deleted file mode 100644
index 314539d..0000000
--- a/chrome/browser/ui/android/fakepdf/java/src/org/chromium/chrome/browser/fakepdf/ProjectorContext.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.fakepdf;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-
-/** Fake PDF viewer API. Will be removed once real APIs become available. */
-public class ProjectorContext {
-    public static void installProjectorGlobalsForTest(@NonNull Context appContext) {}
-}
diff --git a/chrome/browser/ui/android/pdf/BUILD.gn b/chrome/browser/ui/android/pdf/BUILD.gn
index b34de3c2c..3e425e3 100644
--- a/chrome/browser/ui/android/pdf/BUILD.gn
+++ b/chrome/browser/ui/android/pdf/BUILD.gn
@@ -35,7 +35,6 @@
     "//base:base_java",
     "//base:content_uri_utils_java",
     "//chrome/browser/profiles/android:java",
-    "//chrome/browser/ui/android/fakepdf:java",
     "//chrome/browser/ui/android/native_page:java",
     "//chrome/browser/util:java",
     "//components/browser_ui/styles/android:java",
@@ -75,7 +74,6 @@
     "//base:base_java_test_support",
     "//base:base_junit_test_support",
     "//chrome/browser/profiles/android:java",
-    "//chrome/browser/ui/android/fakepdf:java",
     "//chrome/browser/ui/android/native_page:java",
     "//chrome/browser/util:java",
     "//components/embedder_support/android:util_java",
diff --git a/chrome/browser/ui/ash/graduation/BUILD.gn b/chrome/browser/ui/ash/graduation/BUILD.gn
new file mode 100644
index 0000000..3e6bd46
--- /dev/null
+++ b/chrome/browser/ui/ash/graduation/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/chromeos/ui_mode.gni")
+
+assert(is_chromeos_ash)
+
+component("graduation") {
+  sources = [
+    "graduation_manager.cc",
+    "graduation_manager.h",
+  ]
+
+  defines = [ "IS_GRADUATION_MANAGER_IMPL" ]
+
+  deps = [
+    "//base",
+    "//components/session_manager/core",
+  ]
+}
diff --git a/chrome/browser/ui/ash/graduation/DEPS b/chrome/browser/ui/ash/graduation/DEPS
new file mode 100644
index 0000000..1f2eaa2
--- /dev/null
+++ b/chrome/browser/ui/ash/graduation/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "-chrome",
+
+  "+chrome/browser/ui/ash/graduation",
+]
diff --git a/chrome/browser/ui/ash/graduation/OWNERS b/chrome/browser/ui/ash/graduation/OWNERS
new file mode 100644
index 0000000..7f1a4e1
--- /dev/null
+++ b/chrome/browser/ui/ash/graduation/OWNERS
@@ -0,0 +1 @@
+file://ash/child_accounts/OWNERS
diff --git a/chrome/browser/ui/ash/graduation/graduation_manager.cc b/chrome/browser/ui/ash/graduation/graduation_manager.cc
new file mode 100644
index 0000000..115abbc
--- /dev/null
+++ b/chrome/browser/ui/ash/graduation/graduation_manager.cc
@@ -0,0 +1,36 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/graduation/graduation_manager.h"
+
+namespace ash::graduation {
+namespace {
+GraduationManager* g_instance = nullptr;
+}
+
+GraduationManager::GraduationManager() {
+  CHECK_EQ(g_instance, nullptr);
+  g_instance = this;
+
+  // SessionManager may be unset in unit tests.
+  auto* session_manager = session_manager::SessionManager::Get();
+  if (session_manager) {
+    session_manager_observation_.Observe(session_manager);
+  }
+}
+
+GraduationManager::~GraduationManager() {
+  CHECK_EQ(g_instance, this);
+  g_instance = nullptr;
+}
+
+// static
+GraduationManager* GraduationManager::Get() {
+  return g_instance;
+}
+
+void GraduationManager::OnUserSessionStarted(bool is_primary) {
+  // TOOD(b/357882466): Implement adding initial app enablement state.
+}
+}  // namespace ash::graduation
diff --git a/chrome/browser/ui/ash/graduation/graduation_manager.h b/chrome/browser/ui/ash/graduation/graduation_manager.h
new file mode 100644
index 0000000..6e32743
--- /dev/null
+++ b/chrome/browser/ui/ash/graduation/graduation_manager.h
@@ -0,0 +1,38 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ASH_GRADUATION_GRADUATION_MANAGER_H_
+#define CHROME_BROWSER_UI_ASH_GRADUATION_GRADUATION_MANAGER_H_
+
+#include "base/component_export.h"
+#include "base/scoped_observation.h"
+#include "components/session_manager/core/session_manager.h"
+#include "components/session_manager/core/session_manager_observer.h"
+
+namespace ash::graduation {
+
+// Manages the state of the Graduation app depending on the status of the
+// Graduation enablement policy. The GraduationManager is a singleton that
+// should be created once per user session.
+class COMPONENT_EXPORT(GRADUATION_MANAGER) GraduationManager
+    : public session_manager::SessionManagerObserver {
+ public:
+  GraduationManager();
+  GraduationManager(const GraduationManager&) = delete;
+  GraduationManager& operator=(const GraduationManager&) = delete;
+  ~GraduationManager() override;
+
+  static GraduationManager* Get();
+
+  // session_manager::SessionManagerObserver:
+  void OnUserSessionStarted(bool is_primary) override;
+
+ private:
+  base::ScopedObservation<session_manager::SessionManager,
+                          session_manager::SessionManagerObserver>
+      session_manager_observation_{this};
+};
+}  // namespace ash::graduation
+
+#endif  // CHROME_BROWSER_UI_ASH_GRADUATION_GRADUATION_MANAGER_H_
diff --git a/chrome/browser/ui/ash/main_extra_parts/BUILD.gn b/chrome/browser/ui/ash/main_extra_parts/BUILD.gn
index 7addcb7..ac04985 100644
--- a/chrome/browser/ui/ash/main_extra_parts/BUILD.gn
+++ b/chrome/browser/ui/ash/main_extra_parts/BUILD.gn
@@ -56,6 +56,7 @@
     "//chrome/browser/ui/ash/arc",
     "//chrome/browser/ui/ash/cast_config",
     "//chrome/browser/ui/ash/desks",
+    "//chrome/browser/ui/ash/graduation",
     "//chrome/browser/ui/ash/in_session_auth",
     "//chrome/browser/ui/ash/input_method",
     "//chrome/browser/ui/ash/login",
diff --git a/chrome/browser/ui/ash/main_extra_parts/DEPS b/chrome/browser/ui/ash/main_extra_parts/DEPS
index 13cbf01f..97b79aa 100644
--- a/chrome/browser/ui/ash/main_extra_parts/DEPS
+++ b/chrome/browser/ui/ash/main_extra_parts/DEPS
@@ -17,6 +17,7 @@
   "+chrome/browser/ash/crosapi",
   "+chrome/browser/ash/geolocation",
   "+chrome/browser/ash/growth",
+  "+chrome/browser/ui/ash/graduation",
   "+chrome/browser/ash/input_device_settings",
   "+chrome/browser/ash/lobster",
   "+chrome/browser/ash/login/signin",
diff --git a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc
index e458f56e..42f3523 100644
--- a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.cc
@@ -63,6 +63,7 @@
 #include "chrome/browser/ui/ash/cast_config/cast_config_controller_media_router.h"
 #include "chrome/browser/ui/ash/crosapi_new_window_delegate.h"
 #include "chrome/browser/ui/ash/desks/desks_client.h"
+#include "chrome/browser/ui/ash/graduation/graduation_manager.h"
 #include "chrome/browser/ui/ash/in_session_auth/in_session_auth_dialog_client.h"
 #include "chrome/browser/ui/ash/in_session_auth/in_session_auth_token_provider_impl.h"
 #include "chrome/browser/ui/ash/input_method/ime_controller_client_impl.h"
@@ -417,6 +418,11 @@
             window, game_mode == GameMode::BOREALIS);
       }));
 
+  if (ash::features::IsGraduationEnabled()) {
+    graduation_manager_ =
+        std::make_unique<ash::graduation::GraduationManager>();
+  }
+
   if (ash::features::IsWelcomeExperienceEnabled()) {
     peripherals_app_delegate_ =
         std::make_unique<ash::PeripheralsAppDelegateImpl>();
@@ -488,6 +494,7 @@
   display_settings_handler_.reset();
   media_client_.reset();
   login_screen_client_.reset();
+  graduation_manager_.reset();
 
   ash::privacy_hub_util::SetAppAccessNotifier(nullptr);
   app_access_notifier_.reset();
diff --git a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.h b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.h
index e8cf37a..095ef65 100644
--- a/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.h
+++ b/chrome/browser/ui/ash/main_extra_parts/chrome_browser_main_extra_parts_ash.h
@@ -27,6 +27,11 @@
 namespace boca {
 class BocaAppClientImpl;
 }
+
+namespace graduation {
+class GraduationManager;
+}
+
 }  // namespace ash
 
 namespace chromeos {
@@ -183,6 +188,7 @@
   std::unique_ptr<PickerClientImpl> picker_client_;
   std::unique_ptr<ash::OobeDialogUtil> oobe_dialog_util_;
   std::unique_ptr<chromeos::ReadWriteCardsManager> read_write_cards_manager_;
+  std::unique_ptr<ash::graduation::GraduationManager> graduation_manager_;
 
   // Initialized in PostBrowserStart in all configs:
   std::unique_ptr<MobileDataNotifications> mobile_data_notifications_;
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index 86064c1a..67813304 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -135,7 +135,7 @@
   E_CPONLY(kColorExtensionMenuIconDisabled) \
   E_CPONLY(kColorExtensionMenuPinButtonIcon) \
   E_CPONLY(kColorExtensionMenuPinButtonIconDisabled) \
-  E_CPONLY(kColorExtensionsMenuHighlightedBackground) \
+  E_CPONLY(kColorExtensionsMenuContainerBackground) \
   E_CPONLY(kColorExtensionsToolbarControlsBackground) \
   E_CPONLY(kColorExtensionsMenuText) \
   E_CPONLY(kColorExtensionsMenuSecondaryText) \
diff --git a/chrome/browser/ui/color/chrome_color_mixer.cc b/chrome/browser/ui/color/chrome_color_mixer.cc
index 15a9c53d..589c77c5 100644
--- a/chrome/browser/ui/color/chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/chrome_color_mixer.cc
@@ -218,8 +218,7 @@
       ui::kColorMenuBackground, color_utils::kMinimumVisibleContrastRatio);
   mixer[kColorExtensionMenuPinButtonIconDisabled] = ui::SetAlpha(
       kColorExtensionMenuPinButtonIcon, gfx::kDisabledControlAlpha);
-  mixer[kColorExtensionsMenuHighlightedBackground] = {
-      ui::kColorSysNeutralContainer};
+  mixer[kColorExtensionsMenuContainerBackground] = {ui::kColorSysSurface3};
   mixer[kColorExtensionsToolbarControlsBackground] = {
       kColorToolbarBackgroundSubtleEmphasis};
   mixer[kColorFeaturePromoBubbleBackground] = {gfx::kGoogleBlue700};
diff --git a/chrome/browser/ui/lens/lens_overlay_blur_layer_delegate.cc b/chrome/browser/ui/lens/lens_overlay_blur_layer_delegate.cc
index 6b3ea90..91ed8196 100644
--- a/chrome/browser/ui/lens/lens_overlay_blur_layer_delegate.cc
+++ b/chrome/browser/ui/lens/lens_overlay_blur_layer_delegate.cc
@@ -30,11 +30,13 @@
 
 LensOverlayBlurLayerDelegate::LensOverlayBlurLayerDelegate(
     ui::Layer* layer,
-    content::RenderWidgetHostView* background_view)
-    : layer_(layer), background_view_(background_view) {
+    content::RenderWidgetHost* background_view_host)
+    : layer_(layer), background_view_host_(background_view_host) {
   CHECK(layer);
   layer->set_delegate(this);
 
+  render_widget_host_observer_.Observe(background_view_host);
+
   // Start taking screenshots to render on the layer.
   screenshot_timer_.Start(
       FROM_HERE,
@@ -81,16 +83,26 @@
 void LensOverlayBlurLayerDelegate::OnDeviceScaleFactorChanged(
     float old_device_scale_factor,
     float new_device_scale_factor) {
-  // Do nothing. We will automatically repaint with a new image.
+  // Do nothing. OnPaintLayer will automatically repaint with a new image.
+}
+
+void LensOverlayBlurLayerDelegate::RenderWidgetHostDestroyed(
+    content::RenderWidgetHost* widget_host) {
+  CHECK(widget_host == background_view_host_);
+  background_view_host_ = nullptr;
+  // If the host view was destroyed, stop updating the background blur.
+  screenshot_timer_.Stop();
 }
 
 void LensOverlayBlurLayerDelegate::FetchBackgroundImage() {
-  if (!background_view_) {
+  if (!background_view_host_ || !background_view_host_->GetView()) {
     return;
   }
-  auto size = background_view_->GetViewBounds().size();
+
+  auto* view = background_view_host_->GetView();
+  auto size = view->GetViewBounds().size();
   auto quality = lens::features::GetLensOverlayCustomBlurQuality();
-  background_view_->CopyFromSurface(
+  view->CopyFromSurface(
       /*src_rect=*/gfx::Rect(),
       /*output_size=*/
       gfx::Size(size.width() * quality, size.height() * quality),
diff --git a/chrome/browser/ui/lens/lens_overlay_blur_layer_delegate.h b/chrome/browser/ui/lens/lens_overlay_blur_layer_delegate.h
index 0278ebab..242f5de 100644
--- a/chrome/browser/ui/lens/lens_overlay_blur_layer_delegate.h
+++ b/chrome/browser/ui/lens/lens_overlay_blur_layer_delegate.h
@@ -6,7 +6,10 @@
 #define CHROME_BROWSER_UI_LENS_LENS_OVERLAY_BLUR_LAYER_DELEGATE_H_
 
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
 #include "base/timer/timer.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_observer.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/compositor/layer.h"
@@ -18,7 +21,8 @@
 // LayerDelegate for controlling the background blur behind the overlay. This
 // class is only a LayerDelegate to control the layer painting and does not own
 // the layer.
-class LensOverlayBlurLayerDelegate : public ui::LayerDelegate {
+class LensOverlayBlurLayerDelegate : public ui::LayerDelegate,
+                                     public content::RenderWidgetHostObserver {
  public:
   // Starts painting to the given layer with a blurring background image using
   // the given render view as the background.
@@ -26,7 +30,7 @@
   // doing work for a layer the user can't even see yet. Instead, we should
   // start blurring once the user can actually see the blur.
   LensOverlayBlurLayerDelegate(ui::Layer* layer,
-                               content::RenderWidgetHostView* background_view);
+                               content::RenderWidgetHost* background_view_host);
   ~LensOverlayBlurLayerDelegate() override;
 
  private:
@@ -35,6 +39,10 @@
   void OnDeviceScaleFactorChanged(float old_device_scale_factor,
                                   float new_device_scale_factor) override;
 
+  // content::RenderWidgetHostObserver:
+  void RenderWidgetHostDestroyed(
+      content::RenderWidgetHost* widget_host) override;
+
   // Fetches a new background screenshot to use for blurring.
   void FetchBackgroundImage();
 
@@ -52,10 +60,15 @@
   // LensOverlayController so guaranteed to outlive this class.
   raw_ptr<ui::Layer> layer_;
 
-  // Pointer to the RenderWidgetHostView to get the contents which we are
+  // Pointer to the RenderWidgetHost to get the contents which we are
   // blurring. Owned by the live page web contents, so is possible to become
   // null.
-  raw_ptr<content::RenderWidgetHostView> background_view_;
+  raw_ptr<content::RenderWidgetHost> background_view_host_;
+
+  // Observes the RenderWidgetHost to ensure our pointer stays valid.
+  base::ScopedObservation<content::RenderWidgetHost,
+                          content::RenderWidgetHostObserver>
+      render_widget_host_observer_{this};
 
   // Must be the last member.
   base::WeakPtrFactory<LensOverlayBlurLayerDelegate> weak_factory_{this};
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc
index 1770a1e..150c932 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -970,6 +970,7 @@
   std::unique_ptr<download::DownloadUrlParameters> params =
       content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
           overlay_web_view_->GetWebContents(), data_url, traffic_annotation);
+  params->set_prompt(true);
   params->set_suggested_name(
       GetFilenameForURL(tab_->GetContents()->GetLastCommittedURL()));
   download_manager->DownloadUrl(std::move(params));
@@ -1932,16 +1933,16 @@
     ui::Layer* background_layer = overlay_view_->layer();
     background_layer->SetFillsBoundsOpaquely(true);
 
-    content::RenderWidgetHostView* live_page_view = tab_->GetContents()
-                                                        ->GetPrimaryMainFrame()
-                                                        ->GetRenderViewHost()
-                                                        ->GetWidget()
-                                                        ->GetView();
+    content::RenderWidgetHost* live_page_widget_host =
+        tab_->GetContents()
+            ->GetPrimaryMainFrame()
+            ->GetRenderViewHost()
+            ->GetWidget();
 
     // Create the blur delegate which will start blurring the background;
     lens_overlay_blur_layer_delegate_ =
-        std::make_unique<lens::LensOverlayBlurLayerDelegate>(background_layer,
-                                                             live_page_view);
+        std::make_unique<lens::LensOverlayBlurLayerDelegate>(
+            background_layer, live_page_widget_host);
     return;
   }
 
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_desktop.cc b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_desktop.cc
index e5ee340..8697c38 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_desktop.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_desktop.cc
@@ -91,17 +91,17 @@
   Browser* const browser = desktop_context->browser;
 
   // Open the tabs in the saved group.
-  std::map<content::WebContents*, base::Uuid> opened_web_contents_to_uuid =
-      OpenTabsAndMapWebcontentsToTabUUIDs(browser, group.value());
+  std::map<tabs::TabModel*, base::Uuid> tab_guid_mapping =
+      OpenTabsAndMapToUuids(browser, group.value());
 
-  if (opened_web_contents_to_uuid.empty()) {
+  if (tab_guid_mapping.empty()) {
     // If not tabs were opened, do nothing.
     return;
   }
 
   // Add the tabs to a new group in the tabstrip and link it to `group`.
-  AddOpenedTabsToGroup(browser->tab_strip_model(),
-                       std::move(opened_web_contents_to_uuid), group.value());
+  AddOpenedTabsToGroup(browser->tab_strip_model(), std::move(tab_guid_mapping),
+                       group.value());
 }
 
 void TabGroupSyncDelegateDesktop::CreateLocalTabGroup(
@@ -137,18 +137,28 @@
     CHECK(tab_group);
 
     const gfx::Range tab_range = tab_group->ListTabs();
-    std::map<content::WebContents*, base::Uuid> web_contents_to_uuid;
+    std::map<tabs::TabModel*, base::Uuid> tab_guid_mapping;
 
     for (auto i = tab_range.start(); i < tab_range.end(); ++i) {
-      content::WebContents* web_contents = tab_strip_model->GetWebContentsAt(i);
-      CHECK(web_contents);
-
-      web_contents_to_uuid.emplace(
-          web_contents,
-          group.saved_tabs()[i - tab_range.start()].saved_tab_guid());
+      tabs::TabModel* tab = tab_strip_model->GetTabAtIndex(i);
+      CHECK(tab);
+      tab_guid_mapping.emplace(
+          tab, group.saved_tabs()[i - tab_range.start()].saved_tab_guid());
     }
 
-    listener_->ConnectToLocalTabGroup(group, std::move(web_contents_to_uuid));
+    std::map<content::WebContents*, base::Uuid> contents_guid_mapping;
+    std::transform(
+        tab_guid_mapping.begin(), tab_guid_mapping.end(),
+        std::inserter(contents_guid_mapping, contents_guid_mapping.end()),
+        [](const std::pair<tabs::TabModel*, base::Uuid>& pair) {
+          // Transform the TabModel* to WebContents*
+          content::WebContents* web_contents = pair.first->contents();
+
+          // Return a pair with the transformed key and the same UUID value
+          return std::make_pair(web_contents, pair.second);
+        });
+
+    listener_->ConnectToLocalTabGroup(group, std::move(contents_guid_mapping));
   } else {
     listener_->UpdateLocalGroupFromSync(group_id);
   }
@@ -185,11 +195,11 @@
   return std::make_unique<ScopedLocalObservationPauserImpl>(listener_.get());
 }
 
-std::map<content::WebContents*, base::Uuid>
-TabGroupSyncDelegateDesktop::OpenTabsAndMapWebcontentsToTabUUIDs(
+std::map<tabs::TabModel*, base::Uuid>
+TabGroupSyncDelegateDesktop::OpenTabsAndMapToUuids(
     Browser* const browser,
     const SavedTabGroup& saved_group) {
-  std::map<content::WebContents*, base::Uuid> web_contents;
+  std::map<tabs::TabModel*, base::Uuid> tab_guid_mapping;
   for (const SavedTabGroupTab& saved_tab : saved_group.saved_tabs()) {
     if (!saved_tab.url().is_valid()) {
       continue;
@@ -198,28 +208,28 @@
     auto* navigation_handle = SavedTabGroupUtils::OpenTabInBrowser(
         saved_tab.url(), browser, browser->profile(),
         WindowOpenDisposition::NEW_BACKGROUND_TAB);
-    content::WebContents* created_contents =
-        navigation_handle ? navigation_handle->GetWebContents() : nullptr;
+    tabs::TabModel* tab =
+        navigation_handle ? browser->tab_strip_model()->GetTabForWebContents(
+                                navigation_handle->GetWebContents())
+                          : nullptr;
 
-    if (!created_contents) {
+    if (!tab) {
       continue;
     }
 
-    web_contents.emplace(created_contents, saved_tab.saved_tab_guid());
+    tab_guid_mapping.emplace(tab, saved_tab.saved_tab_guid());
   }
 
-  return web_contents;
+  return tab_guid_mapping;
 }
 
 TabGroupId TabGroupSyncDelegateDesktop::AddOpenedTabsToGroup(
     TabStripModel* tab_strip_model,
-    const std::map<content::WebContents*, base::Uuid>&
-        opened_web_contents_to_uuid,
+    const std::map<tabs::TabModel*, base::Uuid>& tab_guid_mapping,
     const SavedTabGroup& saved_group) {
   std::vector<int> tab_indices;
   for (int i = 0; i < tab_strip_model->count(); ++i) {
-    if (base::Contains(opened_web_contents_to_uuid,
-                       tab_strip_model->GetWebContentsAt(i)) &&
+    if (base::Contains(tab_guid_mapping, tab_strip_model->GetTabAtIndex(i)) &&
         !tab_strip_model->GetTabGroupForTab(i).has_value()) {
       tab_indices.push_back(i);
     }
@@ -246,7 +256,20 @@
   const std::optional<SavedTabGroup> saved_group2 =
       service_->GetGroup(saved_group.saved_guid());
 
-  listener_->ConnectToLocalTabGroup(*saved_group2, opened_web_contents_to_uuid);
+  // TODO (later in chain) Remove.
+  std::map<content::WebContents*, base::Uuid> contents_guid_mapping;
+  std::transform(
+      tab_guid_mapping.begin(), tab_guid_mapping.end(),
+      std::inserter(contents_guid_mapping, contents_guid_mapping.end()),
+      [](const std::pair<tabs::TabModel*, base::Uuid>& pair) {
+        // Transform the TabModel* to WebContents*
+        content::WebContents* web_contents = pair.first->contents();
+
+        // Return a pair with the transformed key and the same UUID value
+        return std::make_pair(web_contents, pair.second);
+      });
+
+  listener_->ConnectToLocalTabGroup(*saved_group2, contents_guid_mapping);
   return tab_group_id;
 }
 }  // namespace tab_groups
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_desktop.h b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_desktop.h
index 3c2e7add..4ad3cb3 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_desktop.h
+++ b/chrome/browser/ui/tabs/saved_tab_groups/tab_group_sync_delegate_desktop.h
@@ -20,9 +20,9 @@
 class Profile;
 class TabStripModel;
 
-namespace content {
-class WebContents;
-}  // namespace content
+namespace tabs {
+class TabModel;
+}
 
 namespace tab_groups {
 class TabGroupSyncService;
@@ -50,15 +50,15 @@
 
  private:
   // Opens the tabs in `saved_group` in `browser`. These tabs are not grouped.
-  std::map<content::WebContents*, base::Uuid>
-  OpenTabsAndMapWebcontentsToTabUUIDs(Browser* const browser,
-                                      const SavedTabGroup& saved_group);
-  // Adds the opened tabs from OpenTabsAndMapWebcontentsToTabUUIDs into a tab
+  std::map<tabs::TabModel*, base::Uuid> OpenTabsAndMapToUuids(
+      Browser* const browser,
+      const SavedTabGroup& saved_group);
+
+  // Adds the opened tabs from OpenTabsAndMapToUuids into a tab
   // group and links it to `saved_group`.
   TabGroupId AddOpenedTabsToGroup(
       TabStripModel* tab_strip_model,
-      const std::map<content::WebContents*, base::Uuid>&
-          opened_web_contents_to_uuid,
+      const std::map<tabs::TabModel*, base::Uuid>& opened_web_contents_to_uuid,
       const SavedTabGroup& saved_group);
 
   // The service used to query and manage SavedTabGroups.
diff --git a/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.cc b/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.cc
index 174e10e..9518345 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.cc
@@ -72,6 +72,15 @@
   UpdateReadiness(Readiness::kNotReady);
 }
 
+void ThumbnailReadinessTracker::WasDiscarded() {
+  // A new ThumbnailTabHelper and its associated tracker is created every time
+  // a new tab WebContents is created. A new tracker starts at the kNotReady
+  // state. Set this explicitly during discard to reset readiness for the new
+  // discard implementation that retains the tab's WebContents (and consequently
+  // does not recreate the tab helper).
+  UpdateReadiness(Readiness::kNotReady);
+}
+
 void ThumbnailReadinessTracker::UpdateReadiness(Readiness readiness) {
   if (readiness == last_readiness_)
     return;
diff --git a/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.h b/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.h
index d2d8897..cf172694 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.h
+++ b/chrome/browser/ui/thumbnails/thumbnail_readiness_tracker.h
@@ -32,6 +32,7 @@
       content::NavigationHandle* navigation_handle) override;
   void DocumentOnLoadCompletedInPrimaryMainFrame() override;
   void WebContentsDestroyed() override;
+  void WasDiscarded() override;
 
  private:
   void UpdateReadiness(Readiness readiness);
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
index fb9868c..7fd4224 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper.cc
@@ -190,7 +190,8 @@
   // ThumbnailImage::Delegate:
   void ThumbnailImageBeingObservedChanged(bool is_being_observed) override {
     capture_driver_.UpdateThumbnailVisibility(is_being_observed);
-    if (is_being_observed) {
+    // Do not attempt to reload discarded tabs for thumbnail observation events.
+    if (is_being_observed && !web_contents()->WasDiscarded()) {
       web_contents()->GetController().LoadIfNecessary();
     }
   }
@@ -200,11 +201,17 @@
   }
 
   void PageReadinessChanged(CaptureReadiness readiness) {
-    if (page_readiness_ == readiness)
+    if (page_readiness_ == readiness) {
       return;
+    }
+
     // If we transition back to a kNotReady state, clear any existing thumbnail,
     // as it will contain an old snapshot, possibly from a different domain.
-    if (readiness == CaptureReadiness::kNotReady) {
+    // Readiness will be reset to kNotReady when a tab is discarded. In this
+    // specific case we do not clear thumbnail data to ensure the existing
+    // preview remains available while discarded tabs are hovered.
+    if (readiness == CaptureReadiness::kNotReady &&
+        !web_contents()->WasDiscarded()) {
       thumbnail_tab_helper_->ClearData();
     }
     page_readiness_ = readiness;
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index e1e3d15..38f3b78 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -209,11 +209,7 @@
 #if !BUILDFLAG(IS_ANDROID)
 BASE_FEATURE(kPressAndHoldEscToExitBrowserFullscreen,
              "PressAndHoldEscToExitBrowserFullscreen",
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-             base::FEATURE_DISABLED_BY_DEFAULT
-#else
              base::FEATURE_ENABLED_BY_DEFAULT
-#endif
 );
 #endif
 
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
index c54a8bc..c20685f 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
@@ -219,7 +219,7 @@
       // TODO(crbug.com/40879945): After adding margins, compute radius from a
       // variable or create a const variable.
       .SetBackground(views::CreateThemedRoundedRectBackground(
-          kColorExtensionsMenuHighlightedBackground, 4))
+          kColorExtensionsMenuContainerBackground, 4))
       .SetInsideBorderInsets(
           gfx::Insets::VH(section_vertical_margin, section_horizontal_margin))
       .AddChildren(
@@ -274,7 +274,7 @@
                   views::Builder<views::MdTextButton>()
                       .SetCallback(base::BindRepeating(reload_callback_))
                       .SetBgColorIdOverride(
-                          kColorExtensionsMenuHighlightedBackground)
+                          kColorExtensionsMenuContainerBackground)
                       .SetProperty(
                           views::kMarginsKey,
                           gfx::Insets::TLBR(control_vertical_margin, 0, 0, 0))
@@ -428,7 +428,7 @@
                     .SetCallback(base::BindRepeating(dismiss_callback_, id))
                     .SetStyle(ui::ButtonStyle::kText)
                     .SetBgColorIdOverride(
-                        kColorExtensionsMenuHighlightedBackground)
+                        kColorExtensionsMenuContainerBackground)
                     .SetText(l10n_util::GetStringUTF16(
                         IDS_EXTENSIONS_MENU_REQUESTS_ACCESS_SECTION_DISMISS_BUTTON_TEXT))
                     .SetTooltipText(l10n_util::GetStringUTF16(
@@ -437,7 +437,7 @@
                     .SetCallback(base::BindRepeating(allow_callback_, id))
                     .SetStyle(ui::ButtonStyle::kText)
                     .SetBgColorIdOverride(
-                        kColorExtensionsMenuHighlightedBackground)
+                        kColorExtensionsMenuContainerBackground)
                     .SetText(l10n_util::GetStringUTF16(
                         IDS_EXTENSIONS_MENU_REQUESTS_ACCESS_SECTION_ALLOW_BUTTON_TEXT))
                     .SetTooltipText(l10n_util::GetStringUTF16(
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 6db0526..a1dcdba 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -2140,9 +2140,19 @@
 
 void BrowserView::FullscreenStateChanged() {
 #if BUILDFLAG(IS_CHROMEOS)
-  if (platform_util::IsBrowserLockedFullscreen(browser_.get())) {
-    // Never use immersive in locked fullscreen as it allows the user to exit
-    // the locked mode.
+  // Avoid using immersive mode in locked fullscreen as it allows the user to
+  // exit the locked mode. Keep immersive mode enabled if the webapp is locked
+  // for OnTask (only relevant for non-web browser scenarios).
+  // TODO(b/365146870): Remove once we consolidate locked fullscreen with
+  // OnTask.
+  bool avoid_using_immersive_mode =
+      platform_util::IsBrowserLockedFullscreen(browser_.get());
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (browser_->IsLockedForOnTask()) {
+    avoid_using_immersive_mode = false;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+  if (avoid_using_immersive_mode) {
     immersive_mode_controller_->SetEnabled(false);
   } else {
     // Enable immersive before the browser refreshes its list of enabled
diff --git a/chrome/browser/ui/views/frame/browser_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/browser_view_interactive_uitest.cc
index ace2b804..9e964b0 100644
--- a/chrome/browser/ui/views/frame/browser_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/browser_view_interactive_uitest.cc
@@ -530,14 +530,22 @@
 }
 
 IN_PROC_BROWSER_TEST_F(BrowserViewLockedFullscreenTestChromeOS,
-                       EnableImmersiveModeWhenNotLocked) {
+                       EnableImmersiveModeWhenNotTrustedPinned) {
   PinWindow(browser()->window()->GetNativeWindow(), /*trusted=*/false);
   EXPECT_TRUE(browser_view()->immersive_mode_controller()->IsEnabled());
 }
 
 IN_PROC_BROWSER_TEST_F(BrowserViewLockedFullscreenTestChromeOS,
-                       DisableImmersiveModeWhenLocked) {
+                       DisableImmersiveModeWhenNotLockedForOnTask) {
+  browser()->SetLockedForOnTask(false);
   PinWindow(browser()->window()->GetNativeWindow(), /*trusted=*/true);
   EXPECT_FALSE(browser_view()->immersive_mode_controller()->IsEnabled());
 }
+
+IN_PROC_BROWSER_TEST_F(BrowserViewLockedFullscreenTestChromeOS,
+                       EnableImmersiveModeWhenLockedForOnTask) {
+  browser()->SetLockedForOnTask(true);
+  PinWindow(browser()->window()->GetNativeWindow(), /*trusted=*/true);
+  EXPECT_TRUE(browser_view()->immersive_mode_controller()->IsEnabled());
+}
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc
index 9fb0783..360e3b2a 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc
@@ -7,6 +7,7 @@
 #include "build/buildflag.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/platform_util.h"
+#include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/top_container_view.h"
@@ -146,10 +147,22 @@
     return;
   }
 
-  // Don't use immersive mode as long as we are in the locked fullscreen mode
-  // since immersive shows browser controls which allow exiting the mode.
-  if (platform_util::IsBrowserLockedFullscreen(browser_view_->browser()))
+  // Avoid using immersive mode in locked fullscreen as it allows the user to
+  // exit the locked mode. Keep immersive mode enabled if the webapp is locked
+  // for OnTask (only relevant for non-web browser scenarios).
+  // TODO(b/365146870): Remove once we consolidate locked fullscreen with
+  // OnTask.
+  Browser* const browser = browser_view_->browser();
+  bool avoid_using_immersive_mode =
+      platform_util::IsBrowserLockedFullscreen(browser);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (browser->IsLockedForOnTask()) {
+    avoid_using_immersive_mode = false;
+  }
+#endif
+  if (avoid_using_immersive_mode) {
     return;
+  }
 
   // TODO(sammiequon): Investigate if we can move immersive mode logic to the
   // browser non client frame view.
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos_unittest.cc b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos_unittest.cc
index 0b0b203..b86ae18 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos_unittest.cc
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos_unittest.cc
@@ -210,6 +210,26 @@
   EXPECT_FALSE(controller()->IsEnabled());
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Verifies that transitioning from fullscreen to trusted pinned keeps immersive
+// controls when the webapp is locked for OnTask. Only relevant for non-web
+// browser scenarios.
+TEST_F(ImmersiveModeControllerChromeosTest,
+       FullscreenToLockedTransitionWhenLockedForOnTask) {
+  browser()->SetLockedForOnTask(true);
+  AddTab(browser(), GURL("about:blank"));
+  // Start in fullscreen and verify ImmersiveController is enabled.
+  ChromeOSBrowserUITest::EnterImmersiveFullscreenMode(browser());
+  EXPECT_TRUE(controller()->IsEnabled());
+
+  // Transition to locked fullscreen and verify ImmersiveController remains
+  // enabled.
+  ChromeOSBrowserUITest::PinWindow(
+      browser_view()->GetWidget()->GetNativeWindow(), /*trusted=*/true);
+  EXPECT_TRUE(controller()->IsEnabled());
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 // Test that the browser commands which are usually disabled in fullscreen are
 // are enabled in immersive fullscreen.
 TEST_F(ImmersiveModeControllerChromeosTest, EnabledCommands) {
diff --git a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_coordinator.cc
index cc686d0..0b13119 100644
--- a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_coordinator.cc
@@ -64,12 +64,14 @@
     Browser* browser,
     content::WebContents* web_contents,
     const Extension* extension,
-    SidePanelRegistry* registry)
+    SidePanelRegistry* registry,
+    bool for_tab)
     : profile_(profile),
       browser_(browser),
       web_contents_(web_contents),
       extension_(extension),
-      registry_(registry) {
+      registry_(registry),
+      for_tab_(for_tab) {
   DCHECK(base::FeatureList::IsEnabled(
       extensions_features::kExtensionSidePanelIntegration));
 
@@ -328,7 +330,7 @@
   SidePanelEntry* entry = GetEntry();
   DCHECK(entry);
 
-  if (coordinator->IsSidePanelEntryShowing(entry)) {
+  if (coordinator->IsSidePanelEntryShowing(entry->key(), for_tab_)) {
     coordinator->Close();
   } else {
     entry->ClearCachedView();
diff --git a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_coordinator.h
index 0ee2481a..aa05e95 100644
--- a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_coordinator.h
@@ -46,7 +46,8 @@
                                          Browser* browser,
                                          content::WebContents* web_contents,
                                          const Extension* extension,
-                                         SidePanelRegistry* registry);
+                                         SidePanelRegistry* registry,
+                                         bool for_tab);
   ExtensionSidePanelCoordinator(const ExtensionSidePanelCoordinator&) = delete;
   ExtensionSidePanelCoordinator& operator=(
       const ExtensionSidePanelCoordinator&) = delete;
@@ -159,6 +160,9 @@
   // be shown again on a different tab where it's enabled.
   std::unique_ptr<views::View> global_entry_view_;
 
+  // Whether this coordinator is tab-scoped or window-scoped.
+  const bool for_tab_;
+
   base::ScopedObservation<ExtensionViewViews, ExtensionViewViews::Observer>
       scoped_view_observation_{this};
   base::ScopedObservation<SidePanelService, SidePanelService::Observer>
diff --git a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.cc b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.cc
index e07fdc7..d6e1fe1e 100644
--- a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.cc
+++ b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.cc
@@ -40,11 +40,13 @@
     Profile* profile,
     Browser* browser,
     content::WebContents* web_contents,
-    SidePanelRegistry* registry)
+    SidePanelRegistry* registry,
+    bool for_tab)
     : profile_(profile),
       browser_(browser),
       web_contents_(web_contents),
-      registry_(registry) {
+      registry_(registry),
+      for_tab_(for_tab) {
   side_panel_registry_observation_.Observe(registry_);
   profile_observation_.Observe(profile);
 
@@ -62,7 +64,8 @@
   // std::make_unique<ExtensionSidePanelManager> to access a private
   // constructor.
   auto manager = absl::WrapUnique(new ExtensionSidePanelManager(
-      browser->profile(), browser, /*web_contents=*/nullptr, window_registry));
+      browser->profile(), browser, /*web_contents=*/nullptr, window_registry,
+      /*for_tab=*/false));
   browser->SetUserData(kExtensionSidePanelManagerKey, std::move(manager));
 }
 
@@ -82,8 +85,9 @@
   // Use absl::WrapUnique(new ExtensionSidePanelManager(...)) instead of
   // std::make_unique<ExtensionSidePanelManager> to access a private
   // constructor.
-  auto manager = absl::WrapUnique(new ExtensionSidePanelManager(
-      profile, /*browser=*/nullptr, web_contents, tab_registry));
+  auto manager = absl::WrapUnique(
+      new ExtensionSidePanelManager(profile, /*browser=*/nullptr, web_contents,
+                                    tab_registry, /*for_tab=*/true));
   web_contents->SetUserData(kExtensionSidePanelManagerKey, std::move(manager));
 }
 
@@ -227,7 +231,7 @@
     coordinators_.emplace(
         extension->id(),
         std::make_unique<ExtensionSidePanelCoordinator>(
-            profile_, browser_, web_contents_, extension, registry_));
+            profile_, browser_, web_contents_, extension, registry_, for_tab_));
   }
 }
 
diff --git a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.h b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.h
index 29fefb5..fa2c29eb 100644
--- a/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.h
+++ b/chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.h
@@ -87,7 +87,8 @@
   ExtensionSidePanelManager(Profile* profile,
                             Browser* browser,
                             content::WebContents* web_contents,
-                            SidePanelRegistry* registry);
+                            SidePanelRegistry* registry,
+                            bool for_tab);
 
   // Creates an ExtensionSidePanelCoordinator for `extension` and adds it to
   // `coordinators_` if the extension is capable of hosting side panel content.
@@ -126,6 +127,9 @@
   base::flat_map<ExtensionId, std::unique_ptr<ExtensionSidePanelCoordinator>>
       coordinators_;
 
+  // Whether this class is tab-scoped or window-scoped.
+  const bool for_tab_;
+
   base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
       extension_registry_observation_{this};
   base::ScopedObservation<SidePanelRegistry, SidePanelRegistryObserver>
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
index 7fe849d..85b4ba59 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
@@ -472,15 +472,12 @@
 }
 
 bool SidePanelCoordinator::IsSidePanelShowing() const {
-  if (auto* side_panel = browser_view_->unified_side_panel()) {
-    return side_panel->GetVisible();
-  }
-  return false;
+  return current_key_.has_value();
 }
 
 bool SidePanelCoordinator::IsSidePanelEntryShowing(
     const SidePanelEntry::Key& entry_key) const {
-  return IsSidePanelShowing() && current_key_ && current_key_->key == entry_key;
+  return current_key_ && current_key_->key == entry_key;
 }
 
 content::WebContents* SidePanelCoordinator::GetWebContentsForTest(
@@ -502,13 +499,15 @@
       ->DisableAnimationsForTesting();  // IN-TEST
 }
 
-SidePanelEntry* SidePanelCoordinator::GetLoadingEntryForTesting() const {
-  return GetLoadingEntry();
+bool SidePanelCoordinator::IsSidePanelEntryShowing(
+    const SidePanelEntry::Key& entry_key,
+    bool for_tab) const {
+  return current_key_ && current_key_->key == entry_key &&
+         current_key_->tab_handle.has_value() == for_tab;
 }
 
-bool SidePanelCoordinator::IsSidePanelEntryShowing(
-    const SidePanelEntry* entry) const {
-  return IsSidePanelEntryShowing(entry->key());
+SidePanelEntry* SidePanelCoordinator::GetLoadingEntryForTesting() const {
+  return GetLoadingEntry();
 }
 
 void SidePanelCoordinator::Show(
@@ -687,16 +686,6 @@
   return content_wrapper->loading_entry();
 }
 
-bool SidePanelCoordinator::IsGlobalEntryShowing(
-    const SidePanelEntry::Key& entry_key) const {
-  if (!IsSidePanelShowing() || !current_key_) {
-    return false;
-  }
-
-  return window_registry_->GetEntryForKey(entry_key) ==
-         GetEntryForUniqueKey(*current_key_);
-}
-
 void SidePanelCoordinator::InitializeSidePanel() {
   auto content_wrapper = std::make_unique<SidePanelContentSwappingContainer>(
       no_delays_for_testing_);  // Set to not visible so that the side panel is
@@ -1050,10 +1039,11 @@
 
 void SidePanelCoordinator::OnEntryRegistered(SidePanelRegistry* registry,
                                              SidePanelEntry* entry) {
+  bool is_global_entry_showing = current_key_ && !current_key_->tab_handle &&
+                                 current_key_->key == entry->key();
   // If `entry` is a contextual entry and the global entry with the same key is
   // currently being shown, show the tab-scoped `entry`.
-  if (registry == GetActiveContextualRegistry() &&
-      IsGlobalEntryShowing(entry->key())) {
+  if (registry == GetActiveContextualRegistry() && is_global_entry_showing) {
     Show({browser_view_->browser()->GetActiveTabInterface()->GetTabHandle(),
           entry->key()},
          SidePanelUtil::SidePanelOpenTrigger::kExtensionEntryRegistered,
@@ -1072,8 +1062,7 @@
   // Update the current entry to make sure we don't show an entry that is being
   // removed or close the panel if the entry being deregistered is the only one
   // that has been visible.
-  if (IsSidePanelShowing() &&
-      !browser_view_->unified_side_panel()->IsClosing() && current_key_ &&
+  if (!browser_view_->unified_side_panel()->IsClosing() && current_key_ &&
       (current_key_->key == entry->key())) {
     // If a global entry is deregistered but we are currently showing a
     // tab-scoped key, then do nothing.
@@ -1214,7 +1203,7 @@
 }
 
 void SidePanelCoordinator::UpdateHeaderPinButtonState() {
-  if (!IsSidePanelShowing() || !current_key_) {
+  if (!current_key_) {
     return;
   }
 
@@ -1286,53 +1275,62 @@
 
 void SidePanelCoordinator::OnViewVisibilityChanged(views::View* observed_view,
                                                    views::View* starting_from) {
-  if (!observed_view->GetVisible()) {
-    bool closing_global = false;
-    if (current_key_) {
-      // Reset current_key_ first to prevent previous_entry->OnEntryHidden()
-      // from calling multiple times. This could happen in the edge cases when
-      // callback inside current_entry->OnEntryHidden() is calling Close() to
-      // trigger race condition.
-      closing_global = !current_key_->tab_handle;
-      SidePanelEntry* previous_entry = current_entry_.get();
-      current_key_.reset();
-      current_entry_.reset();
-      if (previous_entry) {
-        previous_entry->OnEntryHidden();
-      }
-    }
+  // This method is called in 3 situations:
+  //   (1) The SidePanel was previously invisible, and Show() is called. This is
+  //   independent of the /*suppress_animations*/ parameter, and is re-entrant.
+  //   (2) The SidePanel was previously visible and has finished becoming
+  //   invisible. This is asynchronous if animated, and re-entrant if
+  //   non-animated.
+  //   (3) A parent view or widget changes its visibility state (e.g. window
+  //   becomes visible).
+  //   We currently only take action on (2). We use `current_key_` to
+  //   distinguish (3) from (2). We use visibility to distinguish (1) from (2).
+  if (observed_view->GetVisible() || !current_key_) {
+    return;
+  }
 
-    // Reset active entry values for all observed registries and clear cache for
-    // everything except remaining active entries (i.e. if another tab has an
-    // active contextual entry).
-    if (auto* contextual_registry = GetActiveContextualRegistry()) {
-      contextual_registry->ResetActiveEntry();
-      if (closing_global) {
-        // Reset last active entry in contextual registry as global entry should
-        // take precedence.
-        contextual_registry->ResetLastActiveEntry();
-      }
-    }
-    window_registry_->ResetActiveEntry();
-    ClearCachedEntryViews();
+  bool closing_global = !current_key_->tab_handle;
 
-    // `OnEntryWillDeregister` (triggered by calling `OnEntryHidden`) may
-    // already have deleted the content container, so check that it still
-    // exists.
-    if (auto* content_wrapper =
-            browser_view_->unified_side_panel()->GetViewByID(
-                kSidePanelContentWrapperViewId)) {
-      if (!content_wrapper->children().empty()) {
-        content_wrapper->RemoveChildViewT(content_wrapper->children().front());
-      }
-    }
-    SidePanelUtil::RecordSidePanelClosed(opened_timestamp_);
+  // Reset current_key_ first to prevent previous_entry->OnEntryHidden()
+  // from calling multiple times. This could happen in the edge cases when
+  // callback inside current_entry->OnEntryHidden() is calling Close() to
+  // trigger race condition.
+  SidePanelEntry* previous_entry = current_entry_.get();
+  current_key_.reset();
+  current_entry_.reset();
+  if (previous_entry) {
+    previous_entry->OnEntryHidden();
+  }
 
-    for (SidePanelViewStateObserver& view_state_observer :
-         view_state_observers_) {
-      view_state_observer.OnSidePanelDidClose();
+  // Reset active entry values for all observed registries and clear cache for
+  // everything except remaining active entries (i.e. if another tab has an
+  // active contextual entry).
+  if (auto* contextual_registry = GetActiveContextualRegistry()) {
+    contextual_registry->ResetActiveEntry();
+    if (closing_global) {
+      // Reset last active entry in contextual registry as global entry should
+      // take precedence.
+      contextual_registry->ResetLastActiveEntry();
     }
   }
+  window_registry_->ResetActiveEntry();
+  ClearCachedEntryViews();
+
+  // `OnEntryWillDeregister` (triggered by calling `OnEntryHidden`) may
+  // already have deleted the content container, so check that it still
+  // exists.
+  if (auto* content_wrapper = browser_view_->unified_side_panel()->GetViewByID(
+          kSidePanelContentWrapperViewId)) {
+    if (!content_wrapper->children().empty()) {
+      content_wrapper->RemoveChildViewT(content_wrapper->children().front());
+    }
+  }
+  SidePanelUtil::RecordSidePanelClosed(opened_timestamp_);
+
+  for (SidePanelViewStateObserver& view_state_observer :
+       view_state_observers_) {
+    view_state_observer.OnSidePanelDidClose();
+  }
 }
 
 void SidePanelCoordinator::OnActionsChanged() {
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
index 610497b..d832d69 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
@@ -93,9 +93,10 @@
   content::WebContents* GetWebContentsForTest(SidePanelEntryId id) override;
   void DisableAnimationsForTesting() override;
 
-  // TODO(crbug.com/40851017): Move this method to `SidePanelUI` after
-  // decoupling `SidePanelEntry` from views.
-  bool IsSidePanelEntryShowing(const SidePanelEntry* entry) const;
+  // Similar to IsSidePanelEntryShowing, but restricts to either the tab-scoped
+  // or window-scoped registry.
+  bool IsSidePanelEntryShowing(const SidePanelEntry::Key& entry_key,
+                               bool for_tab) const;
 
   // Re-runs open new tab URL check and sets button state to enabled/disabled
   // accordingly.
@@ -161,10 +162,6 @@
   // Returns the current loading entry or nullptr if none exists.
   SidePanelEntry* GetLoadingEntry() const;
 
-  // Returns whether the global entry with the same key as `entry_key` is
-  // showing.
-  bool IsGlobalEntryShowing(const SidePanelEntry::Key& entry_key) const;
-
   // Creates header and SidePanelEntry content container within the side panel.
   void InitializeSidePanel();
 
@@ -273,6 +270,9 @@
   // following cases:
   //   * The active tab is switched, and UniqueKey is tab-scoped.
   //   * The entry is removed from tab or window-scoped registry.
+  // The side-panel is showing if and only if current_key_ is set. That means it
+  // must only be set in one place: PopulateSidePanel() and unset in one place:
+  // OnViewVisibilityChanged()
   std::optional<UniqueKey> current_key_;
   // TODO(https://crbug.com/363743081): Remove this member.
   // There are a few cases where the current control flow first modifies the
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc
index 08d3c47..5e009b1 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc
@@ -42,6 +42,7 @@
 #include "components/performance_manager/public/decorators/process_metrics_decorator.h"
 #include "components/performance_manager/public/performance_manager.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
 #include "content/public/test/browser_test.h"
 #include "net/base/url_util.h"
 #include "net/dns/mock_host_resolver.h"
@@ -510,10 +511,24 @@
   EXPECT_TRUE(alert_row->icon()->GetImageModel().IsEmpty());
 }
 
+class TabHoverCardFadeFooterWithDiscardInteractiveUiTest
+    : public TabHoverCardFadeFooterInteractiveUiTest,
+      public ::testing::WithParamInterface<bool> {
+ public:
+  void SetUp() override {
+    TabHoverCardFadeFooterInteractiveUiTest::SetUp();
+    scoped_feature_list_.InitWithFeatureState(features::kWebContentsDiscard,
+                                              GetParam());
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
 // Mocks that a tab is discarded and verifies that the correct string
 // for a discarded tab and discarded tab with memory usage is displayed
 // on the hover card
-IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
+IN_PROC_BROWSER_TEST_P(TabHoverCardFadeFooterWithDiscardInteractiveUiTest,
                        HoverCardFooterShowsDiscardStatus) {
   TabStrip* const tab_strip = GetTabStrip(browser());
   ASSERT_TRUE(
@@ -651,7 +666,7 @@
 
 // The discarded status in the hover card footer should disappear after a
 // discarded tab is reloaded
-IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
+IN_PROC_BROWSER_TEST_P(TabHoverCardFadeFooterWithDiscardInteractiveUiTest,
                        DiscardStatusHidesOnReload) {
   RunTestSequence(
       InstrumentTab(kFirstTabContents, 0),
@@ -802,3 +817,12 @@
       WaitForHide(TabHoverCardBubbleView::kHoverCardBubbleElementId));
 }
 #endif
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    TabHoverCardFadeFooterWithDiscardInteractiveUiTest,
+    ::testing::Values(false, true),
+    [](const ::testing::TestParamInfo<
+        TabHoverCardFadeFooterWithDiscardInteractiveUiTest::ParamType>& info) {
+      return info.param ? "RetainedWebContents" : "UnretainedWebContents";
+    });
diff --git a/chrome/browser/ui/webauthn/sheet_models.cc b/chrome/browser/ui/webauthn/sheet_models.cc
index 3fff701..412ae9e 100644
--- a/chrome/browser/ui/webauthn/sheet_models.cc
+++ b/chrome/browser/ui/webauthn/sheet_models.cc
@@ -1279,7 +1279,7 @@
 }
 
 bool AuthenticatorQRSheetModel::ShowSecurityKeyLabel() const {
-  return dialog_model()->security_key_is_possible;
+  return dialog_model()->show_security_key_on_qr_sheet;
 }
 
 std::u16string AuthenticatorQRSheetModel::GetSecurityKeyLabel() const {
diff --git a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_ui.cc b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_ui.cc
index 52338de..88e0aeee 100644
--- a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_ui.cc
+++ b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_ui.cc
@@ -57,6 +57,10 @@
           .c_str());
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::FrameSrc, frame_src);
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::ScriptSrc,
+      "script-src chrome://resources chrome://webui-test 'self' "
+      "'unsafe-inline';");
 
   // Set up Content Security Policy (CSP) for
   // chrome-untrusted://privacy-sandbox-dialog/ iframe.
diff --git a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_untrusted_ui.cc b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_untrusted_ui.cc
index 5462128..17facf7 100644
--- a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_untrusted_ui.cc
+++ b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_untrusted_ui.cc
@@ -38,6 +38,9 @@
   untrusted_source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::ObjectSrc,
       "object-src https://policies.google.com;");
+  untrusted_source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::ScriptSrc,
+      "script-src chrome-untrusted://resources 'self' 'unsafe-inline';");
 
   untrusted_source->AddResourcePath(
       chrome::kChromeUIUntrustedPrivacySandboxDialogPrivacyPolicyPath,
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut.cc b/chrome/browser/web_applications/os_integration/web_app_shortcut.cc
index b3516638..4a0d0dd 100644
--- a/chrome/browser/web_applications/os_integration/web_app_shortcut.cc
+++ b/chrome/browser/web_applications/os_integration/web_app_shortcut.cc
@@ -102,14 +102,6 @@
 #endif
 }
 
-void DeleteShortcutInfoOnUIThread(std::unique_ptr<ShortcutInfo> shortcut_info,
-                                  ResultCallback callback,
-                                  Result result) {
-  shortcut_info.reset();
-  if (callback)
-    std::move(callback).Run(result);
-}
-
 void CreatePlatformShortcutsAndPostCallback(
     const base::FilePath& shortcut_data_path,
     const ShortcutLocations& creation_locations,
@@ -549,21 +541,6 @@
       std::move(shortcut_info));
 }
 
-void PostShortcutIOTaskAndReplyWithResult(
-    base::OnceCallback<Result(const ShortcutInfo&)> task,
-    std::unique_ptr<ShortcutInfo> shortcut_info,
-    ResultCallback reply) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  // Ownership of |shortcut_info| moves to the Reply, which is guaranteed to
-  // outlive the const reference.
-  const ShortcutInfo& shortcut_info_ref = *shortcut_info;
-  GetShortcutIOTaskRunner()->PostTaskAndReplyWithResult(
-      FROM_HERE, base::BindOnce(std::move(task), std::cref(shortcut_info_ref)),
-      base::BindOnce(&DeleteShortcutInfoOnUIThread, std::move(shortcut_info),
-                     std::move(reply)));
-}
-
 scoped_refptr<base::SequencedTaskRunner> GetShortcutIOTaskRunner() {
   return g_shortcuts_task_runner.Get();
 }
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut.h b/chrome/browser/web_applications/os_integration/web_app_shortcut.h
index a101b13..a485bc4 100644
--- a/chrome/browser/web_applications/os_integration/web_app_shortcut.h
+++ b/chrome/browser/web_applications/os_integration/web_app_shortcut.h
@@ -279,10 +279,6 @@
 // Tasks posted here run with BEST_EFFORT priority and block shutdown.
 void PostShortcutIOTask(base::OnceCallback<void(const ShortcutInfo&)> task,
                         std::unique_ptr<ShortcutInfo> shortcut_info);
-void PostShortcutIOTaskAndReplyWithResult(
-    base::OnceCallback<Result(const ShortcutInfo&)> task,
-    std::unique_ptr<ShortcutInfo> shortcut_info,
-    ResultCallback reply);
 
 // Run an IO task on a worker thread. Ownership of |shortcut_info| transfers
 // to the task which must delete it on the UI thread when the task is complete.
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_controller.cc b/chrome/browser/webauthn/authenticator_request_dialog_controller.cc
index dd690bb..fe159f0 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_controller.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_controller.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/ui/webauthn/ambient/ambient_signin_controller.h"
 #include "chrome/browser/ui/webauthn/user_actions.h"
 #include "chrome/browser/webauthn/authenticator_reference.h"
+#include "chrome/browser/webauthn/authenticator_transport.h"
 #include "chrome/browser/webauthn/change_pin_controller_impl.h"
 #include "chrome/browser/webauthn/gpm_user_verification_policy.h"
 #include "chrome/browser/webauthn/passkey_model_factory.h"
@@ -752,6 +753,11 @@
       });
 
   if (mech_it != model_->mechanisms.end()) {
+    if (transport == AuthenticatorTransport::kHybrid) {
+      // If the site sent a "hybrid" hint, focus the UI exclusively on the
+      // hybrid case and don't suggest security keys.
+      model_->show_security_key_on_qr_sheet = false;
+    }
     mech_it->callback.Run();
     return true;
   }
@@ -2170,13 +2176,14 @@
 
   // If the new UI is enabled, only show USB as an option if the QR code is
   // not available, if tapping it would trigger a prompt to enable BLE, or if
-  // hints will cause us to jump to USB UI.
+  // hints suggest that hybrid and USB should be separate options.
   const bool include_usb_option =
       base::Contains(transport_availability_.available_transports,
                      AuthenticatorTransport::kUsbHumanInterfaceDevice) &&
       (!include_add_phone_option ||
        transport_availability_.ble_status != BleStatus::kOn ||
-       hints_.transport == AuthenticatorTransport::kUsbHumanInterfaceDevice);
+       hints_.transport == AuthenticatorTransport::kUsbHumanInterfaceDevice ||
+       hints_.transport == AuthenticatorTransport::kHybrid);
 
   if (include_add_phone_option) {
     std::u16string label = l10n_util::GetStringUTF16(
@@ -2401,7 +2408,7 @@
       transport_availability_.resident_key_requirement;
   model_->ble_adapter_is_powered =
       transport_availability_.ble_status == BleStatus::kOn;
-  model_->security_key_is_possible =
+  model_->show_security_key_on_qr_sheet =
       base::Contains(transport_availability_.available_transports,
                      device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
   model_->is_off_the_record = transport_availability_.is_off_the_record_context;
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.h b/chrome/browser/webauthn/authenticator_request_dialog_model.h
index 99f90df..bd284902 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.h
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.h
@@ -444,9 +444,9 @@
   // errors.
   bool offer_try_again_in_ui = true;
   bool ble_adapter_is_powered = false;
-  // security_key_is_possible is true if a security key might be used for the
-  // current transaction.
-  bool security_key_is_possible = false;
+  // show_security_key_on_qr_sheet is true if the security key option should be
+  // offered on the QR sheet.
+  bool show_security_key_on_qr_sheet = false;
   bool is_off_the_record = false;
 
   std::optional<int> max_bio_samples;
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
index dc7c246b..6ab2d52 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
@@ -1102,10 +1102,10 @@
 
        // create(): Hybrid hint should show QR.
        {L, mc, {usb, internal, cable}, {rk, hint_hybrid}, {},
-        {add, t(internal)}, qr},
+        {add, t(internal), t(usb)}, qr},
        // ... even if there are paired phones.
        {L, mc, {usb, internal, cable}, {rk, hint_hybrid}, {psync("a")}, {p("a"),
-        add, t(internal)}, qr},
+        add, t(internal), t(usb)}, qr},
        // But not if Hybrid isn't a valid transport.
        {L, mc, {usb, internal}, {rk, hint_hybrid}, {}, {t(internal), t(usb)},
 #if BUILDFLAG(IS_MAC)
@@ -1154,10 +1154,10 @@
        {L, ga, {cable}, {has_winapi, rk, hint_sk}, {}, {add, winapi}, plat_ui},
 
        // get(): Hybrid hint should show QR.
-       {L, ga, {usb, internal, cable}, {rk, hint_hybrid}, {}, {add}, qr},
+       {L, ga, {usb, internal, cable}, {rk, hint_hybrid}, {}, {add, t(usb)}, qr},
        // ... even if there are paired phones.
        {L, ga, {usb, internal, cable}, {rk, hint_hybrid}, {psync("a")},
-        {p("a"), add}, qr},
+        {p("a"), add, t(usb)}, qr},
        // But not if hybrid isn't available.
        {L, ga, {usb, internal}, {rk, hint_hybrid}, {}, {t(usb)}, usb_ui},
        // If older webauthn.dll is present, don't jump to it since it doesn't do
@@ -1171,7 +1171,7 @@
        // If credentials are found on a platform authenticator, they are still
        // shown.
        {L, ga, {usb, internal, cable}, {one_cred, rk, hint_hybrid}, {},
-        {c(cred1), add}, mss},
+        {c(cred1), add, t(usb)}, mss},
 
        // get(): Client device hint should trigger webauthn.dll, if it exists.
        {L, ga, {cable}, {rk, has_winapi, hint_plat}, {}, {add, winapi},
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
index d162648..1f05c844 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
@@ -118,7 +118,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/page_transition_types.h"
 #include "ui/base/window_open_disposition.h"
-#include "ui/gfx/native_widget_types.h"
 #include "url/url_constants.h"
 #include "url/url_util.h"
 
@@ -1301,29 +1300,20 @@
 
 #if BUILDFLAG(IS_MAC)
   {
-    NSWindow* window = nullptr;
     content::WebContents* web_contents =
         content::WebContents::FromRenderFrameHost(GetRenderFrameHost());
-    BrowserWindow* browser_window =
-        BrowserWindow::FindBrowserWindowWithWebContents(web_contents);
-    if (browser_window) {
-      window = browser_window->GetNativeWindow().GetNativeNSWindow();
-    }
-    if (!window) {
-      // Not all contexts in which this code runs have a BrowserWindow.
-      // Notably the dialog containing a WebContents that is used for signing
-      // into a new profile does not. Thus the NSWindow is fetched more
-      // directly.
-      const views::Widget* widget =
-          views::Widget::GetTopLevelWidgetForNativeView(
-              web_contents->GetNativeView());
-      if (widget) {
-        window = widget->GetNativeWindow().GetNativeNSWindow();
+    // Not all contexts in which this code runs have a BrowserWindow.
+    // Notably the dialog containing a WebContents that is used for signing
+    // into a new profile does not. Thus the NSWindow is fetched more directly.
+    const views::Widget* widget = views::Widget::GetTopLevelWidgetForNativeView(
+        web_contents->GetNativeView());
+    if (widget) {
+      const gfx::NativeWindow window = widget->GetNativeWindow();
+      if (window) {
+        discovery_factory->set_nswindow(
+            reinterpret_cast<uintptr_t>(window.GetNativeNSWindow()));
       }
     }
-    if (window) {
-      discovery_factory->set_nswindow(reinterpret_cast<uintptr_t>(window));
-    }
   }
 #endif
 
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 98dca4f..dafa706 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1725991190-24631c0641b2a3c066729711becd141ad28c46f9-b34b7c4498378ea2312efe9ff3a0ae23127c4dca.profdata
+chrome-mac-arm-main-1726005491-94846b5fbb8dfdbf78a27603b96b29f3b01b0ef6-1d38fb86a4ab7a2652d28109e7afbbe64d921e23.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 8ebac81..2b9059852 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1725969564-5c59eb862e12fe8838a80ee911496fcc69306aa5-4af0941c421ce5681598c40547bab2f75fc187c6.profdata
+chrome-mac-main-1725991190-5286f368172fa4432169f41b9b3e563221792c81-b34b7c4498378ea2312efe9ff3a0ae23127c4dca.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 48ef955..d8481a2 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1725980374-7f693865d857752422c5e4c50376a1e265edb670-1ffd9c50147df4b321a97d0332898ffcab4a2aad.profdata
+chrome-win32-main-1726001946-70d4753af0df4f22539a2b86cc77ba912d13906c-d6371ec593dc342c2ee18517e9ef52aaea6908b6.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 2f063c1a..6b422e8 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1725980374-4964f4bfdcab5036a73f7ad9cd6fbfcbf4f49808-1ffd9c50147df4b321a97d0332898ffcab4a2aad.profdata
+chrome-win64-main-1725991190-4329617499220518a9a3a806e07078c4dd382c5b-b34b7c4498378ea2312efe9ff3a0ae23127c4dca.profdata
diff --git a/chrome/installer/mac/third_party/bsdiff/README.chromium b/chrome/installer/mac/third_party/bsdiff/README.chromium
index dc3adbc8..f6059955 100644
--- a/chrome/installer/mac/third_party/bsdiff/README.chromium
+++ b/chrome/installer/mac/third_party/bsdiff/README.chromium
@@ -5,6 +5,7 @@
 License: BSD
 License File: LICENSE
 Shipped: yes
+Security Critical: yes
 
 Description:
 Binary diff/patch utility. This is used by the Mac diff updaters, not Chrome
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 1914d45..ed21987 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -6040,13 +6040,6 @@
     # All unittests in browser, common, renderer and service.
     "../browser/about_flags_unittest.cc",
     "../browser/after_startup_task_utils_unittest.cc",
-    "../browser/ai/ai_manager_keyed_service_unittest.cc",
-    "../browser/ai/ai_rewriter_unittest.cc",
-    "../browser/ai/ai_summarizer_unittest.cc",
-    "../browser/ai/ai_test_utils.cc",
-    "../browser/ai/ai_test_utils.h",
-    "../browser/ai/ai_text_session_unittest.cc",
-    "../browser/ai/ai_writer_unittest.cc",
     "../browser/apps/icon_standardizer_unittest.cc",
     "../browser/apps/user_type_filter_unittest.cc",
     "../browser/autocomplete/chrome_autocomplete_provider_client_unittest.cc",
@@ -6628,6 +6621,8 @@
     "//chrome/browser:primitives",
     "//chrome/browser/accessibility:utils",
     "//chrome/browser/ai",
+    "//chrome/browser/ai:test_support",
+    "//chrome/browser/ai:unit_tests",
     "//chrome/browser/apps:icon_standardizer",
     "//chrome/browser/apps:user_type_filter",
     "//chrome/browser/autofill",
diff --git a/chrome/test/data/webui/chromeos/graduation/graduation_ui_test.ts b/chrome/test/data/webui/chromeos/graduation/graduation_ui_test.ts
index 62fba45..3b57fd21 100644
--- a/chrome/test/data/webui/chromeos/graduation/graduation_ui_test.ts
+++ b/chrome/test/data/webui/chromeos/graduation/graduation_ui_test.ts
@@ -6,6 +6,7 @@
 import 'chrome://graduation/strings.m.js';
 
 import {GraduationUi} from 'chrome://graduation/js/graduation_ui.js';
+import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
 import {PaperSpinnerLiteElement} from 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
@@ -14,6 +15,13 @@
 suite('GraduationUiTest', function() {
   let graduationUi: GraduationUi;
 
+  function getBackButton(): CrButtonElement {
+    const backButton =
+        graduationUi.shadowRoot!.querySelector<CrButtonElement>('#backButton');
+    assertTrue(!!backButton);
+    return backButton;
+  }
+
   function getSpinner(): PaperSpinnerLiteElement {
     const spinner =
         graduationUi.shadowRoot!.querySelector<PaperSpinnerLiteElement>(
@@ -30,6 +38,13 @@
     return webview;
   }
 
+  function getDoneButton(): CrButtonElement {
+    const doneButton =
+        graduationUi.shadowRoot!.querySelector<CrButtonElement>('#doneButton');
+    assertTrue(!!doneButton);
+    return doneButton;
+  }
+
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
     graduationUi = new GraduationUi();
@@ -41,20 +56,22 @@
     graduationUi.remove();
   });
 
-  test('HideSpinnerOnContentLoad', function() {
+  test('HideSpinnerAndShowUiOnContentLoad', function() {
     assertFalse(getSpinner().hidden);
     assertTrue(getWebview().hidden);
-    graduationUi.shadowRoot!.querySelector('webview')!.dispatchEvent(
-        new CustomEvent('contentload'));
+    assertTrue(getBackButton().hidden);
+    assertTrue(getDoneButton().hidden);
+    getWebview().dispatchEvent(new CustomEvent('contentload'));
     assertTrue(getSpinner().hidden);
     assertFalse(getWebview().hidden);
+    assertFalse(getBackButton().hidden);
+    assertTrue(getDoneButton().hidden);
   });
 
   test('HideSpinnerOnLoadAbort', function() {
     assertFalse(getSpinner().hidden);
     assertTrue(getWebview().hidden);
-    graduationUi.shadowRoot!.querySelector('webview')!.dispatchEvent(
-        new CustomEvent('loadabort'));
+    getWebview().dispatchEvent(new CustomEvent('loadabort'));
     assertTrue(getSpinner().hidden);
     assertFalse(getWebview().hidden);
   });
diff --git a/chrome/test/data/webui/privacy_sandbox/privacy_sandbox_dialog_test.ts b/chrome/test/data/webui/privacy_sandbox/privacy_sandbox_dialog_test.ts
index a03e4ad..b093709 100644
--- a/chrome/test/data/webui/privacy_sandbox/privacy_sandbox_dialog_test.ts
+++ b/chrome/test/data/webui/privacy_sandbox/privacy_sandbox_dialog_test.ts
@@ -482,8 +482,9 @@
         browserProxy, PrivacySandboxPromptAction.CONSENT_MORE_INFO_OPENED);
     assertTrue(collapseElement!.opened);
 
-    // After clicking the privacy policy link, the privacy policy page should be
-    // opened.
+    // TODO(crbug.com/358087159): Add metrics testing for privacy policy page
+    // loading. After clicking the privacy policy link, the privacy policy page
+    // should be opened.
     const privacyPolicyDiv =
         learnMore!.querySelector<HTMLElement>('#privacyPolicyDiv');
     const privacyPolicyLink =
@@ -492,9 +493,21 @@
         !!privacyPolicyLink,
         `the link isn\'t found, selector: ${privacyPolicyDiv}`);
     privacyPolicyLink.click();
-
     assertEquals(
-        getActiveStep()!.id, PrivacySandboxCombinedDialogStep.PRIVACYPOLICY);
+        isChildVisible(consentStep, '.iframe'), true,
+        `privacy policy page should be visible when the link is clicked`);
+    assertEquals(
+        isChildVisible(consentStep, '#consentNotice'), false,
+        `if the privacy policy page is visible,
+        the consent notice should not be visible.`);
+
+
+    // After clicking the back button, the content area should display the
+    // consent screen again.
+    testClickButton('#backButton', consentStep);
+    assertEquals(
+        isChildVisible(consentStep, '#confirmButton'), true,
+        `buttons should be shown on the consent notice again`);
   });
 });
 
diff --git a/chromeos/ash/components/boca/babelorca/BUILD.gn b/chromeos/ash/components/boca/babelorca/BUILD.gn
index 099d9148..67e203b 100644
--- a/chromeos/ash/components/boca/babelorca/BUILD.gn
+++ b/chromeos/ash/components/boca/babelorca/BUILD.gn
@@ -15,8 +15,6 @@
     "oauth_token_fetcher.h",
     "request_data_wrapper.cc",
     "request_data_wrapper.h",
-    "response_callback_wrapper.h",
-    "response_callback_wrapper_impl.h",
     "tachyon_authed_client.h",
     "tachyon_authed_client_impl.cc",
     "tachyon_authed_client_impl.h",
@@ -27,6 +25,7 @@
     "tachyon_registrar.cc",
     "tachyon_registrar.h",
     "tachyon_request_data_provider.h",
+    "tachyon_request_error.h",
     "tachyon_utils.cc",
     "tachyon_utils.h",
     "token_data_wrapper.h",
@@ -81,7 +80,6 @@
 
   sources = [
     "oauth_token_fetcher_unittest.cc",
-    "response_callback_wrapper_impl_unittest.cc",
     "tachyon_authed_client_impl_unittest.cc",
     "tachyon_client_impl_unittest.cc",
     "tachyon_registrar_unittest.cc",
diff --git a/chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.cc b/chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.cc
index 780da48..054d42f 100644
--- a/chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.cc
+++ b/chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.cc
@@ -5,14 +5,14 @@
 #include "chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.h"
 
 #include <memory>
-#include <string_view>
+#include <string>
 #include <utility>
 
 #include "base/check.h"
 #include "base/run_loop.h"
 #include "base/types/expected.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
-#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "third_party/protobuf/src/google/protobuf/message_lite.h"
 
 namespace ash::babelorca {
@@ -22,23 +22,17 @@
 FakeTachyonAuthedClient::~FakeTachyonAuthedClient() = default;
 
 void FakeTachyonAuthedClient::StartAuthedRequest(
-    const net::NetworkTrafficAnnotationTag& annotation_tag,
-    std::unique_ptr<google::protobuf::MessageLite> request_proto,
-    std::string_view url,
-    int max_retries,
-    std::unique_ptr<ResponseCallbackWrapper> response_cb) {
-  StartAuthedRequestString(annotation_tag, request_proto->SerializeAsString(),
-                           url, max_retries, std::move(response_cb));
+    std::unique_ptr<RequestDataWrapper> request_data,
+    std::unique_ptr<google::protobuf::MessageLite> request_proto) {
+  StartAuthedRequestString(std::move(request_data),
+                           request_proto->SerializeAsString());
 }
 
 void FakeTachyonAuthedClient::StartAuthedRequestString(
-    const net::NetworkTrafficAnnotationTag& annotation_tag,
-    std::string request_string,
-    std::string_view url,
-    int max_retries,
-    std::unique_ptr<ResponseCallbackWrapper> response_cb) {
+    std::unique_ptr<RequestDataWrapper> request_data,
+    std::string request_string) {
   has_new_request_ = true;
-  response_cb_ = std::move(response_cb);
+  response_cb_ = std::move(request_data->response_cb);
   request_string_ = std::move(request_string);
   if (run_loop_) {
     run_loop_->Quit();
@@ -46,14 +40,12 @@
 }
 
 void FakeTachyonAuthedClient::ExecuteResponseCallback(
-    base::expected<std::string, ResponseCallbackWrapper::TachyonRequestError>
-        response) {
+    base::expected<std::string, TachyonRequestError> response) {
   CHECK(response_cb_);
-  response_cb_->Run(std::move(response));
-  response_cb_.reset();
+  std::move(response_cb_).Run(std::move(response));
 }
 
-std::unique_ptr<ResponseCallbackWrapper>
+RequestDataWrapper::ResponseCallback
 FakeTachyonAuthedClient::TakeResponseCallback() {
   return std::move(response_cb_);
 }
diff --git a/chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.h b/chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.h
index ce775b3a..4ad3db5 100644
--- a/chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.h
+++ b/chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.h
@@ -6,12 +6,13 @@
 #define CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_FAKES_FAKE_TACHYON_AUTHED_CLIENT_H_
 
 #include <memory>
-#include <string_view>
+#include <string>
 
 #include "base/run_loop.h"
 #include "base/types/expected.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_authed_client.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 
 namespace ash::babelorca {
 
@@ -26,30 +27,23 @@
 
   // TachyonAuthedClient:
   void StartAuthedRequest(
-      const net::NetworkTrafficAnnotationTag& annotation_tag,
-      std::unique_ptr<google::protobuf::MessageLite> request_proto,
-      std::string_view url,
-      int max_retries,
-      std::unique_ptr<ResponseCallbackWrapper> response_cb) override;
+      std::unique_ptr<RequestDataWrapper> request_data,
+      std::unique_ptr<google::protobuf::MessageLite> request_proto) override;
   void StartAuthedRequestString(
-      const net::NetworkTrafficAnnotationTag& annotation_tag,
-      std::string request_string,
-      std::string_view url,
-      int max_retries,
-      std::unique_ptr<ResponseCallbackWrapper> response_cb) override;
+      std::unique_ptr<RequestDataWrapper> request_data,
+      std::string request_string) override;
 
   void ExecuteResponseCallback(
-      base::expected<std::string, ResponseCallbackWrapper::TachyonRequestError>
-          response);
+      base::expected<std::string, TachyonRequestError> response);
 
-  std::unique_ptr<ResponseCallbackWrapper> TakeResponseCallback();
+  RequestDataWrapper::ResponseCallback TakeResponseCallback();
 
   std::string GetRequestString();
 
   void WaitForRequest();
 
  private:
-  std::unique_ptr<ResponseCallbackWrapper> response_cb_;
+  RequestDataWrapper::ResponseCallback response_cb_;
   std::string request_string_;
   std::unique_ptr<base::RunLoop> run_loop_;
   bool has_new_request_ = false;
diff --git a/chromeos/ash/components/boca/babelorca/request_data_wrapper.cc b/chromeos/ash/components/boca/babelorca/request_data_wrapper.cc
index dd8f7b2..475c6e9 100644
--- a/chromeos/ash/components/boca/babelorca/request_data_wrapper.cc
+++ b/chromeos/ash/components/boca/babelorca/request_data_wrapper.cc
@@ -4,12 +4,10 @@
 
 #include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
 
-#include <memory>
 #include <string>
 #include <string_view>
 #include <utility>
 
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 
 namespace ash::babelorca {
@@ -18,7 +16,7 @@
     const net::NetworkTrafficAnnotationTag& annotation_tag_param,
     std::string_view url_param,
     int max_retries_param,
-    std::unique_ptr<ResponseCallbackWrapper> response_cb_param)
+    ResponseCallback response_cb_param)
     : annotation_tag(annotation_tag_param),
       url(std::move(url_param)),
       max_retries(max_retries_param),
diff --git a/chromeos/ash/components/boca/babelorca/request_data_wrapper.h b/chromeos/ash/components/boca/babelorca/request_data_wrapper.h
index 7808a3f8..8285249 100644
--- a/chromeos/ash/components/boca/babelorca/request_data_wrapper.h
+++ b/chromeos/ash/components/boca/babelorca/request_data_wrapper.h
@@ -5,29 +5,32 @@
 #ifndef CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_REQUEST_DATA_WRAPPER_H_
 #define CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_REQUEST_DATA_WRAPPER_H_
 
-#include <memory>
 #include <string>
 #include <string_view>
 
+#include "base/functional/callback.h"
+#include "base/types/expected.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 
 namespace ash::babelorca {
 
-class ResponseCallbackWrapper;
-
 struct RequestDataWrapper {
+  using ResponseCallback = base::OnceCallback<void(
+      base::expected<std::string, TachyonRequestError>)>;
+
   RequestDataWrapper(
       const net::NetworkTrafficAnnotationTag& annotation_tag_param,
       std::string_view url_param,
       int max_retries_param,
-      std::unique_ptr<ResponseCallbackWrapper> response_cb_param);
+      ResponseCallback response_cb_param);
 
   ~RequestDataWrapper();
 
   const net::NetworkTrafficAnnotationTag annotation_tag;
   const std::string_view url;
   const int max_retries;
-  std::unique_ptr<ResponseCallbackWrapper> response_cb;
+  ResponseCallback response_cb;
   int oauth_version = 0;
   int oauth_retry_num = 0;
   std::string content_data;
diff --git a/chromeos/ash/components/boca/babelorca/response_callback_wrapper.h b/chromeos/ash/components/boca/babelorca/response_callback_wrapper.h
deleted file mode 100644
index 58361e2..0000000
--- a/chromeos/ash/components/boca/babelorca/response_callback_wrapper.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_RESPONSE_CALLBACK_WRAPPER_H_
-#define CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_RESPONSE_CALLBACK_WRAPPER_H_
-
-#include <string>
-
-#include "base/types/expected.h"
-
-namespace ash::babelorca {
-
-class ResponseCallbackWrapper {
- public:
-  enum class TachyonRequestError {
-    kHttpError,
-    kNetworkError,
-    kInternalError,
-    kAuthError
-  };
-
-  ResponseCallbackWrapper(const ResponseCallbackWrapper&) = delete;
-  ResponseCallbackWrapper& operator=(const ResponseCallbackWrapper&) = delete;
-
-  virtual ~ResponseCallbackWrapper() = default;
-
-  virtual void Run(
-      base::expected<std::string, TachyonRequestError> response) = 0;
-
- protected:
-  ResponseCallbackWrapper() = default;
-};
-
-}  // namespace ash::babelorca
-
-#endif  // CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_RESPONSE_CALLBACK_WRAPPER_H_
diff --git a/chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl.h b/chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl.h
deleted file mode 100644
index bd11d14..0000000
--- a/chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_RESPONSE_CALLBACK_WRAPPER_IMPL_H_
-#define CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_RESPONSE_CALLBACK_WRAPPER_IMPL_H_
-
-#include <string>
-#include <utility>
-
-#include "base/functional/bind.h"
-#include "base/functional/callback.h"
-#include "base/location.h"
-#include "base/task/bind_post_task.h"
-#include "base/task/thread_pool.h"
-#include "base/types/expected.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
-
-namespace ash::babelorca {
-
-template <typename ResponseType>
-class ResponseCallbackWrapperImpl : public ResponseCallbackWrapper {
- public:
-  using ResponseExpectedCallback = base::OnceCallback<void(
-      base::expected<ResponseType, TachyonRequestError>)>;
-
-  explicit ResponseCallbackWrapperImpl(ResponseExpectedCallback callback)
-      : callback_(std::move(callback)) {}
-
-  ResponseCallbackWrapperImpl(const ResponseCallbackWrapperImpl&) = delete;
-  ResponseCallbackWrapperImpl& operator=(const ResponseCallbackWrapperImpl&) =
-      delete;
-
-  ~ResponseCallbackWrapperImpl() override = default;
-
-  void Run(base::expected<std::string, TachyonRequestError> response) override {
-    CHECK(callback_);
-    if (!response.has_value()) {
-      std::move(callback_).Run(base::unexpected(response.error()));
-      return;
-    }
-    auto posted_cb = base::BindPostTaskToCurrentDefault(std::move(callback_));
-    base::ThreadPool::PostTask(
-        FROM_HERE, base::BindOnce(ParseAndReply, std::move(response.value()),
-                                  std::move(posted_cb)));
-  }
-
- private:
-  static void ParseAndReply(std::string response_string,
-                            ResponseExpectedCallback callback) {
-    ResponseType response_proto;
-    if (!response_proto.ParseFromString(response_string)) {
-      std::move(callback).Run(
-          base::unexpected(TachyonRequestError::kInternalError));
-      return;
-    }
-    std::move(callback).Run(std::move(response_proto));
-  }
-
-  ResponseExpectedCallback callback_;
-};
-
-}  // namespace ash::babelorca
-
-#endif  // CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_RESPONSE_CALLBACK_WRAPPER_IMPL_H_
diff --git a/chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl_unittest.cc b/chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl_unittest.cc
deleted file mode 100644
index 9ffa2864..0000000
--- a/chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl_unittest.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl.h"
-
-#include <memory>
-
-#include "base/test/task_environment.h"
-#include "base/test/test_future.h"
-#include "base/types/expected.h"
-#include "chromeos/ash/components/boca/babelorca/proto/testing_message.pb.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace ash::babelorca {
-namespace {
-
-using TestingExpectedResponse =
-    base::expected<TestingMessage,
-                   ResponseCallbackWrapper::TachyonRequestError>;
-
-TEST(ResponseCallbackWrapperImplTest, RespondWithProtoOnSuccess) {
-  base::test::TaskEnvironment task_env;
-  static constexpr int kProtoFieldValue = 12345;
-  base::test::TestFuture<TestingExpectedResponse> test_future;
-  ResponseCallbackWrapperImpl<TestingMessage> response_callback(
-      test_future.GetCallback<TestingExpectedResponse>());
-  TestingMessage message_input;
-  message_input.set_int_field(kProtoFieldValue);
-
-  response_callback.Run(message_input.SerializeAsString());
-  TestingExpectedResponse result = test_future.Get();
-
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(result->int_field(), kProtoFieldValue);
-}
-
-TEST(ResponseCallbackWrapperImplTest, RespondWithErrorOnFailure) {
-  base::test::TaskEnvironment task_env;
-  base::test::TestFuture<TestingExpectedResponse> test_future;
-  ResponseCallbackWrapperImpl<TestingMessage> response_callback(
-      test_future.GetCallback<TestingExpectedResponse>());
-
-  response_callback.Run(base::unexpected(
-      ResponseCallbackWrapper::TachyonRequestError::kHttpError));
-  TestingExpectedResponse result = test_future.Get();
-
-  ASSERT_FALSE(result.has_value());
-  EXPECT_EQ(result.error(),
-            ResponseCallbackWrapper::TachyonRequestError::kHttpError);
-}
-
-TEST(ResponseCallbackWrapperImplTest, RespondWithErrorOnParseFailure) {
-  base::test::TaskEnvironment task_env;
-  base::test::TestFuture<TestingExpectedResponse> test_future;
-  ResponseCallbackWrapperImpl<TestingMessage> response_callback(
-      test_future.GetCallback<TestingExpectedResponse>());
-
-  response_callback.Run("invalid message");
-  TestingExpectedResponse result = test_future.Get();
-
-  ASSERT_FALSE(result.has_value());
-  EXPECT_EQ(result.error(),
-            ResponseCallbackWrapper::TachyonRequestError::kInternalError);
-}
-
-}  // namespace
-}  // namespace ash::babelorca
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_authed_client.h b/chromeos/ash/components/boca/babelorca/tachyon_authed_client.h
index dbdfea9..ce4182bf 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_authed_client.h
+++ b/chromeos/ash/components/boca/babelorca/tachyon_authed_client.h
@@ -7,19 +7,14 @@
 
 #include <memory>
 #include <string>
-#include <string_view>
 
 namespace google::protobuf {
 class MessageLite;
 }  // namespace google::protobuf
 
-namespace net {
-struct NetworkTrafficAnnotationTag;
-}  // namespace net
-
 namespace ash::babelorca {
 
-class ResponseCallbackWrapper;
+struct RequestDataWrapper;
 
 class TachyonAuthedClient {
  public:
@@ -29,18 +24,12 @@
   virtual ~TachyonAuthedClient() = default;
 
   virtual void StartAuthedRequest(
-      const net::NetworkTrafficAnnotationTag& annotation_tag,
-      std::unique_ptr<google::protobuf::MessageLite> request_proto,
-      std::string_view url,
-      int max_retries,
-      std::unique_ptr<ResponseCallbackWrapper> response_cb) = 0;
+      std::unique_ptr<RequestDataWrapper> request_data,
+      std::unique_ptr<google::protobuf::MessageLite> request_proto) = 0;
 
   virtual void StartAuthedRequestString(
-      const net::NetworkTrafficAnnotationTag& annotation_tag,
-      std::string request_string,
-      std::string_view url,
-      int max_retries,
-      std::unique_ptr<ResponseCallbackWrapper> response_cb) = 0;
+      std::unique_ptr<RequestDataWrapper> request_data,
+      std::string request_string) = 0;
 
  protected:
   TachyonAuthedClient() = default;
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl.cc b/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl.cc
index e3d9046..a900e4f 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl.cc
+++ b/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl.cc
@@ -18,8 +18,8 @@
 #include "base/task/thread_pool.h"
 #include "base/types/expected.h"
 #include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_client.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "chromeos/ash/components/boca/babelorca/token_manager.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "third_party/protobuf/src/google/protobuf/message_lite.h"
@@ -46,47 +46,33 @@
 TachyonAuthedClientImpl::~TachyonAuthedClientImpl() = default;
 
 void TachyonAuthedClientImpl::StartAuthedRequest(
-    const net::NetworkTrafficAnnotationTag& annotation_tag,
-    std::unique_ptr<google::protobuf::MessageLite> request_proto,
-    std::string_view url,
-    int max_retries,
-    std::unique_ptr<ResponseCallbackWrapper> response_cb) {
+    std::unique_ptr<RequestDataWrapper> request_data,
+    std::unique_ptr<google::protobuf::MessageLite> request_proto) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto serialize_cb =
       base::BindOnce(SerializeProtoToString, std::move(request_proto));
   auto reply_post_cb =
       base::BindOnce(&TachyonAuthedClientImpl::OnRequestProtoSerialized,
-                     weak_ptr_factory_.GetWeakPtr(), annotation_tag, url,
-                     max_retries, std::move(response_cb));
+                     weak_ptr_factory_.GetWeakPtr(), std::move(request_data));
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, std::move(serialize_cb), std::move(reply_post_cb));
 }
 
 void TachyonAuthedClientImpl::StartAuthedRequestString(
-    const net::NetworkTrafficAnnotationTag& annotation_tag,
-    std::string request_string,
-    std::string_view url,
-    int max_retries,
-    std::unique_ptr<ResponseCallbackWrapper> response_cb) {
-  OnRequestProtoSerialized(annotation_tag, url, max_retries,
-                           std::move(response_cb), request_string);
+    std::unique_ptr<RequestDataWrapper> request_data,
+    std::string request_string) {
+  OnRequestProtoSerialized(std::move(request_data), request_string);
 }
 
 void TachyonAuthedClientImpl::OnRequestProtoSerialized(
-    const net::NetworkTrafficAnnotationTag& annotation_tag,
-    std::string_view url,
-    int max_retries,
-    std::unique_ptr<ResponseCallbackWrapper> response_cb,
+    std::unique_ptr<RequestDataWrapper> request_data,
     std::optional<std::string> request_string) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!request_string) {
-    response_cb->Run(base::unexpected(
-        ResponseCallbackWrapper::TachyonRequestError::kInternalError));
+    std::move(request_data->response_cb)
+        .Run(base::unexpected(TachyonRequestError::kInternalError));
     return;
   }
-  std::unique_ptr<RequestDataWrapper> request_data =
-      std::make_unique<RequestDataWrapper>(annotation_tag, url, max_retries,
-                                           std::move(response_cb));
   request_data->content_data = std::move(*request_string);
   const std::string* oauth_token = oauth_token_manager_->GetTokenString();
   if (oauth_token) {
@@ -104,8 +90,8 @@
     bool has_oauth_token) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!has_oauth_token) {
-    request_data->response_cb->Run(base::unexpected(
-        ResponseCallbackWrapper::TachyonRequestError::kAuthError));
+    std::move(request_data->response_cb)
+        .Run(base::unexpected(TachyonRequestError::kAuthError));
     return;
   }
   std::string oauth_token = *(oauth_token_manager_->GetTokenString());
@@ -121,8 +107,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   static int constexpr kMaxAuthRetries = 1;
   if (request_data->oauth_retry_num >= kMaxAuthRetries) {
-    request_data->response_cb->Run(base::unexpected(
-        ResponseCallbackWrapper::TachyonRequestError::kAuthError));
+    std::move(request_data->response_cb)
+        .Run(base::unexpected(TachyonRequestError::kAuthError));
     return;
   }
   ++(request_data->oauth_retry_num);
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl.h b/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl.h
index 6847ee2..b61ea48 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl.h
+++ b/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl.h
@@ -7,18 +7,17 @@
 
 #include <memory>
 #include <optional>
-#include <string_view>
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
-#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_authed_client.h"
 
 namespace ash::babelorca {
 
 class TachyonClient;
 class TokenManager;
+struct RequestDataWrapper;
 
 class TachyonAuthedClientImpl : public TachyonAuthedClient {
  public:
@@ -32,24 +31,15 @@
 
   // TachyonAuthedClient:
   void StartAuthedRequest(
-      const net::NetworkTrafficAnnotationTag& annotation_tag,
-      std::unique_ptr<google::protobuf::MessageLite> request_proto,
-      std::string_view url,
-      int max_retries,
-      std::unique_ptr<ResponseCallbackWrapper> response_cb) override;
+      std::unique_ptr<RequestDataWrapper> request_data,
+      std::unique_ptr<google::protobuf::MessageLite> request_proto) override;
   void StartAuthedRequestString(
-      const net::NetworkTrafficAnnotationTag& annotation_tag,
-      std::string request_string,
-      std::string_view url,
-      int max_retries,
-      std::unique_ptr<ResponseCallbackWrapper> response_cb) override;
+      std::unique_ptr<RequestDataWrapper> request_data,
+      std::string request_string) override;
 
  private:
   void OnRequestProtoSerialized(
-      const net::NetworkTrafficAnnotationTag& annotation_tag,
-      std::string_view url,
-      int max_retries,
-      std::unique_ptr<ResponseCallbackWrapper> response_cb,
+      std::unique_ptr<RequestDataWrapper> request_data,
       std::optional<std::string> request_string);
 
   void StartAuthedRequestInternal(
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl_unittest.cc b/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl_unittest.cc
index 88d37c8..3381d50 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl_unittest.cc
+++ b/chromeos/ash/components/boca/babelorca/tachyon_authed_client_impl_unittest.cc
@@ -15,8 +15,8 @@
 #include "chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_client.h"
 #include "chromeos/ash/components/boca/babelorca/fakes/fake_token_manager.h"
 #include "chromeos/ash/components/boca/babelorca/proto/testing_message.pb.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl.h"
+#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -24,9 +24,7 @@
 namespace ash::babelorca {
 namespace {
 
-using ExpectedTestingMessage =
-    base::expected<TestingMessage,
-                   ResponseCallbackWrapper::TachyonRequestError>;
+using ExpectedTestingMessage = base::expected<std::string, TachyonRequestError>;
 
 constexpr char kOAuthToken1[] = "oauth-token1";
 constexpr char kOAuthToken2[] = "oauth-token2";
@@ -60,9 +58,10 @@
 
   const std::string& request_string() { return request_string_; }
 
-  std::unique_ptr<ResponseCallbackWrapperImpl<TestingMessage>> response_cb() {
-    return std::make_unique<ResponseCallbackWrapperImpl<TestingMessage>>(
-        test_future_.GetCallback());
+  std::unique_ptr<RequestDataWrapper> request_data_wrapper() {
+    return std::make_unique<RequestDataWrapper>(TRAFFIC_ANNOTATION_FOR_TESTS,
+                                                kUrl, kMaxRetries,
+                                                test_future_.GetCallback());
   }
 
   base::test::TestFuture<ExpectedTestingMessage>* test_future() {
@@ -86,9 +85,8 @@
   fake_token_manager()->SetFetchedVersion(1);
 
   CreateAuthedClient();
-  authed_client()->StartAuthedRequest(TRAFFIC_ANNOTATION_FOR_TESTS,
-                                      request_message(), kUrl, kMaxRetries,
-                                      response_cb());
+  authed_client()->StartAuthedRequest(request_data_wrapper(),
+                                      request_message());
   fake_client_ptr()->WaitForRequest();
 
   EXPECT_THAT(fake_client_ptr()->GetOAuthToken(), testing::StrEq(kOAuthToken1));
@@ -109,8 +107,7 @@
 
   CreateAuthedClient();
   authed_client()->StartAuthedRequestString(
-      TRAFFIC_ANNOTATION_FOR_TESTS, request_message()->SerializeAsString(),
-      kUrl, kMaxRetries, response_cb());
+      request_data_wrapper(), request_message()->SerializeAsString());
   fake_client_ptr()->WaitForRequest();
 
   EXPECT_THAT(fake_client_ptr()->GetOAuthToken(), testing::StrEq(kOAuthToken1));
@@ -126,9 +123,8 @@
 
 TEST_F(TachyonAuthedClientImplTest, NotInitiallyAuthed) {
   CreateAuthedClient();
-  authed_client()->StartAuthedRequest(TRAFFIC_ANNOTATION_FOR_TESTS,
-                                      request_message(), kUrl, kMaxRetries,
-                                      response_cb());
+  authed_client()->StartAuthedRequest(request_data_wrapper(),
+                                      request_message());
   fake_token_manager()->WaitForForceFetchRequest();
   fake_token_manager()->SetTokenString(
       std::make_unique<std::string>(kOAuthToken1));
@@ -153,9 +149,8 @@
   fake_token_manager()->SetFetchedVersion(1);
 
   CreateAuthedClient();
-  authed_client()->StartAuthedRequest(TRAFFIC_ANNOTATION_FOR_TESTS,
-                                      request_message(), kUrl, kMaxRetries,
-                                      response_cb());
+  authed_client()->StartAuthedRequest(request_data_wrapper(),
+                                      request_message());
   fake_client_ptr()->WaitForRequest();
   fake_client_ptr()->ExecuteAuthFailCb();
   fake_token_manager()->WaitForForceFetchRequest();
@@ -182,9 +177,8 @@
   fake_token_manager()->SetFetchedVersion(1);
 
   CreateAuthedClient();
-  authed_client()->StartAuthedRequest(TRAFFIC_ANNOTATION_FOR_TESTS,
-                                      request_message(), kUrl, kMaxRetries,
-                                      response_cb());
+  authed_client()->StartAuthedRequest(request_data_wrapper(),
+                                      request_message());
   fake_client_ptr()->WaitForRequest();
   // Simulate new token fetched before auth failure callback.
   fake_token_manager()->SetTokenString(
@@ -210,9 +204,8 @@
   fake_token_manager()->SetFetchedVersion(1);
 
   CreateAuthedClient();
-  authed_client()->StartAuthedRequest(TRAFFIC_ANNOTATION_FOR_TESTS,
-                                      request_message(), kUrl, kMaxRetries,
-                                      response_cb());
+  authed_client()->StartAuthedRequest(request_data_wrapper(),
+                                      request_message());
   fake_client_ptr()->WaitForRequest();
   // Simulate new token fetched before auth failure callback.
   fake_token_manager()->SetTokenString(
@@ -223,21 +216,18 @@
   fake_client_ptr()->ExecuteAuthFailCb();
 
   EXPECT_EQ(test_future()->Get(),
-            base::unexpected(
-                ResponseCallbackWrapper::TachyonRequestError::kAuthError));
+            base::unexpected(TachyonRequestError::kAuthError));
 }
 
 TEST_F(TachyonAuthedClientImplTest, TokenFetchFailed) {
   CreateAuthedClient();
-  authed_client()->StartAuthedRequest(TRAFFIC_ANNOTATION_FOR_TESTS,
-                                      request_message(), kUrl, kMaxRetries,
-                                      response_cb());
+  authed_client()->StartAuthedRequest(request_data_wrapper(),
+                                      request_message());
   fake_token_manager()->WaitForForceFetchRequest();
   fake_token_manager()->ExecuteFetchCallback(/*success=*/false);
 
   EXPECT_EQ(test_future()->Get(),
-            base::unexpected(
-                ResponseCallbackWrapper::TachyonRequestError::kAuthError));
+            base::unexpected(TachyonRequestError::kAuthError));
 }
 
 }  // namespace
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_client_impl.cc b/chromeos/ash/components/boca/babelorca/tachyon_client_impl.cc
index 67d5bfd..003d4a0 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_client_impl.cc
+++ b/chromeos/ash/components/boca/babelorca/tachyon_client_impl.cc
@@ -15,7 +15,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/types/expected.h"
 #include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_request_headers.h"
@@ -82,13 +82,13 @@
     std::unique_ptr<std::string> response_body) {
   if (url_loader->NetError() != net::OK &&
       url_loader->NetError() != net::ERR_HTTP_RESPONSE_CODE_FAILURE) {
-    request_data->response_cb->Run(base::unexpected(
-        ResponseCallbackWrapper::TachyonRequestError::kNetworkError));
+    std::move(request_data->response_cb)
+        .Run(base::unexpected(TachyonRequestError::kNetworkError));
     return;
   }
   if (!url_loader->ResponseInfo() || !url_loader->ResponseInfo()->headers) {
-    request_data->response_cb->Run(base::unexpected(
-        ResponseCallbackWrapper::TachyonRequestError::kInternalError));
+    std::move(request_data->response_cb)
+        .Run(base::unexpected(TachyonRequestError::kInternalError));
     return;
   }
   const int response_code =
@@ -98,16 +98,16 @@
     return;
   }
   if (!network::IsSuccessfulStatus(response_code)) {
-    request_data->response_cb->Run(base::unexpected(
-        ResponseCallbackWrapper::TachyonRequestError::kHttpError));
+    std::move(request_data->response_cb)
+        .Run(base::unexpected(TachyonRequestError::kHttpError));
     return;
   }
   if (!response_body) {
-    request_data->response_cb->Run(base::unexpected(
-        ResponseCallbackWrapper::TachyonRequestError::kInternalError));
+    std::move(request_data->response_cb)
+        .Run(base::unexpected(TachyonRequestError::kInternalError));
     return;
   }
-  request_data->response_cb->Run(std::move(*response_body));
+  std::move(request_data->response_cb).Run(std::move(*response_body));
 }
 
 }  // namespace ash::babelorca
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_client_impl_unittest.cc b/chromeos/ash/components/boca/babelorca/tachyon_client_impl_unittest.cc
index 7ed05c27..6d7b4ad2 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_client_impl_unittest.cc
+++ b/chromeos/ash/components/boca/babelorca/tachyon_client_impl_unittest.cc
@@ -13,8 +13,7 @@
 #include "base/types/expected.h"
 #include "chromeos/ash/components/boca/babelorca/proto/testing_message.pb.h"
 #include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -28,9 +27,7 @@
 namespace ash::babelorca {
 namespace {
 
-using ExpectedTestingMessage =
-    base::expected<TestingMessage,
-                   ResponseCallbackWrapper::TachyonRequestError>;
+using ExpectedTestingMessage = base::expected<std::string, TachyonRequestError>;
 using RequestDataPtr = std::unique_ptr<RequestDataWrapper>;
 
 constexpr char kOAuthToken[] = "oauth-token";
@@ -42,12 +39,9 @@
 class TachyonClientImplTest : public testing::Test {
  protected:
   RequestDataPtr request_data() {
-    auto response_cb =
-        std::make_unique<ResponseCallbackWrapperImpl<TestingMessage>>(
-            result_future_.GetCallback());
     auto request_data = std::make_unique<RequestDataWrapper>(
         kTrafficAnnotationTag, kUrl, /*max_retries_param=*/1,
-        std::move(response_cb));
+        result_future_.GetCallback());
     request_data->content_data = "request-body";
     return request_data;
   }
@@ -78,7 +72,9 @@
 
   auto result = result_future()->Get();
   ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(result.value().int_field(), 9999);
+  TestingMessage result_proto;
+  ASSERT_TRUE(result_proto.ParseFromString(result.value()));
+  EXPECT_EQ(result_proto.int_field(), 9999);
   EXPECT_FALSE(auth_failure_future()->IsReady());
 }
 
@@ -94,8 +90,7 @@
 
   auto result = result_future()->Get();
   ASSERT_FALSE(result.has_value());
-  EXPECT_EQ(result.error(),
-            ResponseCallbackWrapper::TachyonRequestError::kNetworkError);
+  EXPECT_EQ(result.error(), TachyonRequestError::kNetworkError);
   EXPECT_FALSE(auth_failure_future()->IsReady());
 }
 
@@ -110,8 +105,7 @@
 
   auto result = result_future()->Get();
   ASSERT_FALSE(result.has_value());
-  EXPECT_EQ(result.error(),
-            ResponseCallbackWrapper::TachyonRequestError::kHttpError);
+  EXPECT_EQ(result.error(), TachyonRequestError::kHttpError);
   EXPECT_FALSE(auth_failure_future()->IsReady());
 }
 
@@ -131,7 +125,6 @@
             request_data_ptr->annotation_tag);
   EXPECT_EQ(auth_request_data->url, request_data_ptr->url);
   EXPECT_EQ(auth_request_data->max_retries, request_data_ptr->max_retries);
-  EXPECT_EQ(auth_request_data->response_cb, request_data_ptr->response_cb);
   EXPECT_FALSE(result_future()->IsReady());
 }
 
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_registrar.cc b/chromeos/ash/components/boca/babelorca/tachyon_registrar.cc
index ad4565530d..28d13a1 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_registrar.cc
+++ b/chromeos/ash/components/boca/babelorca/tachyon_registrar.cc
@@ -17,8 +17,7 @@
 #include "chromeos/ash/components/boca/babelorca/proto/tachyon.pb.h"
 #include "chromeos/ash/components/boca/babelorca/proto/tachyon_common.pb.h"
 #include "chromeos/ash/components/boca/babelorca/proto/tachyon_enums.pb.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl.h"
+#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_authed_client.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_constants.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_utils.h"
@@ -54,13 +53,15 @@
   register_data->mutable_device_id()->set_id(client_uuid);
   register_data->mutable_device_id()->set_type(DeviceIdType::CLIENT_UUID);
 
-  auto response_callback_wrapper =
-      std::make_unique<ResponseCallbackWrapperImpl<SignInGaiaResponse>>(
-          base::BindOnce(&TachyonRegistrar::OnResponse,
-                         weak_ptr_factory.GetWeakPtr(), std::move(success_cb)));
+  auto response_callback =
+      base::BindOnce(&TachyonRegistrar::OnResponse,
+                     weak_ptr_factory.GetWeakPtr(), std::move(success_cb));
+
   authed_client_->StartAuthedRequest(
-      network_annotation_tag_.get(), std::move(signin_request), kSigninGaiaUrl,
-      kMaxRetries, std::move(response_callback_wrapper));
+      std::make_unique<RequestDataWrapper>(network_annotation_tag_,
+                                           kSigninGaiaUrl, kMaxRetries,
+                                           std::move(response_callback)),
+      std::move(signin_request));
 }
 
 std::optional<std::string> TachyonRegistrar::GetTachyonToken() {
@@ -70,14 +71,18 @@
 
 void TachyonRegistrar::OnResponse(
     base::OnceCallback<void(bool)> success_cb,
-    base::expected<SignInGaiaResponse,
-                   ResponseCallbackWrapper::TachyonRequestError> response) {
+    base::expected<std::string, TachyonRequestError> response) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!response.has_value()) {
     std::move(success_cb).Run(false);
     return;
   }
-  tachyon_token_ = std::move(response->auth_token().payload());
+  SignInGaiaResponse signin_response;
+  if (!signin_response.ParseFromString(response.value())) {
+    std::move(success_cb).Run(false);
+    return;
+  }
+  tachyon_token_ = std::move(signin_response.auth_token().payload());
   std::move(success_cb).Run(true);
 }
 
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_registrar.h b/chromeos/ash/components/boca/babelorca/tachyon_registrar.h
index 4dd9551..7f079cf 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_registrar.h
+++ b/chromeos/ash/components/boca/babelorca/tachyon_registrar.h
@@ -16,15 +16,11 @@
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
 #include "base/types/expected.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
-
-namespace net {
-struct NetworkTrafficAnnotationTag;
-}  // namespace net
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
 
 namespace ash::babelorca {
 
-class SignInGaiaResponse;
 class TachyonAuthedClient;
 
 // Register user with Tachyon and store tachyon token to be used by other
@@ -48,15 +44,13 @@
   std::optional<std::string> GetTachyonToken();
 
  private:
-  void OnResponse(
-      base::OnceCallback<void(bool)> success_cb,
-      base::expected<SignInGaiaResponse,
-                     ResponseCallbackWrapper::TachyonRequestError> response);
+  void OnResponse(base::OnceCallback<void(bool)> success_cb,
+                  base::expected<std::string, TachyonRequestError> response);
 
   SEQUENCE_CHECKER(sequence_checker_);
 
   raw_ptr<TachyonAuthedClient> authed_client_;
-  const raw_ref<const net::NetworkTrafficAnnotationTag> network_annotation_tag_;
+  const net::NetworkTrafficAnnotationTag network_annotation_tag_;
   std::optional<std::string> tachyon_token_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_registrar_unittest.cc b/chromeos/ash/components/boca/babelorca/tachyon_registrar_unittest.cc
index ca0479d..8b9a0a9 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_registrar_unittest.cc
+++ b/chromeos/ash/components/boca/babelorca/tachyon_registrar_unittest.cc
@@ -12,7 +12,7 @@
 #include "base/types/expected.h"
 #include "chromeos/ash/components/boca/babelorca/fakes/fake_tachyon_authed_client.h"
 #include "chromeos/ash/components/boca/babelorca/proto/tachyon.pb.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -46,8 +46,8 @@
   TachyonRegistrar registrar(&authed_client, TRAFFIC_ANNOTATION_FOR_TESTS);
 
   registrar.Register(kClientUuid, test_future.GetCallback());
-  authed_client.ExecuteResponseCallback(base::unexpected(
-      ResponseCallbackWrapper::TachyonRequestError::kHttpError));
+  authed_client.ExecuteResponseCallback(
+      base::unexpected(TachyonRequestError::kHttpError));
 
   EXPECT_FALSE(test_future.Get());
   std::optional<std::string> tachyon_token = registrar.GetTachyonToken();
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_request_error.h b/chromeos/ash/components/boca/babelorca/tachyon_request_error.h
new file mode 100644
index 0000000..41ae4f4
--- /dev/null
+++ b/chromeos/ash/components/boca/babelorca/tachyon_request_error.h
@@ -0,0 +1,19 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_TACHYON_REQUEST_ERROR_H_
+#define CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_TACHYON_REQUEST_ERROR_H_
+
+namespace ash::babelorca {
+
+enum class TachyonRequestError {
+  kHttpError,
+  kNetworkError,
+  kInternalError,
+  kAuthError
+};
+
+}
+
+#endif  // CHROMEOS_ASH_COMPONENTS_BOCA_BABELORCA_TACHYON_REQUEST_ERROR_H_
diff --git a/chromeos/ash/components/boca/babelorca/transcript_sender.cc b/chromeos/ash/components/boca/babelorca/transcript_sender.cc
index 0ea87a53..32e7c2b6 100644
--- a/chromeos/ash/components/boca/babelorca/transcript_sender.cc
+++ b/chromeos/ash/components/boca/babelorca/transcript_sender.cc
@@ -23,11 +23,11 @@
 #include "chromeos/ash/components/boca/babelorca/proto/tachyon.pb.h"
 #include "chromeos/ash/components/boca/babelorca/proto/tachyon_common.pb.h"
 #include "chromeos/ash/components/boca/babelorca/proto/tachyon_enums.pb.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl.h"
+#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_authed_client.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_constants.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_request_data_provider.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_utils.h"
 #include "media/mojo/mojom/speech_recognition_result.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -191,18 +191,17 @@
     return;
   }
 
-  auto response_callback_wrapper =
-      std::make_unique<ResponseCallbackWrapperImpl<InboxSendResponse>>(
-          base::BindOnce(&TranscriptSender::OnSendResponse,
-                         weak_ptr_factory.GetWeakPtr()));
+  auto response_callback_wrapper = base::BindOnce(
+      &TranscriptSender::OnSendResponse, weak_ptr_factory.GetWeakPtr());
   authed_client_->StartAuthedRequestString(
-      network_traffic_annotation_, std::move(request_string), kSendMessageUrl,
-      max_retries, std::move(response_callback_wrapper));
+      std::make_unique<RequestDataWrapper>(
+          network_traffic_annotation_, kSendMessageUrl, max_retries,
+          std::move(response_callback_wrapper)),
+      std::move(request_string));
 }
 
 void TranscriptSender::OnSendResponse(
-    base::expected<InboxSendResponse,
-                   ResponseCallbackWrapper::TachyonRequestError> response) {
+    base::expected<std::string, TachyonRequestError> response) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (response.has_value()) {
     errors_num_ = 0;
diff --git a/chromeos/ash/components/boca/babelorca/transcript_sender.h b/chromeos/ash/components/boca/babelorca/transcript_sender.h
index 7701a49..8d1f15e 100644
--- a/chromeos/ash/components/boca/babelorca/transcript_sender.h
+++ b/chromeos/ash/components/boca/babelorca/transcript_sender.h
@@ -18,7 +18,7 @@
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
 #include "base/types/expected.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 
 namespace media {
@@ -32,7 +32,6 @@
 namespace ash::babelorca {
 
 class BabelOrcaMessage;
-class InboxSendResponse;
 class TachyonAuthedClient;
 class TachyonRequestDataProvider;
 
@@ -74,8 +73,7 @@
   void Send(int max_retries, std::string message);
 
   void OnSendResponse(
-      base::expected<InboxSendResponse,
-                     ResponseCallbackWrapper::TachyonRequestError> response);
+      base::expected<std::string, TachyonRequestError> response);
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/chromeos/ash/components/boca/babelorca/transcript_sender_unittest.cc b/chromeos/ash/components/boca/babelorca/transcript_sender_unittest.cc
index a4cfb779..ddf25e2 100644
--- a/chromeos/ash/components/boca/babelorca/transcript_sender_unittest.cc
+++ b/chromeos/ash/components/boca/babelorca/transcript_sender_unittest.cc
@@ -15,9 +15,9 @@
 #include "chromeos/ash/components/boca/babelorca/proto/babel_orca_message.pb.h"
 #include "chromeos/ash/components/boca/babelorca/proto/tachyon.pb.h"
 #include "chromeos/ash/components/boca/babelorca/proto/tachyon_enums.pb.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper.h"
-#include "chromeos/ash/components/boca/babelorca/response_callback_wrapper_impl.h"
+#include "chromeos/ash/components/boca/babelorca/request_data_wrapper.h"
 #include "chromeos/ash/components/boca/babelorca/tachyon_constants.h"
+#include "chromeos/ash/components/boca/babelorca/tachyon_request_error.h"
 #include "media/mojo/mojom/speech_recognition_result.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -186,15 +186,15 @@
                                              /*is_final=*/true);
   EXPECT_TRUE(sender.SendTranscriptionUpdate(transcript1, kLanguage));
   authed_client.WaitForRequest();
-  authed_client.ExecuteResponseCallback(base::unexpected(
-      ResponseCallbackWrapper::TachyonRequestError::kHttpError));
+  authed_client.ExecuteResponseCallback(
+      base::unexpected(TachyonRequestError::kHttpError));
 
   media::SpeechRecognitionResult transcript2(kTranscriptText,
                                              /*is_final=*/false);
   EXPECT_TRUE(sender.SendTranscriptionUpdate(transcript2, kLanguage));
   authed_client.WaitForRequest();
-  authed_client.ExecuteResponseCallback(base::unexpected(
-      ResponseCallbackWrapper::TachyonRequestError::kHttpError));
+  authed_client.ExecuteResponseCallback(
+      base::unexpected(TachyonRequestError::kHttpError));
 
   media::SpeechRecognitionResult transcript3(kTranscriptText,
                                              /*is_final=*/false);
@@ -221,8 +221,8 @@
                                              /*is_final=*/true);
   EXPECT_TRUE(sender.SendTranscriptionUpdate(transcript1, kLanguage));
   authed_client.WaitForRequest();
-  authed_client.ExecuteResponseCallback(base::unexpected(
-      ResponseCallbackWrapper::TachyonRequestError::kHttpError));
+  authed_client.ExecuteResponseCallback(
+      base::unexpected(TachyonRequestError::kHttpError));
 
   // Successful request, should reset error count.
   media::SpeechRecognitionResult transcript2(kTranscriptText,
@@ -238,8 +238,8 @@
                                              /*is_final=*/false);
   EXPECT_TRUE(sender.SendTranscriptionUpdate(transcript3, kLanguage));
   authed_client.WaitForRequest();
-  authed_client.ExecuteResponseCallback(base::unexpected(
-      ResponseCallbackWrapper::TachyonRequestError::kHttpError));
+  authed_client.ExecuteResponseCallback(
+      base::unexpected(TachyonRequestError::kHttpError));
 
   EXPECT_FALSE(failure_future.IsReady());
 }
@@ -261,32 +261,32 @@
                                              /*is_final=*/true);
   EXPECT_TRUE(sender.SendTranscriptionUpdate(transcript1, kLanguage));
   authed_client.WaitForRequest();
-  std::unique_ptr<ResponseCallbackWrapper> response_cb1 =
+  RequestDataWrapper::ResponseCallback response_cb1 =
       authed_client.TakeResponseCallback();
 
   media::SpeechRecognitionResult transcript2(kTranscriptText,
                                              /*is_final=*/false);
   EXPECT_TRUE(sender.SendTranscriptionUpdate(transcript2, kLanguage));
   authed_client.WaitForRequest();
-  std::unique_ptr<ResponseCallbackWrapper> response_cb2 =
+  RequestDataWrapper::ResponseCallback response_cb2 =
       authed_client.TakeResponseCallback();
 
   media::SpeechRecognitionResult transcript3(kTranscriptText,
                                              /*is_final=*/false);
   EXPECT_TRUE(sender.SendTranscriptionUpdate(transcript3, kLanguage));
   authed_client.WaitForRequest();
-  std::unique_ptr<ResponseCallbackWrapper> response_cb3 =
+  RequestDataWrapper::ResponseCallback response_cb3 =
       authed_client.TakeResponseCallback();
 
-  response_cb1->Run(base::unexpected(
-      ResponseCallbackWrapper::TachyonRequestError::kHttpError));
-  response_cb2->Run(base::unexpected(
-      ResponseCallbackWrapper::TachyonRequestError::kHttpError));
+  std::move(response_cb1)
+      .Run(base::unexpected(TachyonRequestError::kHttpError));
+  std::move(response_cb2)
+      .Run(base::unexpected(TachyonRequestError::kHttpError));
 
   EXPECT_TRUE(failure_future.IsReady());
 
-  response_cb3->Run(base::unexpected(
-      ResponseCallbackWrapper::TachyonRequestError::kHttpError));
+  std::move(response_cb3)
+      .Run(base::unexpected(TachyonRequestError::kHttpError));
 
   media::SpeechRecognitionResult transcript4(kTranscriptText,
                                              /*is_final=*/false);
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 1d32aec8..e170eca 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -684,6 +684,12 @@
         <message name="IDS_GRADUATION_APP_TITLE" desc="Name of the Graduation app in the app shelf. Graduation allows user to transfer data from their EDU account to their consumer account.">
           Graduation
         </message>
+        <message name="IDS_GRADUATION_APP_BACK_BUTTON_LABEL" desc="Text for a button that navigates to the previous step of the Graduation flow.">
+          Back
+        </message>
+        <message name="IDS_GRADUATION_APP_DONE_BUTTON_LABEL" desc="Text for a button that is clicked to indicate that the Graduation flow is complete.">
+          Done
+        </message>
         <message name="IDS_GRADUATION_APP_WEBVIEW_LOADING_MESSAGE" desc="Message shown while the Takeout webview is loading.">
           Please wait...
         </message>
diff --git a/chromeos/chromeos_strings_grd/IDS_GRADUATION_APP_BACK_BUTTON_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_GRADUATION_APP_BACK_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..1f4cf56
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_GRADUATION_APP_BACK_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+558ee684bdced9c80a3ac5e8701196e16dbfb00e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_GRADUATION_APP_DONE_BUTTON_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_GRADUATION_APP_DONE_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..1f4cf56
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_GRADUATION_APP_DONE_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+558ee684bdced9c80a3ac5e8701196e16dbfb00e
\ No newline at end of file
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 8bf71a5..b90f995 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -21,7 +21,7 @@
 // percentage.
 BASE_FEATURE(kBatteryBadgeIcon,
              "BatteryBadgeIcon",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Enables or disables more filtering out of phones from the Bluetooth UI.
 BASE_FEATURE(kBluetoothPhoneFilter,
diff --git a/clank b/clank
index 4fc73bc..123f9cd8 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 4fc73bc9ae2bff63c2115ab8b02284a7eb9fbeee
+Subproject commit 123f9cd86c36186e08fafb2445dd5fa0e5688a94
diff --git a/components/autofill_prediction_improvements/core/browser/BUILD.gn b/components/autofill_prediction_improvements/core/browser/BUILD.gn
index 5f122138..194ef90 100644
--- a/components/autofill_prediction_improvements/core/browser/BUILD.gn
+++ b/components/autofill_prediction_improvements/core/browser/BUILD.gn
@@ -14,6 +14,8 @@
     "autofill_prediction_improvements_filling_engine_impl.h",
     "autofill_prediction_improvements_manager.cc",
     "autofill_prediction_improvements_manager.h",
+    "autofill_prediction_improvements_utils.cc",
+    "autofill_prediction_improvements_utils.h",
   ]
 
   deps = [
@@ -32,6 +34,7 @@
   sources = [
     "autofill_prediction_improvements_filling_engine_impl_unittest.cc",
     "autofill_prediction_improvements_manager_unittest.cc",
+    "autofill_prediction_improvements_utils_unittest.cc",
   ]
   deps = [
     ":browser",
diff --git a/components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils.cc b/components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils.cc
new file mode 100644
index 0000000..a842ea3
--- /dev/null
+++ b/components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils.cc
@@ -0,0 +1,42 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/field_type_utils.h"
+#include "components/autofill/core/browser/field_types.h"
+#include "components/autofill/core/browser/form_structure.h"
+
+namespace autofill_prediction_improvements {
+
+bool IsFormEligibleByFieldCriteria(const autofill::FormStructure& form) {
+  // A counter for the number of fields that have been identified as eligible.
+  int prediction_improvement_eligable_fields = 0;
+  // Address fields that are not also eligible for prediction improvements.
+  int additional_address_fields = 0;
+
+  for (const auto& field : form) {
+#if BUILDFLAG(USE_INTERNAL_AUTOFILL_PATTERNS)
+    if (field->heuristic_type(
+            autofill::HeuristicSource::kPredictionImprovementRegexes) ==
+        autofill::IMPROVED_PREDICTION) {
+      ++prediction_improvement_eligable_fields;
+    }
+#else
+    if (field->Type().GetStorableType() == autofill::IMPROVED_PREDICTION) {
+      ++prediction_improvement_eligable_fields;
+    }
+#endif
+    else if (autofill::IsAddressType(field->Type().GetStorableType())) {
+      ++additional_address_fields;
+    }
+  }
+
+  const int total_number_of_fillable_fields =
+      prediction_improvement_eligable_fields + additional_address_fields;
+
+  // TODO(crbug.com/365517792): Make this controllable via finch.
+  return prediction_improvement_eligable_fields > 0 &&
+         total_number_of_fillable_fields > 0;
+}
+
+}  // namespace autofill_prediction_improvements
diff --git a/components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils.h b/components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils.h
new file mode 100644
index 0000000..e278456
--- /dev/null
+++ b/components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils.h
@@ -0,0 +1,18 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_PREDICTION_IMPROVEMENTS_CORE_BROWSER_AUTOFILL_PREDICTION_IMPROVEMENTS_UTILS_H_
+#define COMPONENTS_AUTOFILL_PREDICTION_IMPROVEMENTS_CORE_BROWSER_AUTOFILL_PREDICTION_IMPROVEMENTS_UTILS_H_
+
+namespace autofill {
+class FormStructure;
+}  // namespace autofill
+
+namespace autofill_prediction_improvements {
+
+bool IsFormEligibleByFieldCriteria(const autofill::FormStructure& form);
+
+}  // namespace autofill_prediction_improvements
+
+#endif  // COMPONENTS_AUTOFILL_PREDICTION_IMPROVEMENTS_CORE_BROWSER_AUTOFILL_PREDICTION_IMPROVEMENTS_UTILS_H_
diff --git a/components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils_unittest.cc b/components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils_unittest.cc
new file mode 100644
index 0000000..820cc95
--- /dev/null
+++ b/components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils_unittest.cc
@@ -0,0 +1,102 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_prediction_improvements/core/browser/autofill_prediction_improvements_utils.h"
+
+#include "components/autofill/core/browser/form_structure.h"
+#include "components/autofill/core/browser/form_structure_test_api.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill_prediction_improvements {
+
+namespace {
+
+// Test that an empty form is not eligible.
+TEST(AutofillPredictionImprovementsUtilsTest,
+     IsFormEligibleByFieldCriteria_EmptyForm) {
+  autofill::FormData form_data;
+  autofill::FormStructure form(form_data);
+  autofill::FormStructureTestApi form_test_api(form);
+
+  EXPECT_FALSE(IsFormEligibleByFieldCriteria(form));
+}
+
+// Test that a form with a single UNKNOWN_TYPE field is not eligible.
+TEST(AutofillPredictionImprovementsUtilsTest,
+     IsFormEligibleByFieldCriteria_SingleUnknownField) {
+  autofill::FormData form_data;
+  autofill::FormStructure form(form_data);
+  autofill::FormStructureTestApi form_test_api(form);
+
+  autofill::AutofillField& unknown_field = form_test_api.PushField();
+  unknown_field.set_heuristic_type(autofill::GetActiveHeuristicSource(),
+                                   autofill::UNKNOWN_TYPE);
+}
+
+// Test that a form with a single address field is not eligible.
+TEST(AutofillPredictionImprovementsUtilsTest,
+     IsFormEligibleByFieldCriteria_SingleAddressField) {
+  autofill::FormData form_data;
+  autofill::FormStructure form(form_data);
+  autofill::FormStructureTestApi form_test_api(form);
+
+  // Add an additional address field and verify that is also does not yield and
+  // overall eligible form.
+  autofill::AutofillField& address_field = form_test_api.PushField();
+  address_field.set_heuristic_type(autofill::GetActiveHeuristicSource(),
+                                   autofill::NAME_FIRST);
+  EXPECT_FALSE(IsFormEligibleByFieldCriteria(form));
+}
+
+// Test that a form with an eligible field is overall eligible.
+TEST(AutofillPredictionImprovementsUtilsTest,
+     IsFormEligibleByFieldCriteria_SingleEligibleField) {
+  autofill::FormData form_data;
+  autofill::FormStructure form(form_data);
+  autofill::FormStructureTestApi form_test_api(form);
+
+  autofill::AutofillField& prediction_improvement_field =
+      form_test_api.PushField();
+#if BUILDFLAG(USE_INTERNAL_AUTOFILL_PATTERNS)
+  prediction_improvement_field.set_heuristic_type(
+      autofill::HeuristicSource::kPredictionImprovementRegexes,
+      autofill::IMPROVED_PREDICTION);
+#else
+  prediction_improvement_field.set_heuristic_type(
+      autofill::GetActiveHeuristicSource(), autofill::IMPROVED_PREDICTION);
+#endif
+  EXPECT_TRUE(IsFormEligibleByFieldCriteria(form));
+}
+
+// Test that a form with an eligible field is overall eligible.
+TEST(AutofillPredictionImprovementsUtilsTest,
+     IsFormEligibleByFieldCriteria_MixedFormWithEligibleField) {
+  autofill::FormData form_data;
+  autofill::FormStructure form(form_data);
+  autofill::FormStructureTestApi form_test_api(form);
+
+  autofill::AutofillField& unknown_field = form_test_api.PushField();
+  unknown_field.set_heuristic_type(autofill::GetActiveHeuristicSource(),
+                                   autofill::UNKNOWN_TYPE);
+
+  autofill::AutofillField& address_field = form_test_api.PushField();
+  address_field.set_heuristic_type(autofill::GetActiveHeuristicSource(),
+                                   autofill::NAME_FIRST);
+
+  autofill::AutofillField& prediction_improvement_field =
+      form_test_api.PushField();
+#if BUILDFLAG(USE_INTERNAL_AUTOFILL_PATTERNS)
+  prediction_improvement_field.set_heuristic_type(
+      autofill::HeuristicSource::kPredictionImprovementRegexes,
+      autofill::IMPROVED_PREDICTION);
+#else
+  prediction_improvement_field.set_heuristic_type(
+      autofill::GetActiveHeuristicSource(), autofill::IMPROVED_PREDICTION);
+#endif
+  EXPECT_TRUE(IsFormEligibleByFieldCriteria(form));
+}
+
+}  // namespace
+
+}  // namespace autofill_prediction_improvements
diff --git a/components/commerce_strings.grdp b/components/commerce_strings.grdp
index 245fc89..d9b9086 100644
--- a/components/commerce_strings.grdp
+++ b/components/commerce_strings.grdp
@@ -451,6 +451,9 @@
   <message name="IDS_PRICE_INSIGHTS_ACCESSIBILITY" desc="Accessibility string for the price insights entry point when the confidence is low.">
     Price Insights
   </message>
+  <message name="IDS_PRICE_INSIGHTS_GRAPH_ACCESSIBILITY_LABEL" is_accessibility_with_no_ui="true" desc="Accessibility string for the price insights graph readout.">
+      These are <ph name="CURRENCY">$1<ex>USD</ex></ph> prices for product <ph name="PRODUCT">$2<ex>iPhone 15.</ex></ph>
+    </message>
   <message name="IDS_PRICE_INSIGHTS_SNACKBAR_BUTTON_TITLE" desc="The snackbar button title for Price Insights when the user has notification enabled.">
     View
   </message>
@@ -539,6 +542,18 @@
     <message name="IDS_DISCOUNT_EXPIRATION_DATE_ANDROID" desc="Text shown in the discount dialog to tell users the discount expiration date.">
       Valid until <ph name="DATE">$1<ex>2023-01-01</ex></ph>
     </message>
+    <message name="IDS_COMMERCE_BOTTOM_SHEET_CONTENT_DESCRIPTION" desc="Accessibility string that describes the commerce bottom sheet." >
+      Commerce bottom sheet
+    </message>
+    <message name="IDS_COMMERCE_BOTTOM_SHEET_CONTENT_OPENED_FULL" desc="Accessibility string that describes fully opening the commerce bottom sheet.">
+      Commerce bottom sheet opened at full height
+    </message>
+    <message name="IDS_COMMERCE_BOTTOM_SHEET_CONTENT_CLOSED" desc="Accessibility string that describes closing the commerce bottom sheet.">
+      Commerce bottom sheet closed
+    </message>
+    <message name="IDS_COMMERCE_BOTTOM_SHEET_CONTENT_OPENED_HALF" desc="Accessibility string that describes half opening the commerce bottom sheet.">
+      Commerce bottom sheet opened at half height
+    </message>
     </if> <!-- is_android -->
 
   <!-- For Compare -->
diff --git a/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_CLOSED.png.sha1 b/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_CLOSED.png.sha1
new file mode 100644
index 0000000..cae9124e
--- /dev/null
+++ b/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_CLOSED.png.sha1
@@ -0,0 +1 @@
+b002f286dcd926b47aa13d381cad01c9707fb863
\ No newline at end of file
diff --git a/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_DESCRIPTION.png.sha1 b/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..cae9124e
--- /dev/null
+++ b/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+b002f286dcd926b47aa13d381cad01c9707fb863
\ No newline at end of file
diff --git a/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_OPENED_FULL.png.sha1 b/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_OPENED_FULL.png.sha1
new file mode 100644
index 0000000..cae9124e
--- /dev/null
+++ b/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_OPENED_FULL.png.sha1
@@ -0,0 +1 @@
+b002f286dcd926b47aa13d381cad01c9707fb863
\ No newline at end of file
diff --git a/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_OPENED_HALF.png.sha1 b/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_OPENED_HALF.png.sha1
new file mode 100644
index 0000000..cae9124e
--- /dev/null
+++ b/components/commerce_strings_grdp/IDS_COMMERCE_BOTTOM_SHEET_CONTENT_OPENED_HALF.png.sha1
@@ -0,0 +1 @@
+b002f286dcd926b47aa13d381cad01c9707fb863
\ No newline at end of file
diff --git a/components/data_sharing/internal/android/data_sharing_conversion_bridge.cc b/components/data_sharing/internal/android/data_sharing_conversion_bridge.cc
index cff37ae..ae1ef42 100644
--- a/components/data_sharing/internal/android/data_sharing_conversion_bridge.cc
+++ b/components/data_sharing/internal/android/data_sharing_conversion_bridge.cc
@@ -8,7 +8,6 @@
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
-#include "components/data_sharing/public/group_data.h"
 #include "url/android/gurl_android.h"
 
 // Must come after all headers that specialize FromJniType() / ToJniType().
@@ -16,6 +15,7 @@
 #include "components/data_sharing/public/jni_headers/GroupData_jni.h"
 #include "components/data_sharing/public/jni_headers/GroupMember_jni.h"
 #include "components/data_sharing/public/jni_headers/GroupToken_jni.h"
+#include "components/data_sharing/public/jni_headers/ServiceStatus_jni.h"
 #include "components/data_sharing/public/jni_headers/SharedEntity_jni.h"
 
 using base::android::AttachCurrentThread;
@@ -65,6 +65,17 @@
 }
 
 // static
+ScopedJavaLocalRef<jobject>
+DataSharingConversionBridge::CreateJavaServiceStatus(
+    JNIEnv* env,
+    const ServiceStatus& status) {
+  return Java_ServiceStatus_createServiceStatus(
+      env, static_cast<int>(status.signin_status),
+      static_cast<int>(status.sync_status),
+      static_cast<int>(status.collaboration_status));
+}
+
+// static
 ScopedJavaLocalRef<jobject> DataSharingConversionBridge::CreateJavaSharedEntity(
     JNIEnv* env,
     const SharedEntity& entity) {
diff --git a/components/data_sharing/internal/android/data_sharing_conversion_bridge.h b/components/data_sharing/internal/android/data_sharing_conversion_bridge.h
index c543a6a..32bd2aaed 100644
--- a/components/data_sharing/internal/android/data_sharing_conversion_bridge.h
+++ b/components/data_sharing/internal/android/data_sharing_conversion_bridge.h
@@ -11,6 +11,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "components/data_sharing/public/data_sharing_service.h"
 #include "components/data_sharing/public/group_data.h"
+#include "components/data_sharing/public/service_status.h"
 
 using base::android::ScopedJavaLocalRef;
 
@@ -34,6 +35,11 @@
       JNIEnv* env,
       const GroupData& result);
 
+  // Creates an object of org.chromium.components.data_sharing.ServiceStatus.
+  static ScopedJavaLocalRef<jobject> CreateJavaServiceStatus(
+      JNIEnv* env,
+      const ServiceStatus& status);
+
   // Creates an object of org.chromium.components.data_sharing.SharedEntity.
   static ScopedJavaLocalRef<jobject> CreateJavaSharedEntity(
       JNIEnv* env,
diff --git a/components/data_sharing/internal/android/data_sharing_service_android.cc b/components/data_sharing/internal/android/data_sharing_service_android.cc
index ec9dec7..1067bf25 100644
--- a/components/data_sharing/internal/android/data_sharing_service_android.cc
+++ b/components/data_sharing/internal/android/data_sharing_service_android.cc
@@ -316,6 +316,12 @@
   return data_sharing_service_->GetUIDelegate()->GetJavaObject();
 }
 
+ScopedJavaLocalRef<jobject> DataSharingServiceAndroid::GetServiceStatus(
+    JNIEnv* env) {
+  return DataSharingConversionBridge::CreateJavaServiceStatus(
+      env, data_sharing_service_->GetServiceStatus());
+}
+
 ScopedJavaLocalRef<jobject> DataSharingServiceAndroid::GetJavaObject() {
   return ScopedJavaLocalRef<jobject>(java_obj_);
 }
diff --git a/components/data_sharing/internal/android/data_sharing_service_android.h b/components/data_sharing/internal/android/data_sharing_service_android.h
index 32c6c52..04688d0 100644
--- a/components/data_sharing/internal/android/data_sharing_service_android.h
+++ b/components/data_sharing/internal/android/data_sharing_service_android.h
@@ -66,6 +66,7 @@
                                 const JavaParamRef<jstring>& access_token,
                                 const JavaParamRef<jobject>& j_callback);
   ScopedJavaLocalRef<jobject> GetUIDelegate(JNIEnv* env);
+  ScopedJavaLocalRef<jobject> GetServiceStatus(JNIEnv* env);
 
   // Returns the DataSharingServiceImpl java object.
   ScopedJavaLocalRef<jobject> GetJavaObject();
diff --git a/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java
index 9aa5434..99b867b 100644
--- a/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java
+++ b/components/data_sharing/internal/android/java/src/org/chromium/components/data_sharing/DataSharingServiceImpl.java
@@ -128,6 +128,11 @@
         return DataSharingServiceImplJni.get().getUIDelegate(mNativePtr);
     }
 
+    @Override
+    public ServiceStatus getServiceStatus() {
+        return DataSharingServiceImplJni.get().getServiceStatus(mNativePtr);
+    }
+
     @CalledByNative
     private void clearNativePtr() {
         mNativePtr = 0;
@@ -193,5 +198,7 @@
                 Callback<SharedDataPreviewOrFailureOutcome> callback);
 
         DataSharingUIDelegate getUIDelegate(long nativeDataSharingServiceAndroid);
+
+        ServiceStatus getServiceStatus(long nativeDataSharingServiceAndroid);
     }
 }
diff --git a/components/data_sharing/internal/data_sharing_service_impl.cc b/components/data_sharing/internal/data_sharing_service_impl.cc
index f1cbb29b..621e6d67 100644
--- a/components/data_sharing/internal/data_sharing_service_impl.cc
+++ b/components/data_sharing/internal/data_sharing_service_impl.cc
@@ -107,6 +107,18 @@
   if (sdk_delegate_) {
     sdk_delegate_->Initialize(data_sharing_network_loader_.get());
   }
+
+  // Initialize ServiceStatus.
+  current_status_.collaboration_status = CollaborationStatus::kDisabled;
+  if (base::FeatureList::IsEnabled(features::kDataSharingFeature)) {
+    current_status_.collaboration_status =
+        CollaborationStatus::kEnabledCreateAndJoin;
+  }
+
+  // TODO(b/360184707): Add identity manager and sync service to observe state
+  // changes.
+  current_status_.signin_status = SigninStatus::kNotSignedIn;
+  current_status_.sync_status = SyncStatus::kNotSyncing;
 }
 
 DataSharingServiceImpl::~DataSharingServiceImpl() {
@@ -591,9 +603,8 @@
   return ui_delegate_.get();
 }
 
-DataSharingService::ServiceStatus DataSharingServiceImpl::GetServiceStatus() {
-  NOTIMPLEMENTED();
-  return DataSharingService::ServiceStatus();
+ServiceStatus DataSharingServiceImpl::GetServiceStatus() {
+  return current_status_;
 }
 
 void DataSharingServiceImpl::OnAccessTokenAdded(
diff --git a/components/data_sharing/internal/data_sharing_service_impl.h b/components/data_sharing/internal/data_sharing_service_impl.h
index 65da7ac9..4d7a58c 100644
--- a/components/data_sharing/internal/data_sharing_service_impl.h
+++ b/components/data_sharing/internal/data_sharing_service_impl.h
@@ -154,6 +154,7 @@
       const base::expected<data_sharing_pb::AddAccessTokenResult, absl::Status>&
           result);
 
+  ServiceStatus current_status_;
   // It must be destroyed after the `sdk_delegate_` member because
   // `sdk_delegate` needs the `data_sharing_network_loader_`.
   std::unique_ptr<DataSharingNetworkLoader> data_sharing_network_loader_;
diff --git a/components/data_sharing/internal/empty_data_sharing_service.cc b/components/data_sharing/internal/empty_data_sharing_service.cc
index ccc5040..9848cc7 100644
--- a/components/data_sharing/internal/empty_data_sharing_service.cc
+++ b/components/data_sharing/internal/empty_data_sharing_service.cc
@@ -91,8 +91,8 @@
   return nullptr;
 }
 
-DataSharingService::ServiceStatus EmptyDataSharingService::GetServiceStatus() {
-  return DataSharingService::ServiceStatus();
+ServiceStatus EmptyDataSharingService::GetServiceStatus() {
+  return ServiceStatus();
 }
 
 }  // namespace data_sharing
diff --git a/components/data_sharing/internal/empty_data_sharing_service.h b/components/data_sharing/internal/empty_data_sharing_service.h
index 0c94de1..2ea8f82 100644
--- a/components/data_sharing/internal/empty_data_sharing_service.h
+++ b/components/data_sharing/internal/empty_data_sharing_service.h
@@ -65,7 +65,7 @@
       base::OnceCallback<void(const SharedDataPreviewOrFailureOutcome&)>
           callback) override;
   DataSharingUIDelegate* GetUIDelegate() override;
-  DataSharingService::ServiceStatus GetServiceStatus() override;
+  ServiceStatus GetServiceStatus() override;
 };
 
 }  // namespace data_sharing
diff --git a/components/data_sharing/public/BUILD.gn b/components/data_sharing/public/BUILD.gn
index 986b93cf..f4212ad 100644
--- a/components/data_sharing/public/BUILD.gn
+++ b/components/data_sharing/public/BUILD.gn
@@ -20,6 +20,8 @@
     "features.h",
     "group_data.cc",
     "group_data.h",
+    "service_status.cc",
+    "service_status.h",
   ]
 
   public_deps = [
@@ -46,6 +48,7 @@
       "android/java/src/org/chromium/components/data_sharing/GroupData.java",
       "android/java/src/org/chromium/components/data_sharing/GroupMember.java",
       "android/java/src/org/chromium/components/data_sharing/GroupToken.java",
+      "android/java/src/org/chromium/components/data_sharing/ServiceStatus.java",
       "android/java/src/org/chromium/components/data_sharing/SharedDataPreview.java",
       "android/java/src/org/chromium/components/data_sharing/SharedEntity.java",
       "android/java/src/org/chromium/components/data_sharing/configs/AvatarConfig.java",
@@ -72,6 +75,7 @@
       "data_sharing_network_loader.h",
       "data_sharing_service.h",
       "group_data.h",
+      "service_status.h",
     ]
   }
 
@@ -81,6 +85,7 @@
       "android/java/src/org/chromium/components/data_sharing/GroupData.java",
       "android/java/src/org/chromium/components/data_sharing/GroupMember.java",
       "android/java/src/org/chromium/components/data_sharing/GroupToken.java",
+      "android/java/src/org/chromium/components/data_sharing/ServiceStatus.java",
       "android/java/src/org/chromium/components/data_sharing/SharedEntity.java",
     ]
   }
diff --git a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java
index 030940e9..f3a9dd6 100644
--- a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java
+++ b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/DataSharingService.java
@@ -245,4 +245,9 @@
      * @return The current instance of {@link DataSharingUIDelegate}.
      */
     DataSharingUIDelegate getUIDelegate();
+
+    /**
+     * @return The current {@link ServiceStatus} of the service.
+     */
+    ServiceStatus getServiceStatus();
 }
diff --git a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/ServiceStatus.java b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/ServiceStatus.java
new file mode 100644
index 0000000..38d2429
--- /dev/null
+++ b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/ServiceStatus.java
@@ -0,0 +1,73 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.data_sharing;
+
+import org.jni_zero.CalledByNative;
+import org.jni_zero.JNINamespace;
+
+/** Information about a member of a group. */
+@JNINamespace("data_sharing")
+public class ServiceStatus {
+    public @SigninStatus int signinStatus;
+    public @SyncStatus int syncStatus;
+    public @CollaborationStatus int collaborationStatus;
+
+    /**
+     * Constructor for a {@link ServiceStatus} object.
+     *
+     * @param signinStatus The signin status of the service.
+     * @param syncStatus The sync status of the service.
+     * @param collaborationStatus The collaboration status of the service.
+     */
+    public ServiceStatus(
+            @SigninStatus int signinStatus,
+            @SyncStatus int syncStatus,
+            @CollaborationStatus int collaborationStatus) {
+        this.signinStatus = signinStatus;
+        this.syncStatus = syncStatus;
+        this.collaborationStatus = collaborationStatus;
+    }
+
+    // Helper functions for checking DataSharingService's status.
+    // Please keep the logic consistent with //components/data_sharing/public/service_status.h.
+
+    /**
+     * @return Whether the user is allowed to join a Data Sharing collaboration.
+     */
+    public boolean isAllowedToJoin() {
+        switch (collaborationStatus) {
+            case CollaborationStatus.DISABLED:
+            case CollaborationStatus.DISABLED_FOR_POLICY:
+                return false;
+            case CollaborationStatus.ALLOWED_TO_JOIN:
+            case CollaborationStatus.ENABLED_JOIN_ONLY:
+            case CollaborationStatus.ENABLED_CREATE_AND_JOIN:
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return Whether the user is allowed to create a Data Sharing collaboration.
+     */
+    public boolean isAllowedToCreate() {
+        switch (collaborationStatus) {
+            case CollaborationStatus.DISABLED:
+            case CollaborationStatus.DISABLED_FOR_POLICY:
+            case CollaborationStatus.ALLOWED_TO_JOIN:
+            case CollaborationStatus.ENABLED_JOIN_ONLY:
+                return false;
+            case CollaborationStatus.ENABLED_CREATE_AND_JOIN:
+                return true;
+        }
+        return false;
+    }
+
+    @CalledByNative
+    private static ServiceStatus createServiceStatus(
+            int signinStatus, int syncStatus, int collaborationStatus) {
+        return new ServiceStatus(signinStatus, syncStatus, collaborationStatus);
+    }
+}
diff --git a/components/data_sharing/public/data_sharing_service.h b/components/data_sharing/public/data_sharing_service.h
index d51d887..47a59b9 100644
--- a/components/data_sharing/public/data_sharing_service.h
+++ b/components/data_sharing/public/data_sharing_service.h
@@ -15,6 +15,7 @@
 #include "build/build_config.h"
 #include "components/data_sharing/public/data_sharing_ui_delegate.h"
 #include "components/data_sharing/public/group_data.h"
+#include "components/data_sharing/public/service_status.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/sync/model/data_type_sync_bridge.h"
 
@@ -54,45 +55,6 @@
     kQueryMissingFailure = 3
   };
 
-  // GENERATED_JAVA_ENUM_PACKAGE: (
-  //   org.chromium.components.data_sharing)
-  enum class SigninStatus {
-    kNotSignedIn = 0,
-    kSignedInPaused = 1,
-    kSignedIn = 2
-  };
-
-  // GENERATED_JAVA_ENUM_PACKAGE: (
-  //   org.chromium.components.data_sharing)
-  enum class SyncStatus {
-    kNotSyncing = 0,
-    kSyncWithoutTabGroup = 1,
-    kSyncEnabled = 2
-  };
-
-  // GENERATED_JAVA_ENUM_PACKAGE: (
-  //   org.chromium.components.data_sharing)
-  enum class CollaborationStatus {
-    // Users are not allowed to either join or create.
-    kDisabled = 0,
-    // The Chrome policy disables this feature, eg: enterprise policies.
-    kDisabledForPolicy = 1,
-    // Users are allowed to join only but have not joined a shared tab group
-    // yet.
-    kAllowedToJoin = 2,
-    // Users are allowed to join only and have already joined at least 1 shared
-    // tab group.
-    kEnabledJoinOnly = 3,
-    // Users are allowed to join and create shared tab groups.
-    kEnabledCreateAndJoin = 4
-  };
-
-  struct ServiceStatus {
-    SigninStatus signin_status;
-    SyncStatus sync_status;
-    CollaborationStatus collaboration_status;
-  };
-
   class Observer : public base::CheckedObserver {
    public:
     Observer() = default;
diff --git a/components/data_sharing/public/service_status.cc b/components/data_sharing/public/service_status.cc
new file mode 100644
index 0000000..7b21c2a
--- /dev/null
+++ b/components/data_sharing/public/service_status.cc
@@ -0,0 +1,39 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/data_sharing/public/service_status.h"
+
+namespace data_sharing {
+
+// LINT.IfChange(ServiceStatus)
+bool ServiceStatus::IsAllowedToJoin() {
+  // Please keep logic consistent with
+  // //components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/ServiceStatus.java.
+  switch (collaboration_status) {
+    case CollaborationStatus::kDisabled:
+    case CollaborationStatus::kDisabledForPolicy:
+      return false;
+    case CollaborationStatus::kAllowedToJoin:
+    case CollaborationStatus::kEnabledJoinOnly:
+    case CollaborationStatus::kEnabledCreateAndJoin:
+      return true;
+  }
+}
+
+bool ServiceStatus::IsAllowedToCreate() {
+  // Please keep logic consistent with
+  // //components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/ServiceStatus.java.
+  switch (collaboration_status) {
+    case CollaborationStatus::kDisabled:
+    case CollaborationStatus::kDisabledForPolicy:
+    case CollaborationStatus::kAllowedToJoin:
+    case CollaborationStatus::kEnabledJoinOnly:
+      return false;
+    case CollaborationStatus::kEnabledCreateAndJoin:
+      return true;
+  }
+}
+// LINT.ThenChange(//components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/ServiceStatus.java)
+
+}  // namespace data_sharing
diff --git a/components/data_sharing/public/service_status.h b/components/data_sharing/public/service_status.h
new file mode 100644
index 0000000..dfc9900
--- /dev/null
+++ b/components/data_sharing/public/service_status.h
@@ -0,0 +1,60 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DATA_SHARING_PUBLIC_SERVICE_STATUS_H_
+#define COMPONENTS_DATA_SHARING_PUBLIC_SERVICE_STATUS_H_
+
+namespace data_sharing {
+
+// GENERATED_JAVA_ENUM_PACKAGE: (
+//   org.chromium.components.data_sharing)
+enum class SigninStatus {
+  kNotSignedIn = 0,
+  kSignedInPaused = 1,
+  kSignedIn = 2
+};
+
+// GENERATED_JAVA_ENUM_PACKAGE: (
+//   org.chromium.components.data_sharing)
+enum class SyncStatus {
+  kNotSyncing = 0,
+  kSyncWithoutTabGroup = 1,
+  kSyncEnabled = 2
+};
+
+// GENERATED_JAVA_ENUM_PACKAGE: (
+//   org.chromium.components.data_sharing)
+enum class CollaborationStatus {
+  // Users are not allowed to either join or create.
+  kDisabled = 0,
+  // The Chrome policy disables this feature, eg: enterprise policies.
+  kDisabledForPolicy = 1,
+  // Users are allowed to join only but have not joined a shared tab group
+  // yet.
+  kAllowedToJoin = 2,
+  // Users are allowed to join only and have already joined at least 1 shared
+  // tab group.
+  kEnabledJoinOnly = 3,
+  // Users are allowed to join and create shared tab groups.
+  kEnabledCreateAndJoin = 4
+};
+
+struct ServiceStatus {
+  SigninStatus signin_status;
+  SyncStatus sync_status;
+  CollaborationStatus collaboration_status;
+
+  // Helper functions for checking DataSharingService's status.
+
+  // Whether the current user is allowed to join a collaboration.
+  bool IsAllowedToJoin();
+
+  // Whether the current user is allowed to both join and create a new
+  // collaboration.
+  bool IsAllowedToCreate();
+};
+
+}  // namespace data_sharing
+
+#endif  // COMPONENTS_DATA_SHARING_PUBLIC_SERVICE_STATUS_H_
diff --git a/components/data_sharing/test_support/android/java/src/org/chromium/components/data_sharing/TestDataSharingService.java b/components/data_sharing/test_support/android/java/src/org/chromium/components/data_sharing/TestDataSharingService.java
index 7f66572..f0c4b12b 100644
--- a/components/data_sharing/test_support/android/java/src/org/chromium/components/data_sharing/TestDataSharingService.java
+++ b/components/data_sharing/test_support/android/java/src/org/chromium/components/data_sharing/TestDataSharingService.java
@@ -119,4 +119,9 @@
     public DataSharingUIDelegate getUIDelegate() {
         return null;
     }
+
+    @Override
+    public ServiceStatus getServiceStatus() {
+        return null;
+    }
 }
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
index 2d7951e..8f544cb 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
@@ -27,6 +27,9 @@
     FeatureConstants.AUTO_DARK_USER_EDUCATION_MESSAGE_OPT_IN_FEATURE,
     FeatureConstants.CCT_HISTORY_FEATURE,
     FeatureConstants.CCT_MINIMIZED_FEATURE,
+    FeatureConstants.DEFAULT_BROWSER_PROMO_MAGIC_STACK,
+    FeatureConstants.DEFAULT_BROWSER_PROMO_MESSAGES,
+    FeatureConstants.DEFAULT_BROWSER_PROMO_SETTING_CARD,
     FeatureConstants.DOWNLOAD_PAGE_FEATURE,
     FeatureConstants.DOWNLOAD_PAGE_SCREENSHOT_FEATURE,
     FeatureConstants.DOWNLOAD_HOME_FEATURE,
@@ -131,6 +134,9 @@
     String CONTEXTUAL_PAGE_ACTIONS_QUIET_VARIANT = "IPH_ContextualPageActions_QuietVariant";
     String CONTEXTUAL_PAGE_ACTIONS_ACTION_CHIP = "IPH_ContextualPageActions_ActionChip";
     String COOKIE_CONTROLS_FEATURE = "IPH_CookieControls";
+    String DEFAULT_BROWSER_PROMO_MAGIC_STACK = "IPH_DefaultBrowserPromoMagicStack";
+    String DEFAULT_BROWSER_PROMO_MESSAGES = "IPH_DefaultBrowserPromoMessages";
+    String DEFAULT_BROWSER_PROMO_SETTING_CARD = "IPH_DefaultBrowserPromoSettingCard";
     String DOWNLOAD_PAGE_FEATURE = "IPH_DownloadPage";
     String DOWNLOAD_PAGE_SCREENSHOT_FEATURE = "IPH_DownloadPageScreenshot";
     String DOWNLOAD_HOME_FEATURE = "IPH_DownloadHome";
diff --git a/components/feature_engagement/public/feature_configurations.cc b/components/feature_engagement/public/feature_configurations.cc
index e8e80461..7d33686 100644
--- a/components/feature_engagement/public/feature_configurations.cc
+++ b/components/feature_engagement/public/feature_configurations.cc
@@ -9,6 +9,7 @@
 #include "components/feature_engagement/public/configuration.h"
 #include "components/feature_engagement/public/event_constants.h"
 #include "components/feature_engagement/public/feature_constants.h"
+#include "components/feature_engagement/public/group_constants.h"
 
 #if BUILDFLAG(IS_IOS)
 #include "base/metrics/field_trial_params.h"
@@ -1594,6 +1595,63 @@
 
     return config;
   }
+
+  if (kIPHDefaultBrowserPromoMagicStackFeature.name == feature->name) {
+    // A config that allows the default browser promo Magic Stack to be shown:
+    // * Up to 3 times
+    // * Neither Magic Stack, Messages, nor the settings card has been triggered
+    // in the last 7 days.
+    std::optional<FeatureConfig> config = FeatureConfig();
+    config->valid = true;
+    config->availability = Comparator(ANY, 0);
+    config->trigger =
+        EventConfig("default_browser_promo_magic_stack_trigger",
+                    Comparator(LESS_THAN, 3), k10YearsInDays, k10YearsInDays);
+    config->groups.push_back(kClankDefaultBrowserPromosGroup.name);
+
+    return config;
+  }
+
+  if (kIPHDefaultBrowserPromoMessagesFeature.name == feature->name) {
+    // A config that allows the default browser promo messages to be shown:
+    // * Up to 2 times
+    // * Neither Magic Stack, Messages, nor the settings card has been triggered
+    // in the last 7 days.
+    std::optional<FeatureConfig> config = FeatureConfig();
+    config->valid = true;
+    config->availability = Comparator(ANY, 0);
+    config->trigger =
+        EventConfig("default_browser_promo_messages_trigger",
+                    Comparator(LESS_THAN, 2), k10YearsInDays, k10YearsInDays);
+    config->used = EventConfig("default_browser_promo_messages_used",
+                               Comparator(ANY, 0), 90, 90);
+    config->event_configs.insert(
+        EventConfig("default_browser_promo_messages_dismissed",
+                    Comparator(EQUAL, 0), k10YearsInDays, k10YearsInDays));
+    config->groups.push_back(kClankDefaultBrowserPromosGroup.name);
+    return config;
+  }
+
+  if (kIPHDefaultBrowserPromoSettingCardFeature.name == feature->name) {
+    // A config that allows the default browser promo setting card to be shown:
+    // * Up to 4 times
+    // * Neither Magic Stack, Messages, nor the settings card has been triggered
+    // in the last 7 days.
+    std::optional<FeatureConfig> config = FeatureConfig();
+    config->valid = true;
+    config->availability = Comparator(ANY, 0);
+    config->trigger =
+        EventConfig("default_browser_promo_setting_card_trigger",
+                    Comparator(LESS_THAN, 4), k10YearsInDays, k10YearsInDays);
+    config->used = EventConfig("default_browser_promo_setting_card_used",
+                               Comparator(ANY, 0), 90, 90);
+    config->event_configs.insert(
+        EventConfig("default_browser_promo_setting_card_dismissed",
+                    Comparator(EQUAL, 0), k10YearsInDays, k10YearsInDays));
+    config->groups.push_back(kClankDefaultBrowserPromosGroup.name);
+
+    return config;
+  }
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) || \
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index e3fdf7e0..616bab1 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -255,6 +255,15 @@
 BASE_FEATURE(kIPHDataSaverPreviewFeature,
              "IPH_DataSaverPreview",
              base::FEATURE_ENABLED_BY_DEFAULT);
+BASE_FEATURE(kIPHDefaultBrowserPromoMagicStackFeature,
+             "IPH_DefaultBrowserPromoMagicStack",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kIPHDefaultBrowserPromoMessagesFeature,
+             "IPH_DefaultBrowserPromoMessages",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kIPHDefaultBrowserPromoSettingCardFeature,
+             "IPH_DefaultBrowserPromoSettingCard",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 BASE_FEATURE(kIPHDownloadHomeFeature,
              "IPH_DownloadHome",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index 4e47b5f..2b971b7 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -112,6 +112,9 @@
 BASE_DECLARE_FEATURE(kIPHDataSaverDetailFeature);
 BASE_DECLARE_FEATURE(kIPHDataSaverMilestonePromoFeature);
 BASE_DECLARE_FEATURE(kIPHDataSaverPreviewFeature);
+BASE_DECLARE_FEATURE(kIPHDefaultBrowserPromoMagicStackFeature);
+BASE_DECLARE_FEATURE(kIPHDefaultBrowserPromoMessagesFeature);
+BASE_DECLARE_FEATURE(kIPHDefaultBrowserPromoSettingCardFeature);
 BASE_DECLARE_FEATURE(kIPHDownloadHomeFeature);
 BASE_DECLARE_FEATURE(kIPHDownloadIndicatorFeature);
 BASE_DECLARE_FEATURE(kIPHDownloadPageFeature);
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index 92e42bb2..8a948ac5 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -37,6 +37,9 @@
     &kIPHDataSaverDetailFeature,
     &kIPHDataSaverMilestonePromoFeature,
     &kIPHDataSaverPreviewFeature,
+    &kIPHDefaultBrowserPromoMagicStackFeature,
+    &kIPHDefaultBrowserPromoMessagesFeature,
+    &kIPHDefaultBrowserPromoSettingCardFeature,
     &kIPHDownloadHomeFeature,
     &kIPHDownloadIndicatorFeature,
     &kIPHDownloadPageFeature,
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index e9f4ed80..0d6d0dac 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -94,6 +94,12 @@
                        "IPH_ChromeReengagementNotification2");
 DEFINE_VARIATION_PARAM(kIPHChromeReengagementNotification3Feature,
                        "IPH_ChromeReengagementNotification3");
+DEFINE_VARIATION_PARAM(kIPHDefaultBrowserPromoMagicStackFeature,
+                       "IPH_DefaultBrowserPromoMagicStack");
+DEFINE_VARIATION_PARAM(kIPHDefaultBrowserPromoMessagesFeature,
+                       "IPH_DefaultBrowserPromoMessages");
+DEFINE_VARIATION_PARAM(kIPHDefaultBrowserPromoSettingCardFeature,
+                       "IPH_DefaultBrowserPromoSettingCard");
 DEFINE_VARIATION_PARAM(kIPHDownloadSettingsFeature, "IPH_DownloadSettings");
 DEFINE_VARIATION_PARAM(kIPHDownloadInfoBarDownloadContinuingFeature,
                        "IPH_DownloadInfoBarDownloadContinuing");
@@ -532,6 +538,9 @@
         VARIATION_ENTRY(kIPHChromeReengagementNotification1Feature),
         VARIATION_ENTRY(kIPHChromeReengagementNotification2Feature),
         VARIATION_ENTRY(kIPHChromeReengagementNotification3Feature),
+        VARIATION_ENTRY(kIPHDefaultBrowserPromoMagicStackFeature),
+        VARIATION_ENTRY(kIPHDefaultBrowserPromoMessagesFeature),
+        VARIATION_ENTRY(kIPHDefaultBrowserPromoSettingCardFeature),
         VARIATION_ENTRY(kIPHDownloadSettingsFeature),
         VARIATION_ENTRY(kIPHDownloadInfoBarDownloadContinuingFeature),
         VARIATION_ENTRY(kIPHDownloadInfoBarDownloadsAreFasterFeature),
diff --git a/components/feature_engagement/public/group_configurations.cc b/components/feature_engagement/public/group_configurations.cc
index 4307a26..b3094c4 100644
--- a/components/feature_engagement/public/group_configurations.cc
+++ b/components/feature_engagement/public/group_configurations.cc
@@ -66,6 +66,19 @@
   }
 #endif  // BUILDFLAG(IS_IOS)
 
+#if BUILDFLAG(IS_ANDROID)
+  if (kClankDefaultBrowserPromosGroup.name == group->name) {
+    // Default browser promos in this groups can only be shown once every seven
+    // days.
+    std::optional<GroupConfig> config = GroupConfig();
+    config->valid = true;
+    config->session_rate = Comparator(EQUAL, 0);
+    config->trigger = EventConfig("default_browser_promos_group_trigger",
+                                  Comparator(EQUAL, 0), 7, kMaxStoragePeriod);
+    return config;
+  }
+#endif  // BUILDFLAG(IS_ANDROID)
+
   if (kIPHDummyGroup.name == group->name) {
     // Only used for tests. Various magic tricks are used below to ensure this
     // config is invalid and unusable.
diff --git a/components/feature_engagement/public/group_constants.cc b/components/feature_engagement/public/group_constants.cc
index 7f0554d7..8a42f57d 100644
--- a/components/feature_engagement/public/group_constants.cc
+++ b/components/feature_engagement/public/group_constants.cc
@@ -27,5 +27,10 @@
              "IPH_iOSTailoredDefaultBrowserPromosGroup",
              base::FEATURE_ENABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_IOS)
+#if BUILDFLAG(IS_ANDROID)
+BASE_FEATURE(kClankDefaultBrowserPromosGroup,
+             "IPH_ClankDefaultBrowserPromosGroup",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+#endif  // BUILDFLAG(IS_ANDROID)
 
 }  // namespace feature_engagement
diff --git a/components/feature_engagement/public/group_constants.h b/components/feature_engagement/public/group_constants.h
index 4092407..a3b67e86 100644
--- a/components/feature_engagement/public/group_constants.h
+++ b/components/feature_engagement/public/group_constants.h
@@ -18,6 +18,9 @@
 BASE_DECLARE_FEATURE(kiOSDefaultBrowserPromosGroup);
 BASE_DECLARE_FEATURE(kiOSTailoredDefaultBrowserPromosGroup);
 #endif  // BUILDFLAG(IS_IOS)
+#if BUILDFLAG(IS_ANDROID)
+BASE_DECLARE_FEATURE(kClankDefaultBrowserPromosGroup);
+#endif  // BUILDFLAG(IS_ANDROID)
 
 }  // namespace feature_engagement
 
diff --git a/components/feature_engagement/public/group_list.cc b/components/feature_engagement/public/group_list.cc
index 78f4cd57..94af5c7b 100644
--- a/components/feature_engagement/public/group_list.cc
+++ b/components/feature_engagement/public/group_list.cc
@@ -22,6 +22,9 @@
     &kiOSDefaultBrowserPromosGroup,
     &kiOSTailoredDefaultBrowserPromosGroup,
 #endif  // BUILDFLAG(IS_IOS)
+#if BUILDFLAG(IS_ANDROID)
+    &kClankDefaultBrowserPromosGroup,
+#endif  // BUILDFLAG(IS_ANDROID)
 };
 }  // namespace
 
diff --git a/components/optimization_guide/core/hints_processing_util.cc b/components/optimization_guide/core/hints_processing_util.cc
index 528d44c9..f3f4b2dc 100644
--- a/components/optimization_guide/core/hints_processing_util.cc
+++ b/components/optimization_guide/core/hints_processing_util.cc
@@ -141,6 +141,8 @@
       return "BuyNowPayLaterAllowlistAffirm";
     case proto::OptimizationType::BUY_NOW_PAY_LATER_ALLOWLIST_ZIP:
       return "BuyNowPayLaterAllowlistZip";
+    case proto::OptimizationType::SAVED_TAB_GROUP:
+      return "SavedTabGroup";
   }
 
   // The returned string is used to record histograms for the optimization type.
diff --git a/components/optimization_guide/proto/hints.proto b/components/optimization_guide/proto/hints.proto
index 7c7608a6..dff348b 100644
--- a/components/optimization_guide/proto/hints.proto
+++ b/components/optimization_guide/proto/hints.proto
@@ -285,6 +285,9 @@
   // This optimization helps determine if this merchant page is a good candidate
   // for enabling buy now pay later flow with Zip.
   BUY_NOW_PAY_LATER_ALLOWLIST_ZIP = 70;
+  // Provides information about whether a URL can be synced across devices for
+  // saved tab groups feature.
+  SAVED_TAB_GROUP = 71;
 }
 
 // Presents semantics for how page load URLs should be matched.
diff --git a/components/safe_browsing/content/browser/browser_url_loader_throttle_unittest.cc b/components/safe_browsing/content/browser/browser_url_loader_throttle_unittest.cc
index 6cefc9a5..c2cd423 100644
--- a/components/safe_browsing/content/browser/browser_url_loader_throttle_unittest.cc
+++ b/components/safe_browsing/content/browser/browser_url_loader_throttle_unittest.cc
@@ -143,26 +143,28 @@
       base::WeakPtr<HashRealTimeService> hash_realtime_service_on_ui,
       hash_realtime_utils::HashRealTimeSelection hash_realtime_selection,
       bool is_async_check)
-      : SafeBrowsingUrlCheckerImpl(headers,
-                                   load_flags,
-                                   has_user_gesture,
-                                   url_checker_delegate,
-                                   web_contents_getter,
-                                   /*weak_web_state=*/nullptr,
-                                   render_process_id,
-                                   render_frame_token,
-                                   frame_tree_node_id.value(),
-                                   navigation_id,
-                                   url_real_time_lookup_enabled,
-                                   can_check_db,
-                                   can_check_high_confidence_allowlist,
-                                   url_lookup_service_metric_suffix,
-                                   ui_task_runner,
-                                   url_lookup_service_on_ui,
-                                   hash_realtime_service_on_ui,
-                                   hash_realtime_selection,
-                                   is_async_check,
-                                   SessionID::InvalidValue()) {}
+      : SafeBrowsingUrlCheckerImpl(
+            headers,
+            load_flags,
+            has_user_gesture,
+            url_checker_delegate,
+            web_contents_getter,
+            /*weak_web_state=*/nullptr,
+            render_process_id,
+            render_frame_token,
+            frame_tree_node_id.value(),
+            navigation_id,
+            url_real_time_lookup_enabled,
+            can_check_db,
+            can_check_high_confidence_allowlist,
+            url_lookup_service_metric_suffix,
+            ui_task_runner,
+            url_lookup_service_on_ui,
+            hash_realtime_service_on_ui,
+            hash_realtime_selection,
+            is_async_check,
+            /*check_allowlist_before_hash_database=*/false,
+            SessionID::InvalidValue()) {}
 
   // Returns the CallbackInfo that was previously added in |AddCallbackInfo|.
   // It will crash if |AddCallbackInfo| was not called.
diff --git a/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc b/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc
index a9fde12..eb39e6f2 100644
--- a/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc
+++ b/components/safe_browsing/content/browser/mojo_safe_browsing_impl.cc
@@ -145,7 +145,8 @@
       /*hash_realtime_service_on_ui=*/nullptr,
       /*hash_realtime_selection=*/
       hash_realtime_utils::HashRealTimeSelection::kNone,
-      /*is_async_check=*/false, SessionID::InvalidValue());
+      /*is_async_check=*/false, /*check_allowlist_before_hash_database=*/false,
+      SessionID::InvalidValue());
   auto weak_impl = checker_impl->WeakPtr();
 
   checker_impl->CheckUrl(url, method,
diff --git a/components/safe_browsing/content/browser/url_checker_holder.cc b/components/safe_browsing/content/browser/url_checker_holder.cc
index 742d5de..d4c9b29 100644
--- a/components/safe_browsing/content/browser/url_checker_holder.cc
+++ b/components/safe_browsing/content/browser/url_checker_holder.cc
@@ -97,6 +97,8 @@
   if (url_checker_for_testing_) {
     url_checker_ = std::move(url_checker_for_testing_);
   } else {
+    // TODO(crbug.com/347890373): Set check_allowlist_before_hash_database to
+    // true if this is the additional sync database check.
     url_checker_ = std::make_unique<SafeBrowsingUrlCheckerImpl>(
         params.headers, params.load_flags, params.has_user_gesture,
         url_checker_delegate, web_contents_getter_, nullptr,
@@ -106,7 +108,7 @@
         can_check_high_confidence_allowlist_, url_lookup_service_metric_suffix_,
         content::GetUIThreadTaskRunner({}), url_lookup_service_,
         hash_realtime_service_, hash_realtime_selection_, is_async_check_,
-        tab_id_);
+        /*check_allowlist_before_hash_database=*/false, tab_id_);
   }
 
   CheckUrl(params.url, params.method);
diff --git a/components/safe_browsing/core/browser/database_manager_mechanism.cc b/components/safe_browsing/core/browser/database_manager_mechanism.cc
index e24259c..8ccf5c54 100644
--- a/components/safe_browsing/core/browser/database_manager_mechanism.cc
+++ b/components/safe_browsing/core/browser/database_manager_mechanism.cc
@@ -15,14 +15,16 @@
     const GURL& url,
     const SBThreatTypeSet& threat_types,
     scoped_refptr<SafeBrowsingDatabaseManager> database_manager,
-    CheckBrowseUrlType check_type)
+    CheckBrowseUrlType check_type,
+    bool check_allowlist)
     : SafeBrowsingLookupMechanism(url, threat_types, database_manager),
+      check_allowlist_(check_allowlist),
       check_type_(check_type) {}
 
 DatabaseManagerMechanism::~DatabaseManagerMechanism() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (is_async_database_manager_check_in_progress_) {
+  if (is_async_blocklist_check_in_progress_) {
     database_manager_->CancelCheck(this);
   }
 }
@@ -34,10 +36,45 @@
 SafeBrowsingLookupMechanism::StartCheckResult
 DatabaseManagerMechanism::StartCheckInternal() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!check_allowlist_) {
+    return StartBlocklistCheck();
+  }
+
+  database_manager_->CheckUrlForHighConfidenceAllowlist(
+      url_, base::BindOnce(
+                &DatabaseManagerMechanism::OnCheckUrlForHighConfidenceAllowlist,
+                weak_factory_.GetWeakPtr()));
+  return StartCheckResult(
+      /*is_safe_synchronously=*/false, /*threat_source=*/std::nullopt);
+}
+
+void DatabaseManagerMechanism::OnCheckUrlForHighConfidenceAllowlist(
+    bool did_match_allowlist) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK(check_allowlist_);
+
+  if (did_match_allowlist) {
+    CompleteCheck(std::make_unique<CompleteCheckResult>(
+        url_, SBThreatType::SB_THREAT_TYPE_SAFE, ThreatMetadata(),
+        /*threat_source=*/std::nullopt,
+        /*url_real_time_lookup_response=*/nullptr));
+    // NOTE: Calling CompleteCheck results in the synchronous destruction of
+    // this object, so there is nothing safe to do here but return.
+    return;
+  } else {
+    StartBlocklistCheck();
+  }
+}
+
+SafeBrowsingLookupMechanism::StartCheckResult
+DatabaseManagerMechanism::StartBlocklistCheck() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   bool is_safe_synchronously =
       database_manager_->CheckBrowseUrl(url_, threat_types_, this, check_type_);
   if (!is_safe_synchronously) {
-    is_async_database_manager_check_in_progress_ = true;
+    is_async_blocklist_check_in_progress_ = true;
   }
   return StartCheckResult(is_safe_synchronously, GetThreatSource());
 }
@@ -47,7 +84,7 @@
     SBThreatType threat_type,
     const ThreatMetadata& metadata) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  is_async_database_manager_check_in_progress_ = false;
+  is_async_blocklist_check_in_progress_ = false;
   CompleteCheck(std::make_unique<CompleteCheckResult>(
       url, threat_type, metadata, GetThreatSource(),
       /*url_real_time_lookup_response=*/nullptr));
diff --git a/components/safe_browsing/core/browser/database_manager_mechanism.h b/components/safe_browsing/core/browser/database_manager_mechanism.h
index 64e77f01..c192fc8 100644
--- a/components/safe_browsing/core/browser/database_manager_mechanism.h
+++ b/components/safe_browsing/core/browser/database_manager_mechanism.h
@@ -21,7 +21,8 @@
       const GURL& url,
       const SBThreatTypeSet& threat_types,
       scoped_refptr<SafeBrowsingDatabaseManager> database_manager,
-      CheckBrowseUrlType check_type);
+      CheckBrowseUrlType check_type,
+      bool check_allowlist);
 
   DatabaseManagerMechanism(const DatabaseManagerMechanism&) = delete;
   DatabaseManagerMechanism& operator=(const DatabaseManagerMechanism&) = delete;
@@ -29,10 +30,17 @@
 
  private:
   // SafeBrowsingLookupMechanism implementation:
-  // Calls |CheckBrowseUrl| on the database manager and sets
-  // is_async_database_manager_check_in_progress_ if the check is asynchronous.
   StartCheckResult StartCheckInternal() override;
 
+  // Callback from the allowlist check. If |did_match_allowlist| is true, this
+  // will skip the blocklist check. Should only be called if `check_allowlist_`
+  // is true.
+  void OnCheckUrlForHighConfidenceAllowlist(bool did_match_allowlist);
+
+  // Calls |CheckBrowseUrl| on the database manager and sets
+  // is_async_blocklist_check_in_progress_ if the check is asynchronous.
+  StartCheckResult StartBlocklistCheck();
+
   // SafeBrowsingDatabaseManager::Client implementation:
   void OnCheckBrowseUrlResult(const GURL& url,
                               SBThreatType threat_type,
@@ -42,14 +50,23 @@
 
   SEQUENCE_CHECKER(sequence_checker_);
 
-  // Tracks whether there is currently an async call into the database manager
-  // that the checker is waiting to hear back on. This is used in order to
-  // decide whether to ask the database manager to cancel the check on destruct.
-  bool is_async_database_manager_check_in_progress_ = false;
+  // If the allowlist should be checked first before checking the blocklist.
+  const bool check_allowlist_;
+
+  // Tracks whether there is currently an async blocklist check call into the
+  // database manager that the checker is waiting to hear back on. This is used
+  // in order to decide whether to ask the database manager to cancel the check
+  // on destruct.
+  // We don't need to check the progress of allowlist check, because
+  // `database_manager_` doesn't require explicit cancelling if the callback is
+  // not handled by SafeBrowsingDatabaseManager::Client.
+  bool is_async_blocklist_check_in_progress_ = false;
 
   // The type of check that is passed into |CheckBrowseUrl| on the
   // database manager.
   CheckBrowseUrlType check_type_;
+
+  base::WeakPtrFactory<DatabaseManagerMechanism> weak_factory_{this};
 };
 
 }  // namespace safe_browsing
diff --git a/components/safe_browsing/core/browser/hash_realtime_mechanism.cc b/components/safe_browsing/core/browser/hash_realtime_mechanism.cc
index d34f3f06..c5debf2 100644
--- a/components/safe_browsing/core/browser/hash_realtime_mechanism.cc
+++ b/components/safe_browsing/core/browser/hash_realtime_mechanism.cc
@@ -129,7 +129,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   hash_database_mechanism_ = std::make_unique<DatabaseManagerMechanism>(
-      url, threat_types_, database_manager_, CheckBrowseUrlType::kHashDatabase);
+      url, threat_types_, database_manager_, CheckBrowseUrlType::kHashDatabase,
+      /*check_allowlist=*/false);
   auto result = hash_database_mechanism_->StartCheck(
       base::BindOnce(&HashRealTimeMechanism::OnHashDatabaseCompleteCheckResult,
                      weak_factory_.GetWeakPtr(), fallback_trigger));
diff --git a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc
index 2fec396..1cea949 100644
--- a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc
+++ b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.cc
@@ -194,6 +194,7 @@
     base::WeakPtr<HashRealTimeService> hash_realtime_service_on_ui,
     HashRealTimeSelection hash_realtime_selection,
     bool is_async_check,
+    bool check_allowlist_before_hash_database,
     SessionID tab_id)
     : headers_(headers),
       load_flags_(load_flags),
@@ -215,6 +216,8 @@
       hash_realtime_service_on_ui_(hash_realtime_service_on_ui),
       hash_realtime_selection_(hash_realtime_selection),
       is_async_check_(is_async_check),
+      check_allowlist_before_hash_database_(
+          check_allowlist_before_hash_database),
       tab_id_(tab_id) {
   DCHECK(url_real_time_lookup_enabled_ || can_check_db_);
 
@@ -506,7 +509,8 @@
   }
   return std::make_unique<DatabaseManagerMechanism>(
       url, url_checker_delegate_->GetThreatTypes(), database_manager_,
-      CheckBrowseUrlType::kHashRealTime);
+      CheckBrowseUrlType::kHashRealTime,
+      /*check_allowlist=*/false);
 }
 
 SafeBrowsingUrlCheckerImpl::KickOffLookupMechanismResult
@@ -559,12 +563,14 @@
     performed_check = PerformedCheck::kHashRealTimeCheck;
     lookup_mechanism = std::make_unique<DatabaseManagerMechanism>(
         url, url_checker_delegate_->GetThreatTypes(), database_manager_,
-        CheckBrowseUrlType::kHashRealTime);
+        CheckBrowseUrlType::kHashRealTime,
+        /*check_allowlist=*/false);
   } else {
     performed_check = PerformedCheck::kHashDatabaseCheck;
     lookup_mechanism = std::make_unique<DatabaseManagerMechanism>(
         url, url_checker_delegate_->GetThreatTypes(), database_manager_,
-        CheckBrowseUrlType::kHashDatabase);
+        CheckBrowseUrlType::kHashDatabase,
+        /*check_allowlist=*/check_allowlist_before_hash_database_);
   }
   DCHECK(performed_check != PerformedCheck::kUnknown);
   lookup_mechanism_runner_ =
diff --git a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h
index 8b2bcd3..cba3472 100644
--- a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h
+++ b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h
@@ -112,6 +112,7 @@
       base::WeakPtr<HashRealTimeService> hash_realtime_service_on_ui,
       hash_realtime_utils::HashRealTimeSelection hash_realtime_selection,
       bool is_async_check,
+      bool check_allowlist_before_hash_database,
       SessionID tab_id);
 
   SafeBrowsingUrlCheckerImpl(const SafeBrowsingUrlCheckerImpl&) = delete;
@@ -335,6 +336,13 @@
   // If this check allows navigation to commit before it completes.
   const bool is_async_check_;
 
+  // If the allowlist should be checked before the hash database check. This
+  // is useful to speed up the check if the allowlist check is faster than the
+  // hash database check. Callers should only set this value to true if they
+  // fully trust the correctness of the allowlist or there are other mitigations
+  // in place when blocklisted URLs are mistakenly added in the allowlist.
+  bool check_allowlist_before_hash_database_ = false;
+
   // The current tab ID. Used sometimes for identifying the referrer chain for
   // URL real-time lookups. Can be |SessionID::InvalidValue()|.
   SessionID tab_id_;
diff --git a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl_unittest.cc b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl_unittest.cc
index 0d60ecac..1eb10b3 100644
--- a/components/safe_browsing/core/browser/safe_browsing_url_checker_impl_unittest.cc
+++ b/components/safe_browsing/core/browser/safe_browsing_url_checker_impl_unittest.cc
@@ -353,6 +353,7 @@
 
 struct CreateSafeBrowsingUrlCheckerOptionalArgs {
   std::string url_lookup_service_metric_suffix = ".Enterprise";
+  bool check_allowlist_before_hash_database = false;
 };
 
 }  // namespace
@@ -392,7 +393,9 @@
                                      : nullptr,
         /*hash_realtime_service=*/hash_realtime_service_->GetWeakPtr(),
         hash_real_time_selection,
-        /*is_async_check=*/false, SessionID::InvalidValue());
+        /*is_async_check=*/false,
+        optional_args.check_allowlist_before_hash_database,
+        SessionID::InvalidValue());
   }
 
  protected:
@@ -615,6 +618,73 @@
                                    /*expected_hpd_log_count=*/1);
 }
 
+TEST_F(SafeBrowsingUrlCheckerTest,
+       CheckUrl_CheckAllowlistBeforeHashDatabaseCheck) {
+  auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker(
+      /*url_real_time_lookup_enabled=*/false,
+      /*can_check_safe_browsing_db=*/true,
+      /*hash_real_time_selection=*/
+      hash_realtime_utils::HashRealTimeSelection::kNone,
+      /*optional_args=*/{.check_allowlist_before_hash_database = true});
+
+  GURL url("https://example.test/");
+  database_manager_->SetAllowlistLookupDetailsForUrl(
+      url, /*match=*/true,
+      /*logging_details=*/std::nullopt);
+  // Although we mark the URL as phishing in the hash database, the callback
+  // should return safe because it is on the allowlist.
+  database_manager_->SetThreatTypeForUrl(url, SB_THREAT_TYPE_URL_PHISHING,
+                                         /*delayed_callback=*/false);
+
+  base::MockCallback<SafeBrowsingUrlCheckerImpl::NativeCheckUrlCallback>
+      callback;
+  EXPECT_CALL(
+      callback,
+      Run(/*proceed=*/true, /*showed_interstitial=*/false,
+          /*has_post_commit_interstitial_skipped=*/false,
+          SafeBrowsingUrlCheckerImpl::PerformedCheck::kHashDatabaseCheck));
+
+  safe_browsing_url_checker->CheckUrl(url, "GET", callback.Get());
+  task_environment_.RunUntilIdle();
+  ValidateCheckUrlTimeTakenMetrics(/*expected_hprt_log_count=*/0,
+                                   /*expected_urt_log_count=*/0,
+                                   /*expected_hpd_log_count=*/1);
+}
+
+TEST_F(SafeBrowsingUrlCheckerTest,
+       CheckUrl_NotCheckAllowlistBeforeHashDatabaseCheck) {
+  auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker(
+      /*url_real_time_lookup_enabled=*/false,
+      /*can_check_safe_browsing_db=*/true,
+      /*hash_real_time_selection=*/
+      hash_realtime_utils::HashRealTimeSelection::kNone,
+      /*optional_args=*/{.check_allowlist_before_hash_database = false});
+
+  GURL url("https://example.test/");
+  // Although we mark the URL matching the allowlist, the URL should still be
+  // flagged by the hash database check because
+  // check_allowlist_before_hash_database is set to false.
+  database_manager_->SetAllowlistLookupDetailsForUrl(
+      url, /*match=*/true,
+      /*logging_details=*/std::nullopt);
+  database_manager_->SetThreatTypeForUrl(url, SB_THREAT_TYPE_URL_PHISHING,
+                                         /*delayed_callback=*/false);
+
+  base::MockCallback<SafeBrowsingUrlCheckerImpl::NativeCheckUrlCallback>
+      callback;
+  EXPECT_CALL(callback, Run(_, _, _, _)).Times(0);
+  EXPECT_CALL(*url_checker_delegate_,
+              StartDisplayingBlockingPageHelper(
+                  IsSameThreatSource(ThreatSource::UNKNOWN), _, _, _))
+      .Times(1);
+
+  safe_browsing_url_checker->CheckUrl(url, "GET", callback.Get());
+  task_environment_.RunUntilIdle();
+  ValidateCheckUrlTimeTakenMetrics(/*expected_hprt_log_count=*/0,
+                                   /*expected_urt_log_count=*/0,
+                                   /*expected_hpd_log_count=*/1);
+}
+
 TEST_F(SafeBrowsingUrlCheckerTest, CheckUrl_UrlRealTimeEnabledAllowlistMatch) {
   auto safe_browsing_url_checker = CreateSafeBrowsingUrlChecker(
       /*url_real_time_lookup_enabled=*/true,
diff --git a/components/safe_browsing/core/browser/url_realtime_mechanism.cc b/components/safe_browsing/core/browser/url_realtime_mechanism.cc
index bd6ae8a..670634d 100644
--- a/components/safe_browsing/core/browser/url_realtime_mechanism.cc
+++ b/components/safe_browsing/core/browser/url_realtime_mechanism.cc
@@ -275,7 +275,8 @@
   if (can_check_db_) {
     hash_database_mechanism_ = std::make_unique<DatabaseManagerMechanism>(
         url, threat_types_, database_manager_,
-        CheckBrowseUrlType::kHashDatabase);
+        CheckBrowseUrlType::kHashDatabase,
+        /*check_allowlist=*/false);
     result = hash_database_mechanism_->StartCheck(
         base::BindOnce(&UrlRealTimeMechanism::OnHashDatabaseCompleteCheckResult,
                        weak_factory_.GetWeakPtr(), fallback_trigger));
diff --git a/components/viz/service/display/direct_renderer.h b/components/viz/service/display/direct_renderer.h
index b2a6f88..23ff8de3 100644
--- a/components/viz/service/display/direct_renderer.h
+++ b/components/viz/service/display/direct_renderer.h
@@ -97,7 +97,7 @@
   gfx::Rect GetTargetDamageBoundingRect() const;
 
   // Public interface implemented by subclasses.
-  struct SwapFrameData {
+  struct VIZ_SERVICE_EXPORT SwapFrameData {
     SwapFrameData();
     ~SwapFrameData();
 
diff --git a/components/viz/service/display/output_surface.cc b/components/viz/service/display/output_surface.cc
index abaaede..8db0271 100644
--- a/components/viz/service/display/output_surface.cc
+++ b/components/viz/service/display/output_surface.cc
@@ -77,4 +77,9 @@
   NOTREACHED_IN_MIGRATION();
 }
 
+void OutputSurface::ReadbackForTesting(
+    CopyOutputRequest::CopyOutputRequestCallback result_callback) {
+  NOTIMPLEMENTED();
+}
+
 }  // namespace viz
diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
index d8235317..f6b351a 100644
--- a/components/viz/service/display/output_surface.h
+++ b/components/viz/service/display/output_surface.h
@@ -11,6 +11,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/threading/thread_checker.h"
 #include "components/viz/common/display/update_vsync_parameters_callback.h"
+#include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "components/viz/common/resources/returned_resource.h"
 #include "components/viz/common/resources/shared_image_format.h"
 #include "components/viz/service/display/pending_swap_params.h"
@@ -272,6 +273,13 @@
       mojo::PendingReceiver<gfx::mojom::DelegatedInkPointRenderer>
           pending_receiver);
 
+  // Read back the contents of this output surface. This reads back the root
+  // render pass and does not affect rendering in the ways that a copy request
+  // might (e.g. damage, overlays, etc). This uses |CopyOutputRequestCallback|
+  // to be able to be used with code that consumes copy output responses.
+  virtual void ReadbackForTesting(
+      CopyOutputRequest::CopyOutputRequestCallback result_callback);
+
  protected:
   struct OutputSurface::Capabilities capabilities_;
 
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index 6d871f0..bd7cf3f 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -880,6 +880,59 @@
                                  cc::AlphaDiscardingExactPixelComparator()));
 }
 
+// Check that RendererPixelTest can run tests that verify incremental damage.
+TEST_P(RendererPixelTest, SimpleDamageRect) {
+  const gfx::Rect rect(this->device_viewport_size_);
+  const gfx::Rect damage_rect = gfx::Rect(20, 30, 40, 50);
+
+  const SkColor4f background_color = SkColors::kGreen;
+  const SkColor4f foreground_color = SkColors::kBlue;
+
+  std::vector<SkColor> expected_output_colors(rect.width() * rect.height());
+  for (int y = 0; y < rect.height(); y++) {
+    for (int x = 0; x < rect.width(); x++) {
+      expected_output_colors[y * rect.width() + x] =
+          damage_rect.Contains(x, y) ? foreground_color.toSkColor()
+                                     : background_color.toSkColor();
+    }
+  }
+
+  // Draw two frames with semi-transparent content. Both frames should result in
+  // the same image.
+  for (size_t i = 0; i < 2; i++) {
+    SCOPED_TRACE(base::StringPrintf("Frame %zu", i));
+
+    auto pass = CreateTestRootRenderPass(AggregatedRenderPassId{1}, rect);
+
+    if (i != 0) {
+      pass->damage_rect = damage_rect;
+    }
+
+    SharedQuadState* shared_state = CreateTestSharedQuadState(
+        gfx::Transform(), rect, pass.get(), gfx::MaskFilterInfo());
+
+    auto* foreground_quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
+    foreground_quad->SetNew(shared_state, damage_rect, damage_rect,
+                            foreground_color, false);
+
+    // Only add the background in the first frame. If the renderer forces full
+    // damage for all frames, the second frame will not contain the background
+    // color from the first frame.
+    if (i == 0) {
+      auto* background_quad =
+          pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
+      background_quad->SetNew(shared_state, rect, rect, background_color,
+                              false);
+    }
+
+    AggregatedRenderPassList pass_list;
+    pass_list.push_back(std::move(pass));
+
+    EXPECT_TRUE(this->RunPixelTest(&pass_list, &expected_output_colors,
+                                   cc::AlphaDiscardingExactPixelComparator()));
+  }
+}
+
 TEST_P(RendererPixelTest, OutputSurfaceClipRect) {
   gfx::Rect rect(device_viewport_size_);
 
@@ -938,7 +991,7 @@
   pass_list.push_back(std::move(child_pass));
   pass_list.push_back(std::move(root_pass));
 
-  EXPECT_TRUE(this->RunPixelTestWithReadbackTarget(
+  EXPECT_TRUE(this->RunPixelTestWithCopyOutputRequest(
       &pass_list, child_pass_ptr,
       base::FilePath(FILE_PATH_LITERAL("green_small.png")),
       cc::AlphaDiscardingExactPixelComparator()));
@@ -5027,7 +5080,7 @@
   pass_list.push_back(std::move(root_pass));
 
   // Check that the child pass remains unflipped.
-  EXPECT_TRUE(this->RunPixelTestWithReadbackTarget(
+  EXPECT_TRUE(this->RunPixelTestWithCopyOutputRequest(
       &pass_list, pass_list.front().get(),
       base::FilePath(FILE_PATH_LITERAL("blue_yellow.png")),
       cc::AlphaDiscardingExactPixelComparator()));
@@ -5076,7 +5129,7 @@
                          this->device_viewport_size_.height() / 2,
                          this->device_viewport_size_.width() / 2,
                          this->device_viewport_size_.height() / 2);
-  EXPECT_TRUE(this->RunPixelTestWithReadbackTargetAndArea(
+  EXPECT_TRUE(this->RunPixelTestWithCopyOutputRequestAndArea(
       &pass_list, pass_list.front().get(),
       base::FilePath(FILE_PATH_LITERAL("green_small_with_blue_corner.png")),
       cc::AlphaDiscardingExactPixelComparator(), &capture_rect));
@@ -6496,7 +6549,7 @@
 
   // This will only check what was drawn in the child pass, which should never
   // contain a delegated ink trail, so it should be solid green.
-  EXPECT_TRUE(this->RunPixelTestWithReadbackTarget(
+  EXPECT_TRUE(this->RunPixelTestWithCopyOutputRequest(
       &pass_list, child_pass_ptr,
       base::FilePath(FILE_PATH_LITERAL("green.png")),
       cc::AlphaDiscardingExactPixelComparator()));
diff --git a/components/viz/service/display/software_output_device.cc b/components/viz/service/display/software_output_device.cc
index fb074695..bac6593 100644
--- a/components/viz/service/display/software_output_device.cc
+++ b/components/viz/service/display/software_output_device.cc
@@ -10,7 +10,9 @@
 #include "base/functional/bind.h"
 #include "base/task/sequenced_task_runner.h"
 #include "skia/ext/legacy_display_globals.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColorSpace.h"
 #include "ui/gfx/vsync_provider.h"
 
 namespace viz {
@@ -68,4 +70,12 @@
   return false;
 }
 
+SkBitmap SoftwareOutputDevice::ReadbackForTesting() {
+  SkBitmap bitmap;
+  bitmap.allocPixels(
+      surface_->imageInfo().makeColorSpace(SkColorSpace::MakeSRGB()));
+  CHECK(surface_->readPixels(bitmap, 0, 0));
+  return bitmap;
+}
+
 }  // namespace viz
diff --git a/components/viz/service/display/software_output_device.h b/components/viz/service/display/software_output_device.h
index fb7f4a406..2e5f6986c 100644
--- a/components/viz/service/display/software_output_device.h
+++ b/components/viz/service/display/software_output_device.h
@@ -85,6 +85,9 @@
   // platform's proposed surface size.
   virtual bool SupportsOverridePlatformSize() const;
 
+  // Copy and return the contents of |surface_|.
+  SkBitmap ReadbackForTesting();
+
  protected:
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
   raw_ptr<SoftwareOutputDeviceClient> client_ = nullptr;
diff --git a/components/viz/service/display_embedder/skia_output_device.cc b/components/viz/service/display_embedder/skia_output_device.cc
index f6949ebe..a57aac31 100644
--- a/components/viz/service/display_embedder/skia_output_device.cc
+++ b/components/viz/service/display_embedder/skia_output_device.cc
@@ -153,6 +153,11 @@
   gpu_task_ready_ = task_ready;
 }
 
+void SkiaOutputDevice::ReadbackForTesting(
+    base::OnceCallback<void(SkBitmap)> callback) {
+  NOTIMPLEMENTED();
+}
+
 void SkiaOutputDevice::StartSwapBuffers(BufferPresentedCallback feedback) {
   DCHECK_LT(static_cast<int>(pending_swaps_.size()),
             capabilities_.pending_swap_params.GetMax());
diff --git a/components/viz/service/display_embedder/skia_output_device.h b/components/viz/service/display_embedder/skia_output_device.h
index b754e08b..4edbeddb 100644
--- a/components/viz/service/display_embedder/skia_output_device.h
+++ b/components/viz/service/display_embedder/skia_output_device.h
@@ -178,6 +178,11 @@
 
   void SetDependencyTimings(base::TimeTicks task_ready);
 
+  // Copy and return the contents of the surface owned by this device. If this
+  // output device is surfaceless, then reads back from the OS compositor tree,
+  // including non-protected overlays.
+  virtual void ReadbackForTesting(base::OnceCallback<void(SkBitmap)> callback);
+
  protected:
   // Only valid between StartSwapBuffers and FinishSwapBuffers.
   class SwapInfo {
diff --git a/components/viz/service/display_embedder/skia_output_device_offscreen.cc b/components/viz/service/display_embedder/skia_output_device_offscreen.cc
index 35fb572..117357b3d 100644
--- a/components/viz/service/display_embedder/skia_output_device_offscreen.cc
+++ b/components/viz/service/display_embedder/skia_output_device_offscreen.cc
@@ -4,8 +4,10 @@
 
 #include "components/viz/service/display_embedder/skia_output_device_offscreen.h"
 
+#include <memory>
 #include <utility>
 
+#include "base/check_is_test.h"
 #include "components/viz/common/resources/shared_image_format_utils.h"
 #include "gpu/command_buffer/service/service_utils.h"
 #include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
@@ -207,4 +209,54 @@
 
 void SkiaOutputDeviceOffscreen::EndPaint() {}
 
+void SkiaOutputDeviceOffscreen::ReadbackForTesting(
+    base::OnceCallback<void(SkBitmap)> callback) {
+  CHECK_IS_TEST();
+
+  struct ReadPixelsContext {
+    std::unique_ptr<const SkImage::AsyncReadResult> async_result;
+    bool finished = false;
+    static void OnReadPixelsDone(
+        void* raw_ctx,
+        std::unique_ptr<const SkImage::AsyncReadResult> async_result) {
+      ReadPixelsContext* context =
+          reinterpret_cast<ReadPixelsContext*>(raw_ctx);
+      context->async_result = std::move(async_result);
+      context->finished = true;
+    }
+  };
+
+  ReadPixelsContext context;
+  if (auto* graphite_context = context_state_->graphite_context()) {
+    graphite_context->asyncRescaleAndReadPixels(
+        sk_surface_.get(), sk_surface_->imageInfo(),
+        SkIRect::MakeSize(sk_surface_->imageInfo().dimensions()),
+        SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kRepeatedLinear,
+        &ReadPixelsContext::OnReadPixelsDone, &context);
+  } else {
+    CHECK(context_state_->gr_context());
+    sk_surface_->asyncRescaleAndReadPixels(
+        sk_surface_->imageInfo(),
+        SkIRect::MakeSize(sk_surface_->imageInfo().dimensions()),
+        SkImage::RescaleGamma::kSrc, SkImage::RescaleMode::kRepeatedLinear,
+        &ReadPixelsContext::OnReadPixelsDone, &context);
+  }
+
+  context_state_->FlushAndSubmit(true);
+  CHECK(context.finished);
+  CHECK(context.async_result);
+
+  CHECK_EQ(1, context.async_result->count());
+  const SkPixmap src_pixmap(sk_surface_->imageInfo(),
+                            const_cast<void*>(context.async_result->data(0)),
+                            context.async_result->rowBytes(0));
+
+  // Copy the pixels so we don't need to keep |context.async_result| alive.
+  SkBitmap bitmap;
+  bitmap.allocPixels(src_pixmap.info());
+  CHECK(bitmap.writePixels(src_pixmap));
+
+  std::move(callback).Run(std::move(bitmap));
+}
+
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_device_offscreen.h b/components/viz/service/display_embedder/skia_output_device_offscreen.h
index 6df9f74..6382382 100644
--- a/components/viz/service/display_embedder/skia_output_device_offscreen.h
+++ b/components/viz/service/display_embedder/skia_output_device_offscreen.h
@@ -40,6 +40,7 @@
   SkSurface* BeginPaint(
       std::vector<GrBackendSemaphore>* end_semaphores) override;
   void EndPaint() override;
+  void ReadbackForTesting(base::OnceCallback<void(SkBitmap)> callback) override;
 
  protected:
   scoped_refptr<gpu::SharedContextState> context_state_;
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index 7bc63ffd..23d4518 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -1699,4 +1699,15 @@
 }
 #endif
 
+void SkiaOutputSurfaceImpl::ReadbackForTesting(
+    CopyOutputRequest::CopyOutputRequestCallback result_callback) {
+  auto callback = base::BindOnce(
+      &SkiaOutputSurfaceImplOnGpu::ReadbackForTesting,
+      base::Unretained(impl_on_gpu_.get()), std::move(result_callback));
+  EnqueueGpuTask(std::move(callback), std::move(resource_sync_tokens_),
+                 /*make_current=*/true,
+                 /*need_framebuffer=*/!dependency_->IsOffscreen());
+  FlushGpuTasks(SyncMode::kNoWait);
+}
+
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index f15be32b..f86a3ce 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -212,6 +212,9 @@
   void CleanupImageProcessor() override;
 #endif
 
+  void ReadbackForTesting(
+      CopyOutputRequest::CopyOutputRequestCallback result_callback) override;
+
  private:
   bool Initialize();
   void InitializeOnGpuThread(bool* result);
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 4a96e31..e09333ed 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -2831,4 +2831,35 @@
 }
 #endif
 
+void SkiaOutputSurfaceImplOnGpu::ReadbackForTesting(
+    CopyOutputRequest::CopyOutputRequestCallback result_callback) {
+  std::optional<gpu::raster::GrShaderCache::ScopedCacheUse> cache_use;
+  if (dependency_->GetGrShaderCache()) {
+    cache_use.emplace(dependency_->GetGrShaderCache(),
+                      gpu::kDisplayCompositorClientId);
+  }
+
+  output_device_->ReadbackForTesting(base::BindOnce(  // IN-TEST
+      [](std::optional<gpu::raster::GrShaderCache::ScopedCacheUse> cache_use,
+         bool is_emulated_rgbx,
+         CopyOutputRequest::CopyOutputRequestCallback result_callback,
+         SkBitmap bitmap) {
+        // Discard alpha if emulating RGBX.
+        if (is_emulated_rgbx) {
+          SkPaint paint;
+          paint.setColor(SkColors::kBlack);
+          paint.setBlendMode(SkBlendMode::kDstATop);
+          SkCanvas canvas(bitmap);
+          canvas.drawPaint(paint);
+        }
+
+        std::move(result_callback)
+            .Run(std::make_unique<CopyOutputSkBitmapResult>(
+                gfx::Rect(gfx::SkISizeToSize(bitmap.dimensions())),
+                std::move(bitmap)));
+      },
+      std::move(cache_use), output_device_->is_emulated_rgbx(),
+      std::move(result_callback)));
+}
+
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
index 879d87f..1457c086 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -299,6 +299,9 @@
   void CleanupImageProcessor();
 #endif
 
+  void ReadbackForTesting(
+      CopyOutputRequest::CopyOutputRequestCallback result_callback);
+
  private:
   struct MailboxAccessData {
     MailboxAccessData();
diff --git a/content/browser/file_system_access/file_path_watcher/file_path_watcher_fsevents.cc b/content/browser/file_system_access/file_path_watcher/file_path_watcher_fsevents.cc
index 143fcc0..ad9a34c3 100644
--- a/content/browser/file_system_access/file_path_watcher/file_path_watcher_fsevents.cc
+++ b/content/browser/file_system_access/file_path_watcher/file_path_watcher_fsevents.cc
@@ -371,24 +371,6 @@
     // Use the `kFSEventStreamEventFlagItemRenamed` flag to identify a 'move'
     // event.
     if (event_flags & kFSEventStreamEventFlagItemRenamed) {
-      // Sometimes, FSEvents reports an event with a batch of event flags that
-      // contain both a `kFSEventStreamEventFlagItemRenamed` and a
-      // `kFSEventStreamEventFlagItemXattrMod` flag, which represents writable
-      // file contents being modified.
-      if (event_flags & kFSEventStreamEventFlagItemXattrMod) {
-        // Only report 'modified' change events that are in-scope.
-        if (!event_in_scope) {
-          continue;
-        }
-
-        FilePathWatcher::ChangeInfo change_info = {
-            file_path_type, FilePathWatcher::ChangeType::kModified, event_path};
-        callback_.Run(std::move(change_info),
-                      report_modified_path_ ? event_path : target,
-                      /*error=*/false);
-        continue;
-      }
-
       // Based on testing, moves within-scope for FSEvents will have
       // consecutive event ids that differ by 1, and the event with the higher
       // event id represents the "moved to" part of a move event. This allows
diff --git a/content/renderer/media/renderer_webaudiodevice_impl.cc b/content/renderer/media/renderer_webaudiodevice_impl.cc
index e0cfa9c..4c23d51 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl.cc
+++ b/content/renderer/media/renderer_webaudiodevice_impl.cc
@@ -174,6 +174,9 @@
   // On systems without audio hardware the returned parameters may be invalid.
   // In which case just choose whatever we want for the fake device.
   if (!original_sink_params_.IsValid()) {
+    SendLogMessage(base::StringPrintf(
+        "%s => (original_sink_params_ is invalid =[original_sink_params_=%s])",
+        __func__, original_sink_params_.AsHumanReadableString().c_str()));
     original_sink_params_.Reset(media::AudioParameters::AUDIO_FAKE,
                                 media::ChannelLayoutConfig::Stereo(), 48000,
                                 480);
@@ -336,6 +339,8 @@
   }
 
   DCHECK(thread_checker_.CalledOnValidThread());
+  SendLogMessage(base::StringPrintf("%s", __func__));
+
   webaudio_callback_->OnRenderError();
 }
 
diff --git a/docs/chrome_browser_design_principles.md b/docs/chrome_browser_design_principles.md
index af0a4053..7cc8fe9 100644
--- a/docs/chrome_browser_design_principles.md
+++ b/docs/chrome_browser_design_principles.md
@@ -334,8 +334,8 @@
       features) should be able to unconditionally reference "class BrowserView".
       The existence of this test suite forces features tested by these tests to
       have "if (!browser_view)" test-only checks in production code. Either
-      write a browser test (where both classes are provided by the test fixture) or a unit test
-      that requires neither.
+      write a browser test (where both classes are provided by the test fixture)
+      or a unit test that requires neither.
         * This also comes from a corollary of don't use "class Browser".
           Historically, features were written that take a "class Browser" as an
           input parameter. "class Browser" cannot be stubbed/faked/mocked, and
@@ -343,6 +343,42 @@
           provide a "class Browser" without a "class BrowserView". New features
           should not be taking "class Browser" as input, and should instead
           perform dependency injection.
+* Every UI feature should have at least 1 CUJ test.
+    * New UI features should write these tests using InteractiveBrowserTest.
+* Do not write change detector unit tests. The purpose of a unit test is to
+  validate behavior of common and edge cases for a block of code that has many
+  possible valid inputs.
+```cpp
+// Good. Depending on context, this can be broken into separate tests.
+bool IsPrime(int input);
+TEST(Math, CheckIsPrime) {
+  EXPECT_TRUE(IsPrime(2));
+  EXPECT_TRUE(IsPrime(3));
+  EXPECT_FALSE(IsPrime(99));
+  EXPECT_FALSE(IsPrime(-2));
+  EXPECT_FALSE(IsPrime(0));
+  EXPECT_FALSE(IsPrime(1));
+}
+
+// Bad. This is a change detector test. Change detector tests are easy to spot
+// because the test logic looks the same as the production logic.
+class ShowButtonOnBrowserActivation : public BrowserActivationListener {
+  void ShowButton();
+  bool DidShowButton();
+
+  // BrowserActivationListener overrides:
+  void BrowserDidActivate() override {
+    ShowButton();
+  }
+};
+
+Test(ShowButtonOnBrowserActivationTest, ShowButtonOnActivate) {
+  ShowButtonOnBrowserActivation listener;
+  listener.BrowserDidActivate();
+  EXPECT_TRUE(listener.DidShowButton());
+}
+
+```
 * Avoid unreachable branches.
     * We should have a semantic understanding of each path of control flow. How
       is this reached? If we don't know, then we should not have a conditional.
diff --git a/google_apis/youtube_music/youtube_music_api_request_types.cc b/google_apis/youtube_music/youtube_music_api_request_types.cc
index dc28d938..53e3fa27 100644
--- a/google_apis/youtube_music/youtube_music_api_request_types.cc
+++ b/google_apis/youtube_music/youtube_music_api_request_types.cc
@@ -174,6 +174,19 @@
   return json.value();
 }
 
+PlaybackQueueNextRequestPayload::PlaybackQueueNextRequestPayload() = default;
+PlaybackQueueNextRequestPayload::~PlaybackQueueNextRequestPayload() = default;
+
+std::string PlaybackQueueNextRequestPayload::ToJson() const {
+  // All fields are optional so this is currently an empty dictionary.
+  base::Value::Dict root;
+
+  const std::optional<std::string> json = base::WriteJson(root);
+  CHECK(json);
+
+  return json.value();
+}
+
 ReportPlaybackRequestPayload::Params::Params(
     const bool initial_report,
     const std::string& playback_reporting_token,
@@ -254,4 +267,23 @@
   return json.value();
 }
 
+SignedRequest::SignedRequest(RequestSender* sender)
+    : UrlFetchRequestBase(sender, ProgressCallback(), ProgressCallback()) {}
+
+SignedRequest::~SignedRequest() = default;
+
+void SignedRequest::SetSigningHeaders(std::vector<std::string>&& headers) {
+  headers_ = headers;
+}
+
+HttpRequestMethod SignedRequest::GetRequestType() const {
+  // Signed requests must have a body so they are always POSTs.
+  return HttpRequestMethod::kPost;
+}
+
+std::vector<std::string> SignedRequest::GetExtraRequestHeaders() const {
+  CHECK(!headers_.empty());
+  return headers_;
+}
+
 }  // namespace google_apis::youtube_music
diff --git a/google_apis/youtube_music/youtube_music_api_request_types.h b/google_apis/youtube_music/youtube_music_api_request_types.h
index 12d445ca..7bf16b31 100644
--- a/google_apis/youtube_music/youtube_music_api_request_types.h
+++ b/google_apis/youtube_music/youtube_music_api_request_types.h
@@ -10,6 +10,7 @@
 
 #include "base/time/time.h"
 #include "base/values.h"
+#include "google_apis/common/base_requests.h"
 
 namespace google_apis::youtube_music {
 
@@ -45,6 +46,15 @@
   std::optional<ShuffleMode> shuffle_mode;
 };
 
+// Payload for PlaybackQueueNextRequests. Currently, all fields are optional so
+// it's empty;.
+struct PlaybackQueueNextRequestPayload {
+  PlaybackQueueNextRequestPayload();
+  ~PlaybackQueueNextRequestPayload();
+
+  std::string ToJson() const;
+};
+
 // Payload used as a request body for the API request that reports the playback.
 struct ReportPlaybackRequestPayload {
   enum class PlaybackState {
@@ -119,6 +129,24 @@
   Params params;
 };
 
+// Requests that can have their payload signed with a client certificate.
+// Signing is implemented as a series of headers that are computed
+// asynchronously so they must be set before the request begins.
+class SignedRequest : public UrlFetchRequestBase {
+ public:
+  SignedRequest(RequestSender* sender);
+  ~SignedRequest() override;
+
+  void SetSigningHeaders(std::vector<std::string>&& headers);
+
+ protected:
+  HttpRequestMethod GetRequestType() const final;
+  std::vector<std::string> GetExtraRequestHeaders() const override;
+
+ private:
+  std::vector<std::string> headers_;
+};
+
 }  // namespace google_apis::youtube_music
 
 #endif  // GOOGLE_APIS_YOUTUBE_MUSIC_YOUTUBE_MUSIC_API_REQUEST_TYPES_H_
diff --git a/google_apis/youtube_music/youtube_music_api_requests.cc b/google_apis/youtube_music/youtube_music_api_requests.cc
index 03e148a7..5bca47e 100644
--- a/google_apis/youtube_music/youtube_music_api_requests.cc
+++ b/google_apis/youtube_music/youtube_music_api_requests.cc
@@ -162,9 +162,7 @@
     RequestSender* sender,
     const PlaybackQueuePrepareRequestPayload& payload,
     Callback callback)
-    : UrlFetchRequestBase(sender, ProgressCallback(), ProgressCallback()),
-      payload_(payload),
-      callback_(std::move(callback)) {
+    : SignedRequest(sender), payload_(payload), callback_(std::move(callback)) {
   CHECK(!callback_.is_null());
 }
 
@@ -188,10 +186,6 @@
   return error == HTTP_SUCCESS;
 }
 
-HttpRequestMethod PlaybackQueuePrepareRequest::GetRequestType() const {
-  return HttpRequestMethod::kPost;
-}
-
 bool PlaybackQueuePrepareRequest::GetContentData(
     std::string* upload_content_type,
     std::string* upload_content) {
@@ -243,10 +237,10 @@
 
 PlaybackQueueNextRequest::PlaybackQueueNextRequest(
     RequestSender* sender,
+    const PlaybackQueueNextRequestPayload& payload,
     Callback callback,
     const std::string& playback_queue_name)
-    : UrlFetchRequestBase(sender, ProgressCallback(), ProgressCallback()),
-      callback_(std::move(callback)) {
+    : SignedRequest(sender), payload_(payload), callback_(std::move(callback)) {
   CHECK(!callback_.is_null());
   playback_queue_name_ = playback_queue_name;
 }
@@ -270,8 +264,11 @@
   return error == HTTP_SUCCESS;
 }
 
-HttpRequestMethod PlaybackQueueNextRequest::GetRequestType() const {
-  return HttpRequestMethod::kPost;
+bool PlaybackQueueNextRequest::GetContentData(std::string* upload_content_type,
+                                              std::string* upload_content) {
+  *upload_content_type = kContentTypeJson;
+  *upload_content = payload_.ToJson();
+  return true;
 }
 
 void PlaybackQueueNextRequest::ProcessURLFetchResults(
@@ -320,7 +317,7 @@
     RequestSender* sender,
     std::unique_ptr<ReportPlaybackRequestPayload> payload,
     Callback callback)
-    : UrlFetchRequestBase(sender, ProgressCallback(), ProgressCallback()),
+    : SignedRequest(sender),
       payload_(std::move(payload)),
       callback_(std::move(callback)) {
   CHECK(payload_);
@@ -344,10 +341,6 @@
   return error == HTTP_SUCCESS;
 }
 
-HttpRequestMethod ReportPlaybackRequest::GetRequestType() const {
-  return HttpRequestMethod::kPost;
-}
-
 bool ReportPlaybackRequest::GetContentData(std::string* upload_content_type,
                                            std::string* upload_content) {
   *upload_content_type = kContentTypeJson;
diff --git a/google_apis/youtube_music/youtube_music_api_requests.h b/google_apis/youtube_music/youtube_music_api_requests.h
index f128b48f..0c45796 100644
--- a/google_apis/youtube_music/youtube_music_api_requests.h
+++ b/google_apis/youtube_music/youtube_music_api_requests.h
@@ -103,7 +103,7 @@
 // Request that prepares the playback queue for the API server. For API usage,
 // please check below:
 //   https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues/preparePlayback
-class PlaybackQueuePrepareRequest : public UrlFetchRequestBase {
+class PlaybackQueuePrepareRequest : public SignedRequest {
  public:
   using Callback = base::OnceCallback<void(
       base::expected<std::unique_ptr<Queue>, ApiErrorCode>)>;
@@ -122,7 +122,6 @@
   ApiErrorCode MapReasonToError(ApiErrorCode code,
                                 const std::string& reason) override;
   bool IsSuccessfulErrorCode(ApiErrorCode error) override;
-  HttpRequestMethod GetRequestType() const override;
   bool GetContentData(std::string* upload_content_type,
                       std::string* upload_content) override;
   void ProcessURLFetchResults(
@@ -146,12 +145,13 @@
 // Request to play the next in the playback queue for the API server. For API
 // usage, please check below:
 //   https://developers.google.com/youtube/mediaconnect/reference/rest/v1/queues/next
-class PlaybackQueueNextRequest : public UrlFetchRequestBase {
+class PlaybackQueueNextRequest : public SignedRequest {
  public:
   using Callback = base::OnceCallback<void(
       base::expected<std::unique_ptr<QueueContainer>, ApiErrorCode>)>;
 
   PlaybackQueueNextRequest(RequestSender* sender,
+                           const PlaybackQueueNextRequestPayload& payload,
                            Callback callback,
                            const std::string& playback_queue_name);
   PlaybackQueueNextRequest(const PlaybackQueueNextRequest&) = delete;
@@ -171,7 +171,8 @@
   ApiErrorCode MapReasonToError(ApiErrorCode code,
                                 const std::string& reason) override;
   bool IsSuccessfulErrorCode(ApiErrorCode error) override;
-  HttpRequestMethod GetRequestType() const override;
+  bool GetContentData(std::string* upload_content_type,
+                      std::string* upload_content) override;
   void ProcessURLFetchResults(
       const network::mojom::URLResponseHead* response_head,
       const base::FilePath response_file,
@@ -183,6 +184,8 @@
 
   void OnDataParsed(std::unique_ptr<QueueContainer> queue_container);
 
+  const PlaybackQueueNextRequestPayload payload_;
+
   // Playlist queue name. Unique identifier of a queue.
   std::string playback_queue_name_;
 
@@ -194,7 +197,7 @@
 // Request to report the playback to the API server. For API usage, please check
 // below:
 //   https://developers.google.com/youtube/mediaconnect/reference/rest/v1/reports/playback
-class ReportPlaybackRequest : public UrlFetchRequestBase {
+class ReportPlaybackRequest : public SignedRequest {
  public:
   using Callback = base::OnceCallback<void(
       base::expected<std::unique_ptr<ReportPlaybackResult>, ApiErrorCode>)>;
@@ -212,7 +215,6 @@
   ApiErrorCode MapReasonToError(ApiErrorCode code,
                                 const std::string& reason) override;
   bool IsSuccessfulErrorCode(ApiErrorCode error) override;
-  HttpRequestMethod GetRequestType() const override;
   bool GetContentData(std::string* upload_content_type,
                       std::string* upload_content) override;
   void ProcessURLFetchResults(
diff --git a/infra/config/generated/builder-owners/cronet-team@google.com.txt b/infra/config/generated/builder-owners/cronet-team@google.com.txt
index 847e3b2..6cdbd34 100644
--- a/infra/config/generated/builder-owners/cronet-team@google.com.txt
+++ b/infra/config/generated/builder-owners/cronet-team@google.com.txt
@@ -52,5 +52,4 @@
 try/android-cronet-x86-dbg-nougat-tests
 try/android-cronet-x86-dbg-oreo-tests
 try/android-cronet-x86-dbg-pie-tests
-try/android-cronet-x86-rel
-try/android_cronet
\ No newline at end of file
+try/android-cronet-x86-rel
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/android-cronet-arm-rel/properties.json b/infra/config/generated/builders/ci/android-cronet-arm-rel/properties.json
index 9e4904c5..40d02895 100644
--- a/infra/config/generated/builders/ci/android-cronet-arm-rel/properties.json
+++ b/infra/config/generated/builders/ci/android-cronet-arm-rel/properties.json
@@ -50,10 +50,6 @@
         {
           "builder": "android-cronet-arm-rel",
           "group": "tryserver.chromium.android"
-        },
-        {
-          "builder": "android_cronet",
-          "group": "tryserver.chromium.android"
         }
       ],
       "targets_spec_directory": "src/infra/config/generated/builders/ci/android-cronet-arm-rel/targets"
diff --git a/infra/config/generated/builders/gn_args_locations.json b/infra/config/generated/builders/gn_args_locations.json
index e229367..37fae95d 100644
--- a/infra/config/generated/builders/gn_args_locations.json
+++ b/infra/config/generated/builders/gn_args_locations.json
@@ -633,7 +633,6 @@
     "android_compile_dbg": "try/android_compile_dbg/gn-args.json",
     "android_compile_x64_dbg": "try/android_compile_x64_dbg/gn-args.json",
     "android_compile_x86_dbg": "try/android_compile_x86_dbg/gn-args.json",
-    "android_cronet": "try/android_cronet/gn-args.json",
     "android_optional_gpu_tests_rel": "try/android_optional_gpu_tests_rel/gn-args.json",
     "gpu-fyi-cq-android-arm64": "try/gpu-fyi-cq-android-arm64/gn-args.json",
     "gpu-fyi-try-android-m-nexus-5x-64": "try/gpu-fyi-try-android-m-nexus-5x-64/gn-args.json",
diff --git a/infra/config/generated/builders/try/android_cronet/gn-args.json b/infra/config/generated/builders/try/android_cronet/gn-args.json
deleted file mode 100644
index 25e5ea6..0000000
--- a/infra/config/generated/builders/try/android_cronet/gn-args.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
-  "gn_args": {
-    "arm_use_neon": false,
-    "clang_use_default_sample_profile": false,
-    "dcheck_always_on": true,
-    "debuggable_apks": false,
-    "default_min_sdk_version": 21,
-    "disable_file_support": true,
-    "enable_base_tracing": false,
-    "enable_reporting": true,
-    "enable_resource_allowlist_generation": false,
-    "enable_rust": false,
-    "enable_websockets": false,
-    "include_transport_security_state_preload_list": false,
-    "is_component_build": false,
-    "is_cronet_build": true,
-    "is_debug": false,
-    "media_use_ffmpeg": false,
-    "symbol_level": 0,
-    "target_cpu": "arm",
-    "target_os": "android",
-    "use_hashed_jni_names": true,
-    "use_partition_alloc": false,
-    "use_platform_icu_alternatives": true,
-    "use_remoteexec": true,
-    "use_siso": true,
-    "use_thin_lto": false
-  }
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/android_cronet/properties.json b/infra/config/generated/builders/try/android_cronet/properties.json
deleted file mode 100644
index bac8ba0..0000000
--- a/infra/config/generated/builders/try/android_cronet/properties.json
+++ /dev/null
@@ -1,77 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "additional_exclusions": [
-        "infra/config/generated/builders/try/android_cronet/gn-args.json"
-      ],
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "android-cronet-arm-rel",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "build_gs_bucket": "chromium-android-archive",
-              "builder_group": "chromium.android",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_android_config": {
-                "config": "main_builder"
-              },
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "cronet_builder",
-                  "mb"
-                ],
-                "build_config": "Release",
-                "config": "android",
-                "target_bits": 32,
-                "target_platform": "android"
-              },
-              "legacy_gclient_config": {
-                "apply_configs": [
-                  "android"
-                ],
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "ci",
-          "builder": "android-cronet-arm-rel",
-          "project": "chromium"
-        }
-      ],
-      "is_compile_only": true,
-      "targets_spec_directory": "src/infra/config/generated/builders/try/android_cronet/targets"
-    }
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-untrusted",
-    "metrics_project": "chromium-reclient-metrics",
-    "scandeps_server": true
-  },
-  "$build/siso": {
-    "configs": [
-      "builder"
-    ],
-    "enable_cloud_profiler": true,
-    "enable_cloud_trace": true,
-    "experiments": [],
-    "project": "rbe-chromium-untrusted",
-    "remote_jobs": -1
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "tryserver.chromium.android",
-  "recipe": "chromium_trybot"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/android_cronet/targets/chromium.android.json b/infra/config/generated/builders/try/android_cronet/targets/chromium.android.json
deleted file mode 100644
index b7709d48..0000000
--- a/infra/config/generated/builders/try/android_cronet/targets/chromium.android.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
-  "android-cronet-arm-rel": {
-    "additional_compile_targets": [
-      "cronet_package"
-    ],
-    "isolated_scripts": [
-      {
-        "merge": {
-          "script": "//tools/perf/process_perf_results.py"
-        },
-        "name": "cronet_sizes",
-        "resultdb": {
-          "enable": true,
-          "result_file": "${ISOLATED_OUTDIR}/sizes/test_results.json",
-          "result_format": "single"
-        },
-        "swarming": {
-          "dimensions": {
-            "cpu": "x86-64",
-            "os": "Ubuntu-22.04"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cronet_sizes",
-        "test_id_prefix": "ninja://components/cronet/android:cronet_sizes/"
-      },
-      {
-        "merge": {
-          "script": "//tools/perf/process_perf_results.py"
-        },
-        "name": "resource_sizes_cronet_sample_apk",
-        "resultdb": {
-          "enable": true,
-          "result_format": "single"
-        },
-        "swarming": {
-          "dimensions": {
-            "cpu": "x86-64",
-            "os": "Ubuntu-22.04"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "resource_sizes_cronet_sample_apk",
-        "test_id_prefix": "ninja://components/cronet/android:resource_sizes_cronet_sample_apk/"
-      }
-    ]
-  }
-}
\ No newline at end of file
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 120aea1..22b84f4 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -1772,10 +1772,6 @@
         mode_allowlist: "FULL_RUN"
       }
       builders {
-        name: "chromium/try/android_cronet"
-        includable_only: true
-      }
-      builders {
         name: "chromium/try/android_optional_gpu_tests_rel"
         location_filters {
           gerrit_host_regexp: ".*"
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 47c46f9..8ec9a42 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -34880,7 +34880,7 @@
           use_invocation_timestamp: true
         }
       }
-      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/android-cronet-arm-rel\">android-cronet-arm-rel</a></li><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/android_cronet\">android_cronet</a></li></ul>"
+      description_html: "This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/android-cronet-arm-rel\">android-cronet-arm-rel</a></li></ul>"
       shadow_builder_adjustments {
         service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
         pool: "luci.chromium.try"
@@ -76625,106 +76625,6 @@
       description_html: "This builder mirrors the following CI builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/Android x86 Builder (dbg)\">Android x86 Builder (dbg)</a></li></ul>"
     }
     builders {
-      name: "android_cronet"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:android_cronet"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-22.04"
-      dimensions: "pool:luci.chromium.try"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/try/android_cronet/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "tryserver.chromium.android",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium_trybot"'
-        '}'
-      execution_timeout_secs: 14400
-      expiration_secs: 7200
-      grace_period {
-        seconds: 120
-      }
-      build_numbers: YES
-      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "chromium.enable_cleandead"
-        value: 50
-      }
-      experiments {
-        key: "chromium_swarming.expose_merge_script_failures"
-        value: 100
-      }
-      experiments {
-        key: "luci.buildbucket.canary_software"
-        value: 5
-      }
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      experiments {
-        key: "swarming.prpc.cli"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "try_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_try_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_try_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*_wpt_tests/.+)"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-      description_html: "This builder mirrors the following CI builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/android-cronet-arm-rel\">android-cronet-arm-rel</a></li></ul>"
-      contact_team_email: "cronet-team@google.com"
-    }
-    builders {
       name: "android_optional_gpu_tests_rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:android_optional_gpu_tests_rel"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 68881d9..39be627a 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -3028,9 +3028,6 @@
     name: "buildbucket/luci.chromium.try/android_compile_x86_dbg"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/android_cronet"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/android_optional_gpu_tests_rel"
   }
   builders {
@@ -17170,9 +17167,6 @@
     name: "buildbucket/luci.chromium.try/android_compile_x86_dbg"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/android_cronet"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/android_optional_gpu_tests_rel"
   }
   builders {
@@ -18615,9 +18609,6 @@
     name: "buildbucket/luci.chromium.try/android_compile_x86_dbg"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/android_cronet"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/android_optional_gpu_tests_rel"
   }
   builders {
diff --git a/infra/config/generated/testing/PRESUBMIT.py b/infra/config/generated/testing/PRESUBMIT.py
new file mode 100644
index 0000000..afce66a
--- /dev/null
+++ b/infra/config/generated/testing/PRESUBMIT.py
@@ -0,0 +1,31 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+PRESUBMIT_VERSION = '2.0.0'
+
+
+def CheckTestingBuildbotSourceSideSpecs(input_api, output_api):
+  return input_api.RunTests([
+      input_api.Command(
+          name='check source side specs',
+          cmd=[
+              input_api.python3_executable,
+              '../../../../testing/buildbot/generate_buildbot_json.py',
+              '--check', '--verbose'
+          ],
+          kwargs={},
+          message=output_api.PresubmitError),
+  ])
+
+
+def CheckTestingBuildbotJsonFiles(input_api, output_api):
+  return input_api.RunTests([
+      input_api.Command(name='check JSON files',
+                        cmd=[
+                            input_api.python3_executable,
+                            '../../../../testing/buildbot/check.py'
+                        ],
+                        kwargs={},
+                        message=output_api.PresubmitError),
+  ])
diff --git a/infra/config/generated/testing/gn_isolate_map.pyl b/infra/config/generated/testing/gn_isolate_map.pyl
index 847e4b6e..8443b1f 100644
--- a/infra/config/generated/testing/gn_isolate_map.pyl
+++ b/infra/config/generated/testing/gn_isolate_map.pyl
@@ -1006,13 +1006,6 @@
     "label": "//ash/keyboard/ui:keyboard_unittests",
     "type": "windowed_test_launcher",
   },
-  "lacros_all_tast_tests": {
-    "label": "//chromeos/lacros:lacros_all_tast_tests",
-    "type": "generated_script",
-    "args": [
-      "--logs-dir=${ISOLATED_OUTDIR}",
-    ],
-  },
   "lacros_chrome_browsertests": {
     "label": "//chrome/test:lacros_chrome_browsertests",
     "type": "windowed_test_launcher",
diff --git a/infra/config/generated/testing/test_suites.pyl b/infra/config/generated/testing/test_suites.pyl
index 9fe33a78..d6e835c 100644
--- a/infra/config/generated/testing/test_suites.pyl
+++ b/infra/config/generated/testing/test_suites.pyl
@@ -525,7 +525,18 @@
     },
 
     'chromeos_arm_gtests': {
-      'video_decode_accelerator_tests_v4l2': {
+      'video_decode_accelerator_tests_v4l2_vp8': {
+        'test': 'video_decode_accelerator_tests',
+        'args': [
+          '--as-root',
+          '--validator_type=none',
+          '../../media/test/data/test-25fps.vp8',
+          '../../media/test/data/test-25fps.vp8.json',
+        ],
+        'ci_only': True,
+        'experiment_percentage': 100,
+      },
+      'video_decode_accelerator_tests_v4l2_vp9': {
         'test': 'video_decode_accelerator_tests',
         'args': [
           '--as-root',
@@ -5215,6 +5226,9 @@
     'test_traffic_annotation_auditor_script': {
       'test_traffic_annotation_auditor': {
         'script': 'test_traffic_annotation_auditor.py',
+        'precommit_args': [
+          '--no-update-sheet',
+        ],
       },
     },
 
diff --git a/infra/config/lib/targets-internal/common.star b/infra/config/lib/targets-internal/common.star
index 333edda..a48163ce 100644
--- a/infra/config/lib/targets-internal/common.star
+++ b/infra/config/lib/targets-internal/common.star
@@ -184,7 +184,9 @@
         script = None,
         binary = None,
         telemetry_test_name = None,
-        args = None):
+        args = None,
+        precommit_args = None,
+        non_precommit_args = None):
     """The details for the test included when included in a basic suite.
 
     When generating test_suites.pyl, these values will be written out
@@ -204,6 +206,8 @@
         binary = binary,
         telemetry_test_name = telemetry_test_name,
         args = args,
+        precommit_args = precommit_args,
+        non_precommit_args = non_precommit_args,
     )
 
 def _create_legacy_test(*, name, basic_suite_test_config, mixins = None):
diff --git a/infra/config/lib/targets-internal/pyl-generators.star b/infra/config/lib/targets-internal/pyl-generators.star
index d54f7c11..bd371f6 100644
--- a/infra/config/lib/targets-internal/pyl-generators.star
+++ b/infra/config/lib/targets-internal/pyl-generators.star
@@ -184,6 +184,7 @@
     for args_attr in (
         "args",
         "precommit_args",
+        "non_precommit_args",
         "android_args",
         "chromeos_args",
         "desktop_args",
@@ -429,9 +430,10 @@
 
             # Merge any args from the target with those specified for the test
             # in the suite
-            merged_args = args.listify(target_test_config.args, mixin_values.get("args"))
-            if merged_args:
-                mixin_values["args"] = merged_args
+            for a in ("args", "precommit_args", "non_precommit_args"):
+                merged_args = args.listify(getattr(target_test_config, a), mixin_values.get(a))
+                if merged_args:
+                    mixin_values[a] = merged_args
 
             # merge and resultdb can be set on the binary, but don't override
             # values set on the test in the suite
diff --git a/infra/config/lib/targets-internal/test-types/script_test.star b/infra/config/lib/targets-internal/test-types/script_test.star
index 67116aa..824fcbb6 100644
--- a/infra/config/lib/targets-internal/test-types/script_test.star
+++ b/infra/config/lib/targets-internal/test-types/script_test.star
@@ -11,6 +11,9 @@
     return dict(
         name = node.key.id,
         script = node.props.details.script,
+        args = list(node.props.details.args or []),
+        precommit_args = list(node.props.details.precommit_args or []),
+        non_precommit_args = list(node.props.details.non_precommit_args or []),
     )
 
 _script_test_spec_handler = _targets_common.spec_handler(
@@ -19,7 +22,7 @@
     finalize = (lambda name, settings, spec_value: ("scripts", name, spec_value)),
 )
 
-def script_test(*, name, script):
+def script_test(*, name, script, args = None, precommit_args = None, non_precommit_args = None):
     """Define a script test.
 
     A script test is a test that runs a python script wihin the
@@ -37,6 +40,9 @@
         name = name,
         basic_suite_test_config = _targets_common.basic_suite_test_config(
             script = script,
+            args = args,
+            precommit_args = precommit_args,
+            non_precommit_args = non_precommit_args,
         ),
     )
 
@@ -45,5 +51,8 @@
         spec_handler = _script_test_spec_handler,
         details = struct(
             script = script,
+            args = args,
+            precommit_args = precommit_args,
+            non_precommit_args = non_precommit_args,
         ),
     )
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
index d5287b2..10affbed 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
@@ -1350,34 +1350,6 @@
     ),
 )
 
-# TODO(crbug.com/363275110): Remove after "try/android-cronet-arm-rel" is up.
-try_.builder(
-    name = "android_cronet",
-    branch_selector = branches.selector.ANDROID_BRANCHES,
-    mirrors = [
-        "ci/android-cronet-arm-rel",
-    ],
-    builder_config_settings = builder_config.try_settings(
-        is_compile_only = True,
-    ),
-    gn_args = gn_args.config(
-        configs = [
-            "android_builder_without_codecs",
-            "cronet_android",
-            "release_try_builder",
-            "remoteexec",
-            "arm_no_neon",
-        ],
-    ),
-    builderless = not settings.is_main,
-    contact_team_email = "cronet-team@google.com",
-    experiments = {
-        # crbug/940930
-        "chromium.enable_cleandead": 50,
-    },
-    main_list_view = "try",
-)
-
 try_.gpu.optional_tests_builder(
     name = "android_optional_gpu_tests_rel",
     branch_selector = branches.selector.ANDROID_BRANCHES,
diff --git a/infra/config/targets/basic_suites.star b/infra/config/targets/basic_suites.star
index eb4eeff..8de878b 100644
--- a/infra/config/targets/basic_suites.star
+++ b/infra/config/targets/basic_suites.star
@@ -422,10 +422,16 @@
 targets.legacy_basic_suite(
     name = "chromeos_arm_gtests",
     tests = {
-        "video_decode_accelerator_tests_v4l2": targets.legacy_test_config(
+        "video_decode_accelerator_tests_v4l2_vp8": targets.legacy_test_config(
             ci_only = True,
-            # TODO(b/303119905): Remove experimental status once this
-            # suite is added to CQ.
+            # TODO(b/303119905): Remove experimental status first.
+            # Then promote out of ci-only optionally.
+            experiment_percentage = 100,
+        ),
+        "video_decode_accelerator_tests_v4l2_vp9": targets.legacy_test_config(
+            ci_only = True,
+            # TODO(b/303119905): Remove experimental status first.
+            # Then promote out of ci-only optionally.
             experiment_percentage = 100,
         ),
     },
diff --git a/infra/config/targets/binaries.star b/infra/config/targets/binaries.star
index a118b82..9867cec3 100644
--- a/infra/config/targets/binaries.star
+++ b/infra/config/targets/binaries.star
@@ -1054,14 +1054,6 @@
     label = "//ash/keyboard/ui:keyboard_unittests",
 )
 
-targets.binaries.generated_script(
-    name = "lacros_all_tast_tests",
-    label = "//chromeos/lacros:lacros_all_tast_tests",
-    args = [
-        "--logs-dir=${ISOLATED_OUTDIR}",
-    ],
-)
-
 targets.binaries.windowed_test_launcher(
     name = "lacros_chrome_browsertests",
     label = "//chrome/test:lacros_chrome_browsertests",
diff --git a/infra/config/targets/tests.star b/infra/config/targets/tests.star
index 24e234e..6cb0cce5 100644
--- a/infra/config/targets/tests.star
+++ b/infra/config/targets/tests.star
@@ -959,7 +959,18 @@
 )
 
 targets.tests.gtest_test(
-    name = "video_decode_accelerator_tests_v4l2",
+    name = "video_decode_accelerator_tests_v4l2_vp8",
+    args = [
+        "--as-root",
+        "--validator_type=none",
+        "../../media/test/data/test-25fps.vp8",
+        "../../media/test/data/test-25fps.vp8.json",
+    ],
+    binary = "video_decode_accelerator_tests",
+)
+
+targets.tests.gtest_test(
+    name = "video_decode_accelerator_tests_v4l2_vp9",
     args = [
         "--as-root",
         "--validator_type=none",
@@ -1427,10 +1438,6 @@
 )
 
 targets.tests.gtest_test(
-    name = "lacros_all_tast_tests",
-)
-
-targets.tests.gtest_test(
     name = "lacros_chrome_browsertests",
 )
 
@@ -2199,6 +2206,9 @@
 targets.tests.script_test(
     name = "test_traffic_annotation_auditor",
     script = "test_traffic_annotation_auditor.py",
+    precommit_args = [
+        "--no-update-sheet",
+    ],
 )
 
 targets.tests.isolated_script_test(
diff --git a/internal b/internal
index 3d94d1f..30981f0 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 3d94d1f51451feb30d4e8450d3512062403c1fd7
+Subproject commit 30981f06168449b7c9142d22aa6a5b04c008e02c
diff --git a/ios/chrome/browser/first_run/ui_bundled/first_run_without_search_engine_choice_egtest.mm b/ios/chrome/browser/first_run/ui_bundled/first_run_without_search_engine_choice_egtest.mm
index 2219e9c..0c371f6 100644
--- a/ios/chrome/browser/first_run/ui_bundled/first_run_without_search_engine_choice_egtest.mm
+++ b/ios/chrome/browser/first_run/ui_bundled/first_run_without_search_engine_choice_egtest.mm
@@ -1284,7 +1284,8 @@
 
 // Tests that the History Sync Opt-In screen contains the avatar of the
 // signed-in user, and the correct background image for the avatar.
-- (void)testHistorySyncLayout {
+// TODO(crbug.com/365786558): Test flaky on simulator
+- (void)DISABLED_testHistorySyncLayout {
   // Add identity.
   FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
   [SigninEarlGrey addFakeIdentity:fakeIdentity];
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/BUILD.gn b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/BUILD.gn
index b5a4b62e..0a81e71 100644
--- a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/BUILD.gn
+++ b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/BUILD.gn
@@ -36,7 +36,10 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "save_card_infobar_modal_overlay_mediator_unittest.mm" ]
+  sources = [
+    "save_card_infobar_modal_overlay_coordinator_unittest.mm",
+    "save_card_infobar_modal_overlay_mediator_unittest.mm",
+  ]
 
   deps = [
     "//base/test:test_support",
@@ -48,6 +51,7 @@
     "//ios/chrome/browser/autofill/model/message",
     "//ios/chrome/browser/infobars/model",
     "//ios/chrome/browser/infobars/model:public",
+    "//ios/chrome/browser/infobars/model/overlays:overlay_type",
     "//ios/chrome/browser/infobars/model/overlays/browser_agent/interaction_handlers/test",
     "//ios/chrome/browser/infobars/model/test",
     "//ios/chrome/browser/overlays/model",
@@ -56,9 +60,12 @@
     "//ios/chrome/browser/overlays/model/test",
     "//ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card",
     "//ios/chrome/browser/overlays/ui_bundled/test",
+    "//ios/chrome/browser/shared/model/browser/test:test_support",
+    "//ios/chrome/browser/shared/model/profile/test:test",
     "//ios/chrome/browser/ui/infobars/modals",
     "//ios/chrome/browser/ui/infobars/modals/test",
     "//ios/chrome/browser/ui/infobars/test",
+    "//ios/chrome/test:test_support",
     "//ios/web/public/test",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_coordinator.mm b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_coordinator.mm
index 99effddb..737e72de 100644
--- a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_coordinator.mm
+++ b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_coordinator.mm
@@ -15,7 +15,8 @@
 
 @interface SaveCardInfobarModalOverlayCoordinator ()
 // Redefine ModalConfiguration properties as readwrite.
-@property(nonatomic, strong, readwrite) OverlayRequestMediator* modalMediator;
+@property(nonatomic, strong, readwrite)
+    SaveCardInfobarModalOverlayMediator* modalMediator;
 @property(nonatomic, strong, readwrite) UIViewController* modalViewController;
 // The request's config.
 @property(nonatomic, assign, readonly)
@@ -78,6 +79,8 @@
     if (delegate) {
       delegate->OnLegalMessageLinkClicked(self.pendingURLToLoad);
     }
+  } else {
+    [self.modalMediator dismissOverlay];
   }
   self.modalMediator = nil;
   self.modalViewController = nil;
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_coordinator_unittest.mm b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_coordinator_unittest.mm
new file mode 100644
index 0000000..024bc04
--- /dev/null
+++ b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_coordinator_unittest.mm
@@ -0,0 +1,85 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_coordinator.h"
+
+#import "base/uuid.h"
+#import "components/autofill/core/browser/data_model/credit_card.h"
+#import "ios/chrome/browser/infobars/model/infobar_ios.h"
+#import "ios/chrome/browser/infobars/model/infobar_type.h"
+#import "ios/chrome/browser/infobars/model/overlays/browser_agent/interaction_handlers/test/mock_autofill_save_card_infobar_delegate_mobile.h"
+#import "ios/chrome/browser/infobars/model/overlays/infobar_overlay_type.h"
+#import "ios/chrome/browser/overlays/model/public/default/default_infobar_overlay_request_config.h"
+#import "ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator.h"
+#import "ios/chrome/browser/overlays/ui_bundled/test/mock_overlay_coordinator_delegate.h"
+#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
+#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
+#import "ios/web/public/test/web_task_environment.h"
+#import "testing/gmock/include/gmock/gmock.h"
+#import "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/gtest_support.h"
+
+class SaveCardInfobarModalOverlayCoordinatorTest : public PlatformTest {
+ public:
+  SaveCardInfobarModalOverlayCoordinatorTest()
+      : browser_state_(TestChromeBrowserState::Builder().Build()),
+        browser_(std::make_unique<TestBrowser>(browser_state_.get())),
+        root_view_controller_([[UIViewController alloc] init]) {
+    autofill::CreditCard credit_card(
+        base::Uuid::GenerateRandomV4().AsLowercaseString(),
+        "https://www.example.com/");
+    std::unique_ptr<MockAutofillSaveCardInfoBarDelegateMobile> delegate =
+        MockAutofillSaveCardInfoBarDelegateMobileFactory::
+            CreateMockAutofillSaveCardInfoBarDelegateMobileFactory(
+                /*upload=*/true, credit_card);
+    save_card_infobar_delegate_ = delegate.get();
+    infobar_ = std::make_unique<InfoBarIOS>(InfobarType::kInfobarTypeSaveCard,
+                                            std::move(delegate));
+    request_ =
+        OverlayRequest::CreateWithConfig<DefaultInfobarOverlayRequestConfig>(
+            infobar_.get(), InfobarOverlayType::kModal);
+    coordinator_ = [[SaveCardInfobarModalOverlayCoordinator alloc]
+        initWithBaseViewController:root_view_controller_
+                           browser:browser_.get()
+                           request:request_.get()
+                          delegate:&delegate_];
+  }
+
+  void CreateMockSaveCardInfobarModalOverlayMediatorStubbed() {
+    modalMediator_ = OCMClassMock([SaveCardInfobarModalOverlayMediator class]);
+    OCMStub([modalMediator_ alloc]).andReturn(modalMediator_);
+    OCMStub([modalMediator_ initWithRequest:request_.get()])
+        .andReturn(modalMediator_);
+  }
+
+  ~SaveCardInfobarModalOverlayCoordinatorTest() override {
+    [modalMediator_ stopMocking];
+    PlatformTest::TearDown();
+  }
+
+ protected:
+  web::WebTaskEnvironment task_environment_;
+  std::unique_ptr<ChromeBrowserState> browser_state_;
+  std::unique_ptr<TestBrowser> browser_;
+  MockOverlayRequestCoordinatorDelegate delegate_;
+  raw_ptr<MockAutofillSaveCardInfoBarDelegateMobile>
+      save_card_infobar_delegate_ = nil;
+  std::unique_ptr<InfoBarIOS> infobar_;
+  std::unique_ptr<OverlayRequest> request_;
+  UIViewController* root_view_controller_ = nil;
+  id modalMediator_ = nil;
+  SaveCardInfobarModalOverlayCoordinator* coordinator_ = nil;
+};
+
+TEST_F(SaveCardInfobarModalOverlayCoordinatorTest, resetModal) {
+  CreateMockSaveCardInfobarModalOverlayMediatorStubbed();
+  [coordinator_ startAnimated:NO];
+  EXPECT_OCMOCK_VERIFY(modalMediator_);
+
+  // Expect that the mediator's dismissOverlay is called when coordinator is
+  // reset.
+  OCMExpect([modalMediator_ dismissOverlay]);
+  [coordinator_ stopAnimated:NO];
+}
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator.h b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator.h
index 0e7a986..a997f01 100644
--- a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator.h
+++ b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator.h
@@ -28,6 +28,10 @@
 // is not successful.
 - (void)creditCardUploadCompleted:(BOOL)card_saved;
 
+// Instructs AutofillSaveCardInfoBarDelegateIOS that modal is not presenting,
+// sets `CreditCardUploadCompletionCallback` to null and stops the overlay.
+- (void)dismissOverlay;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_OVERLAYS_UI_BUNDLED_INFOBAR_MODAL_SAVE_CARD_SAVE_CARD_INFOBAR_MODAL_OVERLAY_MEDIATOR_H_
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator.mm b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator.mm
index cd0c434..1b83e2b 100644
--- a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator.mm
+++ b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator.mm
@@ -199,6 +199,15 @@
   return DefaultInfobarOverlayRequestConfig::RequestSupport();
 }
 
+- (void)dismissOverlay {
+  if (self.saveCardDelegate) {
+    self.saveCardDelegate->SetCreditCardUploadCompletionCallback(
+        base::NullCallback());
+    self.saveCardDelegate->SetInfobarIsPresenting(NO);
+  }
+  [super dismissOverlay];
+}
+
 #pragma mark - InfobarSaveCardModalDelegate
 
 - (void)saveCardWithCardholderName:(NSString*)cardholderName
@@ -286,13 +295,4 @@
   self.saveCardDelegate->OnConfirmationClosed();
 }
 
-- (void)dismissOverlay {
-  if (self.saveCardDelegate) {
-    self.saveCardDelegate->SetCreditCardUploadCompletionCallback(
-        base::NullCallback());
-    self.saveCardDelegate->SetInfobarIsPresenting(NO);
-  }
-  [super dismissOverlay];
-}
-
 @end
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator_unittest.mm b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator_unittest.mm
index 53d817b7..a295ae222 100644
--- a/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator_unittest.mm
+++ b/ios/chrome/browser/overlays/ui_bundled/infobar_modal/save_card/save_card_infobar_modal_overlay_mediator_unittest.mm
@@ -203,8 +203,8 @@
               base::SysUTF8ToNSString(delegate.pendingURLToLoad.spec()));
 }
 
-// Tests that when modal is dismissed, mediator reset the callback passed to the
-// delegate and informs that infobar is not presenting.
+// Tests that when modal is dismissed, mediator resets the callback passed to
+// the delegate and informs that infobar is not presenting.
 TEST_F(SaveCardInfobarModalOverlayMediatorTest, OnInfoBarDismissed) {
   EXPECT_CALL(*delegate_, SetCreditCardUploadCompletionCallback);
   EXPECT_CALL(*delegate_, SetInfobarIsPresenting(NO));
diff --git a/ios/chrome/browser/price_insights/ui/price_history.swift b/ios/chrome/browser/price_insights/ui/price_history.swift
index 5db1e215..d986933 100644
--- a/ios/chrome/browser/price_insights/ui/price_history.swift
+++ b/ios/chrome/browser/price_insights/ui/price_history.swift
@@ -95,6 +95,7 @@
   /// The price history data consisting of dates and corresponding prices.
   let history: [Date: NSNumber]
   let currency: String
+  let graphAccessibilityLabel: String
 
   /// Graph gradient color.
   static let graphGradientColor = "graph_gradient_color"
@@ -157,6 +158,7 @@
           yEnd: .value("Price", price.doubleValue)
         )
         .foregroundStyle(linearGradient)
+        .accessibilityHidden(true)
 
         // Displaying the line mark on the graph.
         LineMark(
@@ -172,6 +174,8 @@
           x: .value("Date", selectedDate)
         )
         .lineStyle(StrokeStyle(lineWidth: 1, dash: [3]))
+        .accessibilityHidden(true)
+
         PointMark(
           x: .value("Date", selectedDate),
           y: .value("Price", selectedPrice.doubleValue)
@@ -186,6 +190,7 @@
             )
         }
         .foregroundStyle(Color(uiColor: Self.blue600))
+        .accessibilityHidden(true)
       }
     }
     .chartBackground { chartProxy in
@@ -267,8 +272,10 @@
           )
         }
       }
+      .accessibilityHidden(true)
     )
     .edgesIgnoringSafeArea(.all)
+    .accessibilityLabel(graphAccessibilityLabel)
   }
 
   /// Updates the selected data when the given `location` is selected inside the given
diff --git a/ios/chrome/browser/price_insights/ui/price_history_provider.swift b/ios/chrome/browser/price_insights/ui/price_history_provider.swift
index d3415e9..252c974 100644
--- a/ios/chrome/browser/price_insights/ui/price_history_provider.swift
+++ b/ios/chrome/browser/price_insights/ui/price_history_provider.swift
@@ -9,13 +9,16 @@
 // A provider to provide the SwiftUI PriceHistory to Objective C. This is
 // necessary because Objective C can't see SwiftUI types.
 @objcMembers class PriceHistoryProvider: NSObject {
-  public static func makeViewController(withHistory history: [Date: NSNumber], currency: String)
+  public static func makeViewController(
+    withHistory history: [Date: NSNumber], currency: String, graphAccessibilityLabel: String
+  )
     -> UIViewController
   {
     return UIHostingController(
       rootView: HistoryGraph(
         history: history,
-        currency: currency
+        currency: currency,
+        graphAccessibilityLabel: graphAccessibilityLabel
       ))
   }
 }
diff --git a/ios/chrome/browser/price_insights/ui/price_insights_cell.mm b/ios/chrome/browser/price_insights/ui/price_insights_cell.mm
index f4b8317..904b2a6 100644
--- a/ios/chrome/browser/price_insights/ui/price_insights_cell.mm
+++ b/ios/chrome/browser/price_insights/ui/price_insights_cell.mm
@@ -14,6 +14,7 @@
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/util/constraints_ui_util.h"
 #import "ios/chrome/common/ui/util/pointer_interaction_util.h"
+#import "ui/base/l10n/l10n_util.h"
 #import "ui/base/l10n/l10n_util_mac.h"
 #import "url/gurl.h"
 
@@ -340,10 +341,16 @@
     }
   }
 
+  NSString* currency = base::SysUTF8ToNSString(self.item.currency);
+
   UIViewController* priceHistoryViewController = [PriceHistoryProvider
       makeViewControllerWithHistory:self.item.priceHistory
-                           currency:base::SysUTF8ToNSString(
-                                        self.item.currency)];
+                           currency:currency
+            graphAccessibilityLabel:
+                l10n_util::GetNSStringF(
+                    IDS_PRICE_INSIGHTS_GRAPH_ACCESSIBILITY_LABEL,
+                    base::SysNSStringToUTF16(currency),
+                    base::SysNSStringToUTF16(self.item.title))];
   priceHistoryViewController.view.translatesAutoresizingMaskIntoConstraints =
       NO;
   [self.viewController addChildViewController:priceHistoryViewController];
diff --git a/ios/components/security_interstitials/safe_browsing/fake_safe_browsing_service.mm b/ios/components/security_interstitials/safe_browsing/fake_safe_browsing_service.mm
index 64b97c6b..98df91d 100644
--- a/ios/components/security_interstitials/safe_browsing/fake_safe_browsing_service.mm
+++ b/ios/components/security_interstitials/safe_browsing/fake_safe_browsing_service.mm
@@ -45,6 +45,7 @@
             /*hash_realtime_service_on_ui=*/nullptr,
             safe_browsing::hash_realtime_utils::HashRealTimeSelection::kNone,
             /*is_async_check=*/false,
+            /*check_allowlist_before_hash_database=*/false,
             SessionID::InvalidValue()) {}
   ~FakeSafeBrowsingUrlCheckerImpl() override = default;
 
diff --git a/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.mm b/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.mm
index 547f828..8f25314 100644
--- a/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.mm
+++ b/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.mm
@@ -192,7 +192,8 @@
       url_lookup_service ? url_lookup_service->GetWeakPtr() : nullptr,
       hash_real_time_service ? hash_real_time_service->GetWeakPtr() : nullptr,
       hash_real_time_selection,
-      /*is_async_check=*/false, SessionID::InvalidValue());
+      /*is_async_check=*/false, /*check_allowlist_before_hash_database=*/false,
+      SessionID::InvalidValue());
 }
 
 std::unique_ptr<safe_browsing::SafeBrowsingUrlCheckerImpl>
@@ -236,7 +237,8 @@
       url_lookup_service ? url_lookup_service->GetWeakPtr() : nullptr,
       hash_real_time_service ? hash_real_time_service->GetWeakPtr() : nullptr,
       hash_real_time_selection,
-      /*is_async_check=*/true, SessionID::InvalidValue());
+      /*is_async_check=*/true, /*check_allowlist_before_hash_database=*/false,
+      SessionID::InvalidValue());
 }
 
 std::unique_ptr<safe_browsing::SafeBrowsingUrlCheckerImpl>
@@ -266,7 +268,8 @@
       /*hash_realtime_service=*/nullptr,
       /*hash_realtime_selection=*/
       safe_browsing::hash_realtime_utils::HashRealTimeSelection::kNone,
-      /*is_async_check=*/false, SessionID::InvalidValue());
+      /*is_async_check=*/false, /*check_allowlist_before_hash_database=*/false,
+      SessionID::InvalidValue());
 }
 
 // Checks if async check should be created.
diff --git a/ios_internal b/ios_internal
index 2091388..cb9d0f30 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 2091388e503d97e226e4033bd616b11f5c2e19c8
+Subproject commit cb9d0f308d6dd05d93f35f9e1fd6098fb968839a
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 8a10f24..32817f44 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -853,7 +853,7 @@
 // all platforms.
 BASE_FEATURE(kV4L2FlatStatefulVideoDecoder,
              "V4L2FlatStatefulVideoDecoder",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 // Enable H264 temporal layer encoding with V4L2 HW encoder on ChromeOS.
 BASE_FEATURE(kV4L2H264TemporalLayerHWEncoding,
              "V4L2H264TemporalLayerHWEncoding",
diff --git a/media/formats/mp4/avc.cc b/media/formats/mp4/avc.cc
index 0990467..87a9b69 100644
--- a/media/formats/mp4/avc.cc
+++ b/media/formats/mp4/avc.cc
@@ -290,12 +290,8 @@
 
           case H264NALU::kFiller:
           case H264NALU::kUnspecified:
-            if (!(order_state >= kAfterFirstVCL &&
-                  order_state < kEOStreamAllowed)) {
-              DVLOG(1) << "Unexpected NALU type " << nalu.nal_unit_type
-                       << " in order_state " << order_state;
-              return result;
-            }
+            // These syntax elements are to simply be ignored according to H264
+            // Annex B 7.4.2.7
             break;
 
           default:
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn
index a1f741f1..f306502 100644
--- a/mojo/BUILD.gn
+++ b/mojo/BUILD.gn
@@ -37,7 +37,6 @@
     ":mojo_perftests",
     ":mojo_unittests",
     "//mojo/public/tools:mojo_python_unittests",
-    "//mojo/public/tools/bindings/tests:build_ts",
     "//third_party/ipcz/src:ipcz_tests",
   ]
 
diff --git a/mojo/public/tools/bindings/tests/BUILD.gn b/mojo/public/tools/bindings/tests/BUILD.gn
deleted file mode 100644
index 33a0fab..0000000
--- a/mojo/public/tools/bindings/tests/BUILD.gn
+++ /dev/null
@@ -1,46 +0,0 @@
-import("//mojo/public/tools/bindings/mojom.gni")
-import("//tools/typescript/webui_ts_library.gni")
-
-mojom("test_mojom") {
-  sources = [ "test.test-mojom" ]
-  webui_module_path = "chrome://resources/mojo/public/tools/bindings/tests"
-  testonly = true
-
-  ts_typemaps = [
-    {
-      types = [
-        {
-          mojom = "bindings.tests.mojom.SimpleStruct"
-          ts = "String16"
-          ts_import =
-              "//resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js"
-          converter = "SimpleStructConverter"
-          import = "test_converter.js"
-        },
-      ]
-    },
-  ]
-}
-
-copy("copy_converter") {
-  sources = [ "test_converter.ts" ]
-  outputs = [ "${target_gen_dir}/test_converter.ts" ]
-}
-
-# TODO(ffred): Remove this once we have actual dependents on ts typemapping.
-webui_ts_library("build_ts") {
-  root_dir = target_gen_dir
-
-  in_files = [
-    "test_converter.ts",
-    "test.test-mojom-webui.ts",
-    "test.test-mojom-converters.ts",
-  ]
-
-  deps = [ "//ui/webui/resources/mojo:build_ts" ]
-
-  extra_deps = [
-    ":copy_converter",
-    ":test_mojom_ts__generator",
-  ]
-}
diff --git a/mojo/public/tools/bindings/tests/test.test-mojom b/mojo/public/tools/bindings/tests/test.test-mojom
deleted file mode 100644
index 77767982..0000000
--- a/mojo/public/tools/bindings/tests/test.test-mojom
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module bindings.tests.mojom;
-
-struct SimpleStruct {
-    string str;
-    int32 number;
-};
diff --git a/mojo/public/tools/bindings/tests/test_converter.ts b/mojo/public/tools/bindings/tests/test_converter.ts
deleted file mode 100644
index 50cafa9cb..0000000
--- a/mojo/public/tools/bindings/tests/test_converter.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import { SimpleStructDataView, SimpleStructTypeMapper } from './test.test-mojom-converters.js';
-import type {
-  String16
-} from 'chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js';
-
-
-export class SimpleStructConverter implements SimpleStructTypeMapper<String16> {
-  str(_: String16): string {
-    return '';
-  }
-
-  number(_: String16): number {
-    return 888;
-  }
-
-  convert(_: SimpleStructDataView): String16 {
-    return {data: []};
-  }
-}
-
diff --git a/pdf/pdf_ink_module.cc b/pdf/pdf_ink_module.cc
index 22f07dfb..385491df8 100644
--- a/pdf/pdf_ink_module.cc
+++ b/pdf/pdf_ink_module.cc
@@ -657,6 +657,7 @@
 void PdfInkModule::HandleSetAnnotationModeMessage(
     const base::Value::Dict& message) {
   enabled_ = message.FindBool("enable").value();
+  client_->OnAnnotationModeToggled(enabled_);
   MaybeSetCursor();
 }
 
diff --git a/pdf/pdf_ink_module.h b/pdf/pdf_ink_module.h
index 789f22c3..9ab9152 100644
--- a/pdf/pdf_ink_module.h
+++ b/pdf/pdf_ink_module.h
@@ -88,6 +88,9 @@
     // Returns whether the page at `page_index` is visible or not.
     virtual bool IsPageVisible(int page_index) = 0;
 
+    // Notifies the client whether annotation mode is enabled or not.
+    virtual void OnAnnotationModeToggled(bool enable) {}
+
     // Notifies the client that a stroke has finished drawing or erasing.
     virtual void StrokeFinished() {}
 
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 0649430..a56df76 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -1349,6 +1349,12 @@
   return base::Value(url).is_string();
 }
 
+#if BUILDFLAG(ENABLE_PDF_INK2)
+bool PdfViewWebPlugin::IsInAnnotationMode() const {
+  return ink_module_ && ink_module_->enabled();
+}
+#endif  // BUILDFLAG(ENABLE_PDF_INK2)
+
 void PdfViewWebPlugin::SetCaretPosition(const gfx::PointF& position) {
   engine_->SetCaretPosition(FrameToPdfCoordinates(position));
 }
@@ -2072,6 +2078,15 @@
   return engine_->IsPageVisible(page_index);
 }
 
+#if BUILDFLAG(ENABLE_PDF_INK2)
+void PdfViewWebPlugin::OnAnnotationModeToggled(bool enable) {
+  engine_->SetFormHighlight(/*enable_form=*/!enable);
+  if (enable) {
+    engine_->ClearTextSelection();
+  }
+}
+#endif  // BUILDFLAG(ENABLE_PDF_INK2)
+
 void PdfViewWebPlugin::StrokeFinished() {
   base::Value::Dict message;
   message.Set("type", "finishInkStroke");
@@ -2219,6 +2234,11 @@
   if (ink_module_ && ink_module_->HandleInputEvent(event_to_handle)) {
     return true;
   }
+
+  if (IsInAnnotationMode()) {
+    // When in annotation mode, only handle ink input events.
+    return false;
+  }
 #endif
 
   if (engine_->HandleInputEvent(event_to_handle))
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index a2ff052..14e0e5d 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -363,6 +363,9 @@
   void SetSelectedText(const std::string& selected_text) override;
   void SetLinkUnderCursor(const std::string& link_under_cursor) override;
   bool IsValidLink(const std::string& url) override;
+#if BUILDFLAG(ENABLE_PDF_INK2)
+  bool IsInAnnotationMode() const override;
+#endif  // BUILDFLAG(ENABLE_PDF_INK2)
 
   // pdf::mojom::PdfListener:
   void SetCaretPosition(const gfx::PointF& position) override;
@@ -410,6 +413,7 @@
   gfx::Vector2dF GetViewportOriginOffset() override;
   float GetZoom() const override;
   bool IsPageVisible(int page_index) override;
+  void OnAnnotationModeToggled(bool enable) override;
   void StrokeFinished() override;
   void UpdateInkCursorImage(SkBitmap bitmap) override;
   void UpdateThumbnail(int page_index) override;
diff --git a/pdf/pdf_view_web_plugin_unittest.cc b/pdf/pdf_view_web_plugin_unittest.cc
index 778e1496..9cde099 100644
--- a/pdf/pdf_view_web_plugin_unittest.cc
+++ b/pdf/pdf_view_web_plugin_unittest.cc
@@ -2669,6 +2669,20 @@
   EXPECT_EQ(12, plugin_->VisiblePageIndexFromPoint(kPage12Bottom));
   EXPECT_EQ(-1, plugin_->VisiblePageIndexFromPoint(kPageBelowLast));
 }
+
+TEST_F(PdfViewWebPluginInkTest, AnnotationModeSetsFormAndClearsText) {
+  EXPECT_CALL(*engine_ptr_, SetFormHighlight(false));
+  EXPECT_CALL(*engine_ptr_, ClearTextSelection());
+
+  plugin_->OnMessage(CreateSetAnnotationModeMessageForTesting(/*enable=*/true));
+  EXPECT_TRUE(plugin_->IsInAnnotationMode());
+
+  EXPECT_CALL(*engine_ptr_, SetFormHighlight(true));
+
+  plugin_->OnMessage(
+      CreateSetAnnotationModeMessageForTesting(/*enable=*/false));
+  EXPECT_FALSE(plugin_->IsInAnnotationMode());
+}
 #endif  // BUILDFLAG(ENABLE_PDF_INK2)
 
 }  // namespace chrome_pdf
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index ddd2842..063c12df 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -943,6 +943,23 @@
   return client_->IsValidLink(url);
 }
 
+void PDFiumEngine::SetFormHighlight(bool enable_form) {
+  // Restore form highlights.
+  if (enable_form) {
+    FPDF_SetFormFieldHighlightAlpha(form(), kFormHighlightAlpha);
+    return;
+  }
+
+  // Hide form highlights.
+  FPDF_SetFormFieldHighlightAlpha(form(), /*alpha=*/0);
+  KillFormFocus();
+}
+
+void PDFiumEngine::ClearTextSelection() {
+  SelectionChangeInvalidator selection_invalidator(this);
+  selection_.clear();
+}
+
 void PDFiumEngine::ContinueFind(bool case_sensitive) {
   StartFind(current_find_text_, case_sensitive);
 }
@@ -1065,8 +1082,12 @@
 }
 
 void PDFiumEngine::UpdateFocus(bool has_focus) {
+  bool can_focus = !IsReadOnly();
+#if BUILDFLAG(ENABLE_PDF_INK2)
+  can_focus = can_focus && !client_->IsInAnnotationMode();
+#endif  // BUILDFLAG(ENABLE_PDF_INK2)
   base::AutoReset<bool> updating_focus_guard(&updating_focus_, true);
-  if (has_focus && !IsReadOnly()) {
+  if (has_focus && can_focus) {
     UpdateFocusElementType(last_focused_element_type_);
     if (focus_element_type_ == FocusElementType::kPage &&
         PageIndexInBounds(last_focused_page_) &&
@@ -2178,23 +2199,6 @@
   ClearTextSelection();
 }
 
-void PDFiumEngine::SetFormHighlight(bool enable_form) {
-  // Restore form highlights.
-  if (enable_form) {
-    FPDF_SetFormFieldHighlightAlpha(form(), kFormHighlightAlpha);
-    return;
-  }
-
-  // Hide form highlights.
-  FPDF_SetFormFieldHighlightAlpha(form(), /*alpha=*/0);
-  KillFormFocus();
-}
-
-void PDFiumEngine::ClearTextSelection() {
-  SelectionChangeInvalidator selection_invalidator(this);
-  selection_.clear();
-}
-
 void PDFiumEngine::SetDocumentLayout(DocumentLayout::PageSpread page_spread) {
   SaveSelection();
   desired_layout_options_.set_page_spread(page_spread);
@@ -2353,6 +2357,12 @@
   if (IsReadOnly())
     return;
 
+#if BUILDFLAG(ENABLE_PDF_INK2)
+  if (client_->IsInAnnotationMode()) {
+    return;
+  }
+#endif  // BUILDFLAG(ENABLE_PDF_INK2)
+
   if (focus_field_type_ == FocusFieldType::kText) {
     if (PageIndexInBounds(last_focused_page_))
       FORM_SelectAllText(form(), pages_[last_focused_page_]->GetPage());
@@ -2989,7 +2999,7 @@
     }
     FPDF_SetFormFieldHighlightColor(form(), FPDF_FORMFIELD_UNKNOWN,
                                     kFormHighlightColor);
-    FPDF_SetFormFieldHighlightAlpha(form(), kFormHighlightAlpha);
+    SetFormHighlight(true);
 
     if (!client_->IsPrintPreview()) {
       static constexpr FPDF_ANNOTATION_SUBTYPE kFocusableAnnotSubtypes[] = {
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index 2ab92a2..0995a59 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -243,6 +243,8 @@
 
   virtual void SelectAll();
 
+  virtual void ClearTextSelection();
+
   // Gets the list of DocumentAttachmentInfo from the document.
   virtual const std::vector<DocumentAttachmentInfo>&
   GetDocumentAttachmentInfoList() const;
@@ -428,6 +430,9 @@
 
   bool IsValidLink(const std::string& url);
 
+  // Sets whether form highlight should be enabled or cleared.
+  virtual void SetFormHighlight(bool enable_form);
+
  private:
   // This helper class is used to detect the difference in selection between
   // construction and destruction.  At destruction, it invalidates all the
@@ -850,11 +855,6 @@
   // requests the thumbnail for that page.
   void MaybeRequestPendingThumbnail(int page_index);
 
-  // Sets whether form highlight should be enabled or cleared.
-  void SetFormHighlight(bool enable_form);
-
-  void ClearTextSelection();
-
   const raw_ptr<PDFiumEngineClient> client_;
 
   // The current document layout.
diff --git a/pdf/pdfium/pdfium_engine_client.h b/pdf/pdfium/pdfium_engine_client.h
index fc37544..ead035b 100644
--- a/pdf/pdfium/pdfium_engine_client.h
+++ b/pdf/pdfium/pdfium_engine_client.h
@@ -13,6 +13,7 @@
 #include <vector>
 
 #include "base/functional/callback.h"
+#include "pdf/buildflags.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/cursor/mojom/cursor_type.mojom-forward.h"
 #include "ui/base/window_open_disposition.h"
@@ -195,6 +196,11 @@
   // viewers.
   // See https://crbug.com/312882 for an example.
   virtual bool IsValidLink(const std::string& url) = 0;
+
+#if BUILDFLAG(ENABLE_PDF_INK2)
+  // Returns true if the client is in annotation mode.
+  virtual bool IsInAnnotationMode() const = 0;
+#endif  // BUILDFLAG(ENABLE_PDF_INK2)
 };
 
 }  // namespace chrome_pdf
diff --git a/pdf/pdfium/pdfium_engine_unittest.cc b/pdf/pdfium/pdfium_engine_unittest.cc
index fa1e13d..ad430b7 100644
--- a/pdf/pdfium/pdfium_engine_unittest.cc
+++ b/pdf/pdfium/pdfium_engine_unittest.cc
@@ -24,6 +24,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "pdf/buildflags.h"
 #include "pdf/document_attachment_info.h"
 #include "pdf/document_layout.h"
 #include "pdf/document_metadata.h"
@@ -55,7 +56,6 @@
 using ::testing::Invoke;
 using ::testing::IsEmpty;
 using ::testing::NiceMock;
-using ::testing::Not;
 using ::testing::Return;
 using ::testing::StrictMock;
 
@@ -110,9 +110,16 @@
               NavigateTo,
               (const std::string&, WindowOpenDisposition),
               (override));
+  MOCK_METHOD(void,
+              FormFieldFocusChange,
+              (PDFiumEngineClient::FocusFieldType),
+              (override));
   MOCK_METHOD(bool, IsPrintPreview, (), (const override));
   MOCK_METHOD(void, DocumentFocusChanged, (bool), (override));
   MOCK_METHOD(void, SetLinkUnderCursor, (const std::string&), (override));
+#if BUILDFLAG(ENABLE_PDF)
+  MOCK_METHOD(bool, IsInAnnotationMode, (), (const override));
+#endif  // BUILDFLAG(ENABLE_PDF)
 };
 
 }  // namespace
@@ -1058,6 +1065,38 @@
   EXPECT_EQ("Goodbye", engine->GetSelectedText());
 }
 
+TEST_P(PDFiumEngineTest, SetFormHighlight) {
+  NiceMock<MockTestClient> client;
+  std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
+      &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
+  ASSERT_TRUE(engine);
+
+  // Removing form highlights should remove focus.
+  EXPECT_CALL(client, FormFieldFocusChange(
+                          PDFiumEngineClient::FocusFieldType::kNoFocus));
+  engine->SetFormHighlight(false);
+}
+
+TEST_P(PDFiumEngineTest, ClearTextSelection) {
+  NiceMock<MockTestClient> client;
+  std::unique_ptr<PDFiumEngine> engine =
+      InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
+  ASSERT_TRUE(engine);
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
+
+  // Update the plugin size so that all the text is visible by
+  // `SelectionChangeInvalidator`.
+  engine->PluginSizeUpdated({500, 500});
+
+  // Select text.
+  engine->SelectAll();
+  EXPECT_EQ(kSelectTextExpectedText, engine->GetSelectedText());
+
+  // Clear selected text.
+  engine->ClearTextSelection();
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
+}
+
 INSTANTIATE_TEST_SUITE_P(All, PDFiumEngineTest, testing::Bool());
 
 using PDFiumEngineDeathTest = PDFiumEngineTest;
@@ -1771,25 +1810,10 @@
 
 INSTANTIATE_TEST_SUITE_P(All, PDFiumEngineTabbingTest, testing::Bool());
 
-class ReadOnlyTestClient : public TestClient {
- public:
-  ReadOnlyTestClient() = default;
-  ~ReadOnlyTestClient() override = default;
-  ReadOnlyTestClient(const ReadOnlyTestClient&) = delete;
-  ReadOnlyTestClient& operator=(const ReadOnlyTestClient&) = delete;
-
-  // Mock PDFiumEngineClient methods.
-  MOCK_METHOD(void,
-              FormFieldFocusChange,
-              (PDFiumEngineClient::FocusFieldType),
-              (override));
-  MOCK_METHOD(void, SetSelectedText, (const std::string&), (override));
-};
-
 using PDFiumEngineReadOnlyTest = PDFiumTestBase;
 
 TEST_P(PDFiumEngineReadOnlyTest, KillFormFocus) {
-  NiceMock<ReadOnlyTestClient> client;
+  NiceMock<MockTestClient> client;
   std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
       &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
   ASSERT_TRUE(engine);
@@ -1809,10 +1833,11 @@
 }
 
 TEST_P(PDFiumEngineReadOnlyTest, UnselectText) {
-  NiceMock<ReadOnlyTestClient> client;
+  TestClient client;
   std::unique_ptr<PDFiumEngine> engine =
       InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
   ASSERT_TRUE(engine);
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
 
   // Update the plugin size so that all the text is visible by
   // `SelectionChangeInvalidator`.
@@ -1820,14 +1845,72 @@
 
   // Select text before going into read-only mode.
   EXPECT_FALSE(engine->IsReadOnly());
-  EXPECT_CALL(client, SetSelectedText(Not(IsEmpty())));
   engine->SelectAll();
+  EXPECT_EQ(kSelectTextExpectedText, engine->GetSelectedText());
 
   // Setting read-only mode should unselect the text.
-  EXPECT_CALL(client, SetSelectedText(IsEmpty()));
   engine->SetReadOnly(true);
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
 }
 
 INSTANTIATE_TEST_SUITE_P(All, PDFiumEngineReadOnlyTest, testing::Bool());
 
+#if BUILDFLAG(ENABLE_PDF_INK2)
+class AnnotationModeTestClient : public MockTestClient {
+ public:
+  AnnotationModeTestClient() = default;
+  AnnotationModeTestClient(const AnnotationModeTestClient&) = delete;
+  AnnotationModeTestClient& operator=(const AnnotationModeTestClient&) = delete;
+  ~AnnotationModeTestClient() override = default;
+
+  // PDFiumEngineClient overrides:
+  bool IsInAnnotationMode() const override { return annotation_mode_; }
+
+  void set_annotation_mode(bool annotation_mode) {
+    annotation_mode_ = annotation_mode;
+  }
+
+ private:
+  bool annotation_mode_ = false;
+};
+
+using PDFiumEngineAnnotationModeTest = PDFiumTestBase;
+
+TEST_P(PDFiumEngineAnnotationModeTest, KillFormFocus) {
+  NiceMock<AnnotationModeTestClient> client;
+  std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
+      &client, FILE_PATH_LITERAL("annotation_form_fields.pdf"));
+  ASSERT_TRUE(engine);
+
+  client.set_annotation_mode(true);
+
+  // Attempting to focus in annotation mode should once more trigger a killing
+  // of form focus.
+  EXPECT_CALL(client, FormFieldFocusChange(
+                          PDFiumEngineClient::FocusFieldType::kNoFocus));
+  engine->UpdateFocus(true);
+}
+
+TEST_P(PDFiumEngineAnnotationModeTest, CannotSelectText) {
+  NiceMock<AnnotationModeTestClient> client;
+  std::unique_ptr<PDFiumEngine> engine =
+      InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
+  ASSERT_TRUE(engine);
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
+
+  // Update the plugin size so that all the text is visible by
+  // `SelectionChangeInvalidator`.
+  engine->PluginSizeUpdated({500, 500});
+
+  client.set_annotation_mode(true);
+
+  // Attempting to select text should do nothing in annotation mode.
+  engine->SelectAll();
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
+}
+
+INSTANTIATE_TEST_SUITE_P(All, PDFiumEngineAnnotationModeTest, testing::Bool());
+
+#endif  // BUILDFLAG(ENABLE_PDF_INK2)
+
 }  // namespace chrome_pdf
diff --git a/pdf/preview_mode_client.cc b/pdf/preview_mode_client.cc
index 3e517855..0b9cf8c 100644
--- a/pdf/preview_mode_client.cc
+++ b/pdf/preview_mode_client.cc
@@ -13,6 +13,7 @@
 #include "base/functional/callback.h"
 #include "base/notreached.h"
 #include "base/time/time.h"
+#include "pdf/buildflags.h"
 #include "pdf/document_layout.h"
 #include "pdf/loader/url_loader.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -171,4 +172,10 @@
   NOTREACHED();
 }
 
+#if BUILDFLAG(ENABLE_PDF_INK2)
+bool PreviewModeClient::IsInAnnotationMode() const {
+  NOTREACHED();
+}
+#endif  // BUILDFLAG(ENABLE_PDF_INK2)
+
 }  // namespace chrome_pdf
diff --git a/pdf/preview_mode_client.h b/pdf/preview_mode_client.h
index 63a7371..958985a 100644
--- a/pdf/preview_mode_client.h
+++ b/pdf/preview_mode_client.h
@@ -12,6 +12,7 @@
 
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
+#include "pdf/buildflags.h"
 #include "pdf/pdfium/pdfium_engine_client.h"
 
 namespace chrome_pdf {
@@ -73,6 +74,9 @@
   void SetSelectedText(const std::string& selected_text) override;
   void SetLinkUnderCursor(const std::string& link_under_cursor) override;
   bool IsValidLink(const std::string& url) override;
+#if BUILDFLAG(ENABLE_PDF_INK2)
+  bool IsInAnnotationMode() const override;
+#endif  // BUILDFLAG(ENABLE_PDF_INK2)
 
  private:
   const raw_ptr<Client> client_;
diff --git a/pdf/test/test_client.cc b/pdf/test/test_client.cc
index 0230cbd..9576901 100644
--- a/pdf/test/test_client.cc
+++ b/pdf/test/test_client.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/time/time.h"
+#include "pdf/buildflags.h"
 #include "pdf/document_layout.h"
 #include "pdf/loader/url_loader.h"
 #include "pdf/pdfium/pdfium_engine.h"
@@ -71,4 +72,10 @@
   return !url.empty();
 }
 
+#if BUILDFLAG(ENABLE_PDF)
+bool TestClient::IsInAnnotationMode() const {
+  return false;
+}
+#endif  // BUILDFLAG(ENABLE_PDF)
+
 }  // namespace chrome_pdf
diff --git a/pdf/test/test_client.h b/pdf/test/test_client.h
index 2dda168..8db3f536 100644
--- a/pdf/test/test_client.h
+++ b/pdf/test/test_client.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
+#include "pdf/buildflags.h"
 #include "pdf/pdfium/pdfium_engine_client.h"
 
 namespace chrome_pdf {
@@ -43,6 +44,9 @@
   void SetSelectedText(const std::string& selected_text) override;
   void SetLinkUnderCursor(const std::string& link_under_cursor) override;
   bool IsValidLink(const std::string& url) override;
+#if BUILDFLAG(ENABLE_PDF)
+  bool IsInAnnotationMode() const override;
+#endif  // BUILDFLAG(ENABLE_PDF)
 
  private:
   // Not owned. Expected to dangle briefly, as the engine usually is destroyed
diff --git a/pdf/test/test_pdfium_engine.h b/pdf/test/test_pdfium_engine.h
index c6e7c55..b8ef26a 100644
--- a/pdf/test/test_pdfium_engine.h
+++ b/pdf/test/test_pdfium_engine.h
@@ -107,6 +107,10 @@
 
   MOCK_METHOD(void, OnDocumentCanceled, (), (override));
 
+  MOCK_METHOD(void, SetFormHighlight, (bool), (override));
+
+  MOCK_METHOD(void, ClearTextSelection, (), (override));
+
  protected:
   std::vector<DocumentAttachmentInfo>& doc_attachment_info_list() {
     return doc_attachment_info_list_;
diff --git a/remoting/base/fake_oauth_token_getter.cc b/remoting/base/fake_oauth_token_getter.cc
index ba62afd..3fc1a86 100644
--- a/remoting/base/fake_oauth_token_getter.cc
+++ b/remoting/base/fake_oauth_token_getter.cc
@@ -12,15 +12,19 @@
 
 FakeOAuthTokenGetter::FakeOAuthTokenGetter(Status status,
                                            const std::string& user_email,
-                                           const std::string& access_token)
-    : status_(status), user_email_(user_email), access_token_(access_token) {}
+                                           const std::string& access_token,
+                                           const std::string& scopes)
+    : status_(status),
+      user_email_(user_email),
+      access_token_(access_token),
+      scopes_(scopes) {}
 
 FakeOAuthTokenGetter::~FakeOAuthTokenGetter() = default;
 
 void FakeOAuthTokenGetter::CallWithToken(TokenCallback on_access_token) {
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce(std::move(on_access_token), status_,
-                                user_email_, access_token_));
+                                user_email_, access_token_, scopes_));
 }
 
 void FakeOAuthTokenGetter::InvalidateCache() {
diff --git a/remoting/base/fake_oauth_token_getter.h b/remoting/base/fake_oauth_token_getter.h
index ef9bda5..25ae89f7 100644
--- a/remoting/base/fake_oauth_token_getter.h
+++ b/remoting/base/fake_oauth_token_getter.h
@@ -16,7 +16,8 @@
  public:
   FakeOAuthTokenGetter(Status status,
                        const std::string& user_email,
-                       const std::string& access_token);
+                       const std::string& access_token,
+                       const std::string& scopes);
   ~FakeOAuthTokenGetter() override;
 
   // OAuthTokenGetter interface.
@@ -27,6 +28,7 @@
   Status status_;
   std::string user_email_;
   std::string access_token_;
+  std::string scopes_;
 };
 
 }  // namespace remoting
diff --git a/remoting/base/oauth_token_getter.h b/remoting/base/oauth_token_getter.h
index bee7fa6..abe4588 100644
--- a/remoting/base/oauth_token_getter.h
+++ b/remoting/base/oauth_token_getter.h
@@ -27,7 +27,8 @@
 
   typedef base::OnceCallback<void(Status status,
                                   const std::string& user_email,
-                                  const std::string& access_token)>
+                                  const std::string& access_token,
+                                  const std::string& scopes)>
       TokenCallback;
 
   typedef base::RepeatingCallback<void(const std::string& user_email,
diff --git a/remoting/base/oauth_token_getter_impl.cc b/remoting/base/oauth_token_getter_impl.cc
index bc3834b..0a88f36 100644
--- a/remoting/base/oauth_token_getter_impl.cc
+++ b/remoting/base/oauth_token_getter_impl.cc
@@ -82,7 +82,7 @@
 
   // At this point we don't know the email address so we need to fetch it.
   email_discovery_ = true;
-  gaia_oauth_client_->GetUserEmail(access_token, kMaxRetries, this);
+  gaia_oauth_client_->GetTokenInfo(access_token, kMaxRetries, this);
 }
 
 void OAuthTokenGetterImpl::OnRefreshTokenResponse(
@@ -96,26 +96,32 @@
   UpdateAccessToken(access_token, expires_seconds);
 
   if (!authorization_credentials_->is_service_account && !email_verified_) {
-    gaia_oauth_client_->GetUserEmail(access_token, kMaxRetries, this);
+    gaia_oauth_client_->GetTokenInfo(access_token, kMaxRetries, this);
   } else {
     NotifyTokenCallbacks(OAuthTokenGetterImpl::SUCCESS,
-                         authorization_credentials_->login,
-                         oauth_access_token_);
+                         authorization_credentials_->login, oauth_access_token_,
+                         scopes_);
   }
 }
 
-void OAuthTokenGetterImpl::OnGetUserEmailResponse(
-    const std::string& user_email) {
+void OAuthTokenGetterImpl::OnGetTokenInfoResponse(
+    const base::Value::Dict& token_info) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(authorization_credentials_);
-  VLOG(1) << "Received user info.";
+  VLOG(1) << "Received token info.";
+
+  const std::string* user_email = token_info.FindString("email");
+  if (!user_email) {
+    OnOAuthError();
+    return;
+  }
 
   if (email_discovery_) {
-    authorization_credentials_->login = user_email;
+    authorization_credentials_->login = *user_email;
     email_discovery_ = false;
     NotifyUpdatedCallbacks(authorization_credentials_->login,
                            authorization_credentials_->refresh_token);
-  } else if (user_email != authorization_credentials_->login) {
+  } else if (*user_email != authorization_credentials_->login) {
     LOG(ERROR) << "OAuth token and email address do not refer to "
                   "the same account.";
     OnOAuthError();
@@ -124,8 +130,16 @@
 
   email_verified_ = true;
 
+  if (!token_info.FindString("scope")) {
+    LOG(ERROR) << "Missing scopes in token info.";
+    OnOAuthError();
+    return;
+  }
+
+  scopes_ = *token_info.FindString("scope");
   NotifyTokenCallbacks(OAuthTokenGetterImpl::SUCCESS,
-                       authorization_credentials_->login, oauth_access_token_);
+                       authorization_credentials_->login, oauth_access_token_,
+                       scopes_);
 }
 
 void OAuthTokenGetterImpl::UpdateAccessToken(const std::string& access_token,
@@ -143,10 +157,10 @@
   }
 }
 
-void OAuthTokenGetterImpl::NotifyTokenCallbacks(
-    Status status,
-    const std::string& user_email,
-    const std::string& access_token) {
+void OAuthTokenGetterImpl::NotifyTokenCallbacks(Status status,
+                                                const std::string& user_email,
+                                                const std::string& access_token,
+                                                const std::string& scopes) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   SetResponsePending(false);
@@ -155,7 +169,7 @@
   callbacks.swap(pending_callbacks_);
 
   while (!callbacks.empty()) {
-    std::move(callbacks.front()).Run(status, user_email, access_token);
+    std::move(callbacks.front()).Run(status, user_email, access_token, scopes);
     callbacks.pop();
   }
 }
@@ -175,11 +189,12 @@
 
   // Throw away invalid credentials and force a refresh.
   oauth_access_token_.clear();
+  scopes_.clear();
   access_token_expiry_time_ = base::Time();
   email_verified_ = false;
 
   NotifyTokenCallbacks(OAuthTokenGetterImpl::AUTH_ERROR, std::string(),
-                       std::string());
+                       std::string(), std::string());
 }
 
 void OAuthTokenGetterImpl::OnNetworkError(int response_code) {
@@ -187,7 +202,7 @@
   LOG(ERROR) << "Network error when trying to update OAuth token: "
              << response_code;
   NotifyTokenCallbacks(OAuthTokenGetterImpl::NETWORK_ERROR, std::string(),
-                       std::string());
+                       std::string(), std::string());
 }
 
 void OAuthTokenGetterImpl::CallWithToken(TokenCallback on_access_token) {
@@ -215,7 +230,7 @@
       if (!IsResponsePending()) {
         NotifyTokenCallbacks(OAuthTokenGetterImpl::SUCCESS,
                              authorization_credentials_->login,
-                             oauth_access_token_);
+                             oauth_access_token_, scopes_);
       }
     }
   }
@@ -305,7 +320,7 @@
   LOG(ERROR) << "GaiaOAuthClient response timeout";
   gaia_oauth_client_ =
       std::make_unique<gaia::GaiaOAuthClient>(url_loader_factory_);
-  NotifyTokenCallbacks(OAuthTokenGetterImpl::NETWORK_ERROR, {}, {});
+  NotifyTokenCallbacks(OAuthTokenGetterImpl::NETWORK_ERROR, {}, {}, {});
 }
 
 }  // namespace remoting
diff --git a/remoting/base/oauth_token_getter_impl.h b/remoting/base/oauth_token_getter_impl.h
index 565be4c..b154d145 100644
--- a/remoting/base/oauth_token_getter_impl.h
+++ b/remoting/base/oauth_token_getter_impl.h
@@ -57,14 +57,15 @@
                            int expires_seconds) override;
   void OnRefreshTokenResponse(const std::string& access_token,
                               int expires_in_seconds) override;
-  void OnGetUserEmailResponse(const std::string& user_email) override;
+  void OnGetTokenInfoResponse(const base::Value::Dict& token_info) override;
   void OnOAuthError() override;
   void OnNetworkError(int response_code) override;
 
   void UpdateAccessToken(const std::string& access_token, int expires_seconds);
   void NotifyTokenCallbacks(Status status,
                             const std::string& user_email,
-                            const std::string& access_token);
+                            const std::string& access_token,
+                            const std::string& scopes);
   void NotifyUpdatedCallbacks(const std::string& user_email,
                               const std::string& refresh_token);
   void GetOauthTokensFromAuthCode();
@@ -83,6 +84,7 @@
   bool email_verified_ = false;
   bool email_discovery_ = false;
   std::string oauth_access_token_;
+  std::string scopes_;
   base::Time access_token_expiry_time_;
   base::queue<OAuthTokenGetter::TokenCallback> pending_callbacks_;
   std::unique_ptr<base::OneShotTimer> refresh_timer_;
diff --git a/remoting/base/oauth_token_getter_proxy.cc b/remoting/base/oauth_token_getter_proxy.cc
index 6f57133..07e481a7 100644
--- a/remoting/base/oauth_token_getter_proxy.cc
+++ b/remoting/base/oauth_token_getter_proxy.cc
@@ -18,15 +18,16 @@
     scoped_refptr<base::SequencedTaskRunner> original_task_runner,
     OAuthTokenGetter::Status status,
     const std::string& user_email,
-    const std::string& access_token) {
+    const std::string& access_token,
+    const std::string& scopes) {
   if (!original_task_runner->RunsTasksInCurrentSequence()) {
     original_task_runner->PostTask(
         FROM_HERE, base::BindOnce(std::move(on_access_token), status,
-                                  user_email, access_token));
+                                  user_email, access_token, scopes));
     return;
   }
 
-  std::move(on_access_token).Run(status, user_email, access_token);
+  std::move(on_access_token).Run(status, user_email, access_token, scopes);
 }
 
 }  // namespace
diff --git a/remoting/base/oauth_token_getter_proxy_unittest.cc b/remoting/base/oauth_token_getter_proxy_unittest.cc
index 7eb919d2..663dfe22 100644
--- a/remoting/base/oauth_token_getter_proxy_unittest.cc
+++ b/remoting/base/oauth_token_getter_proxy_unittest.cc
@@ -33,7 +33,8 @@
 
   void ResolveCallback(Status status,
                        const std::string& user_email,
-                       const std::string& access_token);
+                       const std::string& access_token,
+                       const std::string& scopes);
 
   void ExpectInvalidateCache();
 
@@ -63,10 +64,11 @@
 
 void FakeOAuthTokenGetter::ResolveCallback(Status status,
                                            const std::string& user_email,
-                                           const std::string& access_token) {
+                                           const std::string& access_token,
+                                           const std::string& scopes) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!on_access_token_.is_null());
-  std::move(on_access_token_).Run(status, user_email, access_token);
+  std::move(on_access_token_).Run(status, user_email, access_token, scopes);
 }
 
 void FakeOAuthTokenGetter::ExpectInvalidateCache() {
@@ -109,11 +111,13 @@
  protected:
   void TestCallWithTokenOnRunnerThread(OAuthTokenGetter::Status status,
                                        const std::string& user_email,
-                                       const std::string& access_token);
+                                       const std::string& access_token,
+                                       const std::string& scopes);
 
   void TestCallWithTokenOnMainThread(OAuthTokenGetter::Status status,
                                      const std::string& user_email,
-                                     const std::string& access_token);
+                                     const std::string& access_token,
+                                     const std::string& scopes);
 
   void ExpectInvalidateCache();
 
@@ -128,15 +132,18 @@
     OAuthTokenGetter::Status status;
     std::string user_email;
     std::string access_token;
+    std::string scopes;
   };
 
   void TestCallWithTokenImpl(OAuthTokenGetter::Status status,
                              const std::string& user_email,
-                             const std::string& access_token);
+                             const std::string& access_token,
+                             const std::string& scopes);
 
   void OnTokenReceived(OAuthTokenGetter::Status status,
                        const std::string& user_email,
-                       const std::string& access_token);
+                       const std::string& access_token,
+                       const std::string& scopes);
 
   std::unique_ptr<TokenCallbackResult> expected_callback_result_;
 
@@ -160,20 +167,23 @@
 void OAuthTokenGetterProxyTest::TestCallWithTokenOnRunnerThread(
     OAuthTokenGetter::Status status,
     const std::string& user_email,
-    const std::string& access_token) {
+    const std::string& access_token,
+    const std::string& scopes) {
   runner_thread_.task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&OAuthTokenGetterProxyTest::TestCallWithTokenImpl,
                      base::Unretained(this),
-                     OAuthTokenGetter::Status::AUTH_ERROR, "email3", "token3"));
+                     OAuthTokenGetter::Status::AUTH_ERROR, user_email,
+                     access_token, scopes));
   runner_thread_.FlushForTesting();
 }
 
 void OAuthTokenGetterProxyTest::TestCallWithTokenOnMainThread(
     OAuthTokenGetter::Status status,
     const std::string& user_email,
-    const std::string& access_token) {
-  TestCallWithTokenImpl(status, user_email, access_token);
+    const std::string& access_token,
+    const std::string& scopes) {
+  TestCallWithTokenImpl(status, user_email, access_token, scopes);
   runner_thread_.FlushForTesting();
   base::RunLoop().RunUntilIdle();
 }
@@ -195,43 +205,46 @@
 void OAuthTokenGetterProxyTest::TestCallWithTokenImpl(
     OAuthTokenGetter::Status status,
     const std::string& user_email,
-    const std::string& access_token) {
+    const std::string& access_token,
+    const std::string& scopes) {
   ASSERT_FALSE(expected_callback_result_);
   expected_callback_result_ = std::make_unique<TokenCallbackResult>();
   expected_callback_result_->status = status;
   expected_callback_result_->user_email = user_email;
   expected_callback_result_->access_token = access_token;
+  expected_callback_result_->scopes = scopes;
   proxy_->CallWithToken(base::BindOnce(
       &OAuthTokenGetterProxyTest::OnTokenReceived, base::Unretained(this)));
   runner_thread_.task_runner()->PostTask(
       FROM_HERE, base::BindOnce(&FakeOAuthTokenGetter::ResolveCallback,
                                 token_getter_->GetWeakPtr(), status, user_email,
-                                access_token));
+                                access_token, scopes));
 }
 
-void OAuthTokenGetterProxyTest::OnTokenReceived(
-    OAuthTokenGetter::Status status,
-    const std::string& user_email,
-    const std::string& access_token) {
+void OAuthTokenGetterProxyTest::OnTokenReceived(OAuthTokenGetter::Status status,
+                                                const std::string& user_email,
+                                                const std::string& access_token,
+                                                const std::string& scopes) {
   ASSERT_TRUE(expected_callback_result_);
   EXPECT_EQ(expected_callback_result_->status, status);
   EXPECT_EQ(expected_callback_result_->user_email, user_email);
   EXPECT_EQ(expected_callback_result_->access_token, access_token);
+  EXPECT_EQ(expected_callback_result_->scopes, scopes);
   expected_callback_result_.reset();
 }
 
 TEST_F(OAuthTokenGetterProxyTest, CallWithTokenOnMainThread) {
   TestCallWithTokenOnMainThread(OAuthTokenGetter::Status::SUCCESS, "email1",
-                                "token1");
+                                "token1", "scope1");
   TestCallWithTokenOnMainThread(OAuthTokenGetter::Status::NETWORK_ERROR,
-                                "email2", "token2");
+                                "email2", "token2", "scope2");
 }
 
 TEST_F(OAuthTokenGetterProxyTest, CallWithTokenOnRunnerThread) {
   TestCallWithTokenOnRunnerThread(OAuthTokenGetter::Status::AUTH_ERROR,
-                                  "email3", "token3");
+                                  "email3", "token3", "scope3");
   TestCallWithTokenOnRunnerThread(OAuthTokenGetter::Status::SUCCESS, "email4",
-                                  "token4");
+                                  "token4", "scope4");
 }
 
 TEST_F(OAuthTokenGetterProxyTest, InvalidateCacheOnMainThread) {
diff --git a/remoting/base/passthrough_oauth_token_getter.cc b/remoting/base/passthrough_oauth_token_getter.cc
index 8369ac6..c2fffff 100644
--- a/remoting/base/passthrough_oauth_token_getter.cc
+++ b/remoting/base/passthrough_oauth_token_getter.cc
@@ -13,8 +13,9 @@
 
 PassthroughOAuthTokenGetter::PassthroughOAuthTokenGetter(
     const std::string& username,
-    const std::string& access_token)
-    : username_(username), access_token_(access_token) {}
+    const std::string& access_token,
+    const std::string& scopes)
+    : username_(username), access_token_(access_token), scopes_(scopes) {}
 
 PassthroughOAuthTokenGetter::~PassthroughOAuthTokenGetter() = default;
 
@@ -23,7 +24,7 @@
   base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce(std::move(on_access_token),
                                 OAuthTokenGetter::Status::SUCCESS, username_,
-                                access_token_));
+                                access_token_, scopes_));
 }
 
 void PassthroughOAuthTokenGetter::InvalidateCache() {
diff --git a/remoting/base/passthrough_oauth_token_getter.h b/remoting/base/passthrough_oauth_token_getter.h
index f7c3e118..3ef0242 100644
--- a/remoting/base/passthrough_oauth_token_getter.h
+++ b/remoting/base/passthrough_oauth_token_getter.h
@@ -17,7 +17,8 @@
   // Caller needs to set them with set_username() and set_access_token().
   PassthroughOAuthTokenGetter();
   PassthroughOAuthTokenGetter(const std::string& username,
-                              const std::string& access_token);
+                              const std::string& access_token,
+                              const std::string& scopes);
   ~PassthroughOAuthTokenGetter() override;
 
   // OAuthTokenGetter overrides.
@@ -33,6 +34,7 @@
  private:
   std::string username_;
   std::string access_token_;
+  std::string scopes_;
 };
 
 }  // namespace remoting
diff --git a/remoting/base/protobuf_http_client.cc b/remoting/base/protobuf_http_client.cc
index 2c3f45f..f1b2760 100644
--- a/remoting/base/protobuf_http_client.cc
+++ b/remoting/base/protobuf_http_client.cc
@@ -45,7 +45,7 @@
 
   if (!request->config().authenticated) {
     DoExecuteRequest(std::move(request), OAuthTokenGetter::Status::SUCCESS, {},
-                     {});
+                     {}, {});
     return;
   }
 
@@ -72,7 +72,8 @@
     std::unique_ptr<ProtobufHttpRequestBase> request,
     OAuthTokenGetter::Status status,
     const std::string& user_email,
-    const std::string& access_token) {
+    const std::string& access_token,
+    const std::string& scopes) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (status != OAuthTokenGetter::Status::SUCCESS) {
diff --git a/remoting/base/protobuf_http_client.h b/remoting/base/protobuf_http_client.h
index 9258f82..af400dd 100644
--- a/remoting/base/protobuf_http_client.h
+++ b/remoting/base/protobuf_http_client.h
@@ -61,7 +61,8 @@
   void DoExecuteRequest(std::unique_ptr<ProtobufHttpRequestBase> request,
                         OAuthTokenGetter::Status status,
                         const std::string& user_email,
-                        const std::string& access_token);
+                        const std::string& access_token,
+                        const std::string& scopes);
 
   void CancelRequest(const PendingRequestListIterator& request_iterator);
 
diff --git a/remoting/base/protobuf_http_client_unittest.cc b/remoting/base/protobuf_http_client_unittest.cc
index e6999341..32a5eff 100644
--- a/remoting/base/protobuf_http_client_unittest.cc
+++ b/remoting/base/protobuf_http_client_unittest.cc
@@ -157,19 +157,19 @@
 void ProtobufHttpClientTest::ExpectCallWithTokenSuccess() {
   EXPECT_CALL(mock_token_getter_, CallWithToken(_))
       .WillOnce(RunOnceCallback<0>(OAuthTokenGetter::Status::SUCCESS, "",
-                                   kFakeAccessToken));
+                                   kFakeAccessToken, ""));
 }
 
 void ProtobufHttpClientTest::ExpectCallWithTokenAuthError() {
   EXPECT_CALL(mock_token_getter_, CallWithToken(_))
       .WillOnce(
-          RunOnceCallback<0>(OAuthTokenGetter::Status::AUTH_ERROR, "", ""));
+          RunOnceCallback<0>(OAuthTokenGetter::Status::AUTH_ERROR, "", "", ""));
 }
 
 void ProtobufHttpClientTest::ExpectCallWithTokenNetworkError() {
   EXPECT_CALL(mock_token_getter_, CallWithToken(_))
-      .WillOnce(
-          RunOnceCallback<0>(OAuthTokenGetter::Status::NETWORK_ERROR, "", ""));
+      .WillOnce(RunOnceCallback<0>(OAuthTokenGetter::Status::NETWORK_ERROR, "",
+                                   "", ""));
 }
 
 // Unary request tests.
@@ -359,7 +359,7 @@
   client_.CancelPendingRequests();
   ASSERT_TRUE(token_callback);
   std::move(token_callback)
-      .Run(OAuthTokenGetter::Status::SUCCESS, "", kFakeAccessToken);
+      .Run(OAuthTokenGetter::Status::SUCCESS, "", kFakeAccessToken, "");
 
   // Verify no request.
   ASSERT_FALSE(test_url_loader_factory_.IsPending(kTestFullUrl));
diff --git a/remoting/base/telemetry_log_writer_unittest.cc b/remoting/base/telemetry_log_writer_unittest.cc
index 91aa1c4..0f5ae7de 100644
--- a/remoting/base/telemetry_log_writer_unittest.cc
+++ b/remoting/base/telemetry_log_writer_unittest.cc
@@ -131,6 +131,7 @@
   TelemetryLogWriter log_writer_{
       std::make_unique<FakeOAuthTokenGetter>(OAuthTokenGetter::SUCCESS,
                                              "dummy",
+                                             "dummy",
                                              "dummy")};
 
  private:
diff --git a/remoting/host/heartbeat_sender_unittest.cc b/remoting/host/heartbeat_sender_unittest.cc
index 13d3aec..ef011f3a 100644
--- a/remoting/host/heartbeat_sender_unittest.cc
+++ b/remoting/host/heartbeat_sender_unittest.cc
@@ -45,6 +45,7 @@
 constexpr char kOAuthAccessToken[] = "fake_access_token";
 constexpr char kHostId[] = "fake_host_id";
 constexpr char kUserEmail[] = "fake_user@domain.com";
+constexpr char kScopes[] = "fake_scope";
 
 constexpr char kFtlId[] = "fake_user@domain.com/chromoting_ftl_abc123";
 
@@ -200,7 +201,8 @@
   std::unique_ptr<HeartbeatSender> heartbeat_sender_;
 
   FakeOAuthTokenGetter oauth_token_getter_{OAuthTokenGetter::Status::SUCCESS,
-                                           kUserEmail, kOAuthAccessToken};
+                                           kUserEmail, kOAuthAccessToken,
+                                           kScopes};
 };
 
 TEST_F(HeartbeatSenderTest, SendHeartbeat) {
diff --git a/remoting/host/it2me/it2me_native_messaging_host.cc b/remoting/host/it2me/it2me_native_messaging_host.cc
index 7fbe3ae8..adb26d9 100644
--- a/remoting/host/it2me/it2me_native_messaging_host.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host.cc
@@ -145,20 +145,21 @@
       std::make_unique<It2MeHost::DeferredConnectContext>();
   connection_context->use_ftl_signaling = true;
   connection_context->signal_strategy = std::make_unique<FtlSignalStrategy>(
-      std::make_unique<PassthroughOAuthTokenGetter>(username, access_token),
+      std::make_unique<PassthroughOAuthTokenGetter>(username, access_token, ""),
       host_context->url_loader_factory(),
       std::make_unique<FtlSupportHostDeviceIdProvider>(device_id));
   connection_context->ftl_device_id = std::move(device_id);
   connection_context->register_request =
       std::make_unique<RemotingRegisterSupportHostRequest>(
-          std::make_unique<PassthroughOAuthTokenGetter>(username, access_token),
+          std::make_unique<PassthroughOAuthTokenGetter>(username, access_token,
+                                                        ""),
           host_context->url_loader_factory());
   connection_context->log_to_server = std::make_unique<RemotingLogToServer>(
       ServerLogEntry::IT2ME,
-      std::make_unique<PassthroughOAuthTokenGetter>(username, access_token),
+      std::make_unique<PassthroughOAuthTokenGetter>(username, access_token, ""),
       host_context->url_loader_factory());
   connection_context->oauth_token_getter =
-      std::make_unique<PassthroughOAuthTokenGetter>(username, access_token);
+      std::make_unique<PassthroughOAuthTokenGetter>(username, access_token, "");
   return connection_context;
 }
 
diff --git a/remoting/host/remoting_register_support_host_request_unittest.cc b/remoting/host/remoting_register_support_host_request_unittest.cc
index 61fe7d6..9f411b7 100644
--- a/remoting/host/remoting_register_support_host_request_unittest.cc
+++ b/remoting/host/remoting_register_support_host_request_unittest.cc
@@ -96,7 +96,7 @@
         std::make_unique<RemotingRegisterSupportHostRequest>(
             std::make_unique<FakeOAuthTokenGetter>(
                 OAuthTokenGetter::Status::SUCCESS, "fake_email",
-                "fake_access_token"),
+                "fake_access_token", "fake_scopes"),
             nullptr);
 
     auto register_host_client =
diff --git a/remoting/ios/facade/host_list_service_unittest.mm b/remoting/ios/facade/host_list_service_unittest.mm
index c386b38..f3b6786 100644
--- a/remoting/ios/facade/host_list_service_unittest.mm
+++ b/remoting/ios/facade/host_list_service_unittest.mm
@@ -67,7 +67,7 @@
   base::test::TaskEnvironment task_environment_;
   ProtobufHttpTestResponder test_responder_;
   FakeOAuthTokenGetter oauth_token_getter_{OAuthTokenGetter::Status::SUCCESS,
-                                           "", ""};
+                                           "", "", ""};
   HostListService host_list_service_;
 
   int on_fetch_failed_call_count_ = 0;
diff --git a/remoting/ios/facade/ios_oauth_token_getter.mm b/remoting/ios/facade/ios_oauth_token_getter.mm
index 3db00b3d..e7beea8 100644
--- a/remoting/ios/facade/ios_oauth_token_getter.mm
+++ b/remoting/ios/facade/ios_oauth_token_getter.mm
@@ -17,28 +17,6 @@
 IosOauthTokenGetter::~IosOauthTokenGetter() {}
 
 void IosOauthTokenGetter::CallWithToken(TokenCallback on_access_token) {
-  __block TokenCallback block_callback = std::move(on_access_token);
-  [RemotingService.instance.authentication
-      callbackWithAccessToken:^(RemotingAuthenticationStatus status,
-                                NSString* userEmail, NSString* accessToken) {
-        Status oauth_status;
-        switch (status) {
-          case RemotingAuthenticationStatusSuccess:
-            oauth_status = Status::SUCCESS;
-            break;
-          case RemotingAuthenticationStatusAuthError:
-            oauth_status = Status::AUTH_ERROR;
-            break;
-          case RemotingAuthenticationStatusNetworkError:
-            oauth_status = Status::NETWORK_ERROR;
-            break;
-          default:
-            NOTREACHED_IN_MIGRATION();
-        }
-        std::move(block_callback)
-            .Run(oauth_status, base::SysNSStringToUTF8(userEmail),
-                 base::SysNSStringToUTF8(accessToken));
-      }];
 }
 
 void IosOauthTokenGetter::InvalidateCache() {
diff --git a/remoting/ios/facade/remoting_oauth_authentication.mm b/remoting/ios/facade/remoting_oauth_authentication.mm
index 67b83f5..2ae047b6 100644
--- a/remoting/ios/facade/remoting_oauth_authentication.mm
+++ b/remoting/ios/facade/remoting_oauth_authentication.mm
@@ -166,7 +166,8 @@
   if (_tokenGetter) {
     _tokenGetter->CallWithToken(base::BindOnce(
         ^(remoting::OAuthTokenGetter::Status status,
-          const std::string& user_email, const std::string& access_token) {
+          const std::string& user_email, const std::string& access_token,
+          const std::string& scopes) {
           onAccessToken(oauthStatusToRemotingAuthenticationStatus(status),
                         base::SysUTF8ToNSString(user_email),
                         base::SysUTF8ToNSString(access_token));
diff --git a/remoting/signaling/ftl_messaging_client_unittest.cc b/remoting/signaling/ftl_messaging_client_unittest.cc
index 2885e924a..11050a76 100644
--- a/remoting/signaling/ftl_messaging_client_unittest.cc
+++ b/remoting/signaling/ftl_messaging_client_unittest.cc
@@ -154,7 +154,8 @@
 
  protected:
   ProtobufHttpTestResponder test_responder_;
-  FakeOAuthTokenGetter token_getter_{OAuthTokenGetter::Status::SUCCESS, "", ""};
+  FakeOAuthTokenGetter token_getter_{OAuthTokenGetter::Status::SUCCESS, "", "",
+                                     ""};
   std::unique_ptr<FtlMessagingClient> messaging_client_;
   raw_ptr<MockMessageReceptionChannel> mock_message_reception_channel_;
 
diff --git a/remoting/signaling/ftl_signal_strategy.cc b/remoting/signaling/ftl_signal_strategy.cc
index 605f2be..388abd66 100644
--- a/remoting/signaling/ftl_signal_strategy.cc
+++ b/remoting/signaling/ftl_signal_strategy.cc
@@ -55,7 +55,8 @@
   // Methods are called in the order below when Connect() is called.
   void OnGetOAuthTokenResponse(OAuthTokenGetter::Status status,
                                const std::string& user_email,
-                               const std::string& access_token);
+                               const std::string& access_token,
+                               const std::string& scopes);
   void OnSignInGaiaResponse(const ProtobufHttpStatus& status);
   void StartReceivingMessages();
   void OnReceiveMessagesStreamStarted();
@@ -242,7 +243,8 @@
 void FtlSignalStrategy::Core::OnGetOAuthTokenResponse(
     OAuthTokenGetter::Status status,
     const std::string& user_email,
-    const std::string& access_token) {
+    const std::string& access_token,
+    const std::string& scopes) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (status != OAuthTokenGetter::Status::SUCCESS) {
     switch (status) {
diff --git a/remoting/signaling/ftl_signal_strategy_unittest.cc b/remoting/signaling/ftl_signal_strategy_unittest.cc
index 61276bb..42fd7e0 100644
--- a/remoting/signaling/ftl_signal_strategy_unittest.cc
+++ b/remoting/signaling/ftl_signal_strategy_unittest.cc
@@ -36,6 +36,7 @@
 constexpr char kFakeLocalUsername[] = "fake_local_user@domain.com";
 constexpr char kFakeRemoteUsername[] = "fake_remote_user@domain.com";
 constexpr char kFakeOAuthToken[] = "fake_oauth_token";
+constexpr char kFakeScopes[] = "fake_scopes";
 constexpr char kFakeFtlAuthToken[] = "fake_auth_token";
 constexpr char kFakeLocalRegistrationId[] = "fake_local_registration_id";
 constexpr char kFakeRemoteRegistrationId[] = "fake_remote_registration_id";
@@ -216,7 +217,7 @@
   void ExpectGetOAuthTokenFails(OAuthTokenGetter::Status status) {
     EXPECT_CALL(*token_getter_, CallWithToken(_))
         .WillOnce([=](OAuthTokenGetter::TokenCallback token_callback) {
-          std::move(token_callback).Run(status, {}, {});
+          std::move(token_callback).Run(status, {}, {}, {});
         });
   }
 
@@ -225,7 +226,7 @@
         .WillOnce([](OAuthTokenGetter::TokenCallback token_callback) {
           std::move(token_callback)
               .Run(OAuthTokenGetter::SUCCESS, kFakeLocalUsername,
-                   kFakeOAuthToken);
+                   kFakeOAuthToken, kFakeScopes);
         });
   }
 
diff --git a/remoting/signaling/remoting_log_to_server_unittest.cc b/remoting/signaling/remoting_log_to_server_unittest.cc
index 8ee3439..52b15bc6 100644
--- a/remoting/signaling/remoting_log_to_server_unittest.cc
+++ b/remoting/signaling/remoting_log_to_server_unittest.cc
@@ -54,7 +54,8 @@
       ServerLogEntry::ME2ME,
       std::make_unique<FakeOAuthTokenGetter>(OAuthTokenGetter::SUCCESS,
                                              "fake_email",
-                                             "fake_access_token"),
+                                             "fake_access_token",
+                                             "fake_scope"),
       nullptr};
 };
 
diff --git a/remoting/test/it2me_cli_host.cc b/remoting/test/it2me_cli_host.cc
index b0e63b39..2e01156 100644
--- a/remoting/test/it2me_cli_host.cc
+++ b/remoting/test/it2me_cli_host.cc
@@ -171,7 +171,8 @@
 
 void It2MeCliHost::StartCRDHostAndGetCode(OAuthTokenGetter::Status status,
                                           const std::string& user_email,
-                                          const std::string& access_token) {
+                                          const std::string& access_token,
+                                          const std::string& scopes) {
   DCHECK(!host_);
 
   // Store all parameters for future connect call.
diff --git a/remoting/test/it2me_cli_host.h b/remoting/test/it2me_cli_host.h
index 2ce44e1..bf404751 100644
--- a/remoting/test/it2me_cli_host.h
+++ b/remoting/test/it2me_cli_host.h
@@ -51,7 +51,8 @@
 
   void StartCRDHostAndGetCode(OAuthTokenGetter::Status status,
                               const std::string& user_email,
-                              const std::string& access_token);
+                              const std::string& access_token,
+                              const std::string& scopes);
 
   // Shuts down host in a separate task.
   void ShutdownHost();
diff --git a/remoting/test/test_oauth_token_getter.cc b/remoting/test/test_oauth_token_getter.cc
index a1dc8497..a38b8579 100644
--- a/remoting/test/test_oauth_token_getter.cc
+++ b/remoting/test/test_oauth_token_getter.cc
@@ -94,7 +94,7 @@
   } else {
     VLOG(0) << "Reusing access token: " << access_token;
     token_getter_ = std::make_unique<FakeOAuthTokenGetter>(
-        OAuthTokenGetter::Status::SUCCESS, user_email, access_token);
+        OAuthTokenGetter::Status::SUCCESS, user_email, access_token, "");
   }
   std::move(on_done).Run();
 }
@@ -192,7 +192,8 @@
 
 void TestOAuthTokenGetter::OnAccessToken(OAuthTokenGetter::Status status,
                                          const std::string& user_email,
-                                         const std::string& access_token) {
+                                         const std::string& access_token,
+                                         const std::string& scopes) {
   is_authenticating_ = false;
   if (status != OAuthTokenGetter::Status::SUCCESS) {
     fprintf(stderr,
diff --git a/remoting/test/test_oauth_token_getter.h b/remoting/test/test_oauth_token_getter.h
index 6e2b74fa..f3c944d 100644
--- a/remoting/test/test_oauth_token_getter.h
+++ b/remoting/test/test_oauth_token_getter.h
@@ -69,7 +69,8 @@
 
   void OnAccessToken(OAuthTokenGetter::Status status,
                      const std::string& user_email,
-                     const std::string& access_token);
+                     const std::string& access_token,
+                     const std::string& scopes);
 
   void RunAuthenticationDoneCallbacks();
 
diff --git a/remoting/test/test_token_storage.cc b/remoting/test/test_token_storage.cc
index 481cf1d2..241f7cd 100644
--- a/remoting/test/test_token_storage.cc
+++ b/remoting/test/test_token_storage.cc
@@ -25,6 +25,7 @@
 constexpr char kRefreshTokenKey[] = "refresh_token";
 constexpr char kUserEmailKey[] = "user_email";
 constexpr char kAccessTokenKey[] = "access_token";
+constexpr char kScopesKey[] = "scopes";
 constexpr char kDeviceIdKey[] = "device_id";
 }  // namespace
 
@@ -50,6 +51,8 @@
   bool StoreUserEmail(const std::string& user_email) override;
   std::string FetchAccessToken() override;
   bool StoreAccessToken(const std::string& access_token) override;
+  std::string FetchScopes() override;
+  bool StoreScopes(const std::string& scopes) override;
   std::string FetchDeviceId() override;
   bool StoreDeviceId(const std::string& device_id) override;
 
@@ -105,6 +108,14 @@
   return StoreTokenForKey(kAccessTokenKey, access_token);
 }
 
+std::string TestTokenStorageOnDisk::FetchScopes() {
+  return FetchTokenFromKey(kScopesKey);
+}
+
+bool TestTokenStorageOnDisk::StoreScopes(const std::string& scopes) {
+  return StoreTokenForKey(kScopesKey, scopes);
+}
+
 std::string TestTokenStorageOnDisk::FetchDeviceId() {
   return FetchTokenFromKey(kDeviceIdKey);
 }
diff --git a/remoting/test/test_token_storage.h b/remoting/test/test_token_storage.h
index 7bf0643..515f8c850 100644
--- a/remoting/test/test_token_storage.h
+++ b/remoting/test/test_token_storage.h
@@ -33,6 +33,9 @@
   virtual std::string FetchAccessToken() = 0;
   virtual bool StoreAccessToken(const std::string& access_token) = 0;
 
+  virtual std::string FetchScopes() = 0;
+  virtual bool StoreScopes(const std::string& scopes) = 0;
+
   // Returns a TestTokenStorage which reads/writes to a user specific token
   // file on the local disk.
   static std::unique_ptr<TestTokenStorage> OnDisk(
diff --git a/services/device/compute_pressure/pressure_manager_impl_unittest.cc b/services/device/compute_pressure/pressure_manager_impl_unittest.cc
index ca89778..72b9a1a 100644
--- a/services/device/compute_pressure/pressure_manager_impl_unittest.cc
+++ b/services/device/compute_pressure/pressure_manager_impl_unittest.cc
@@ -111,7 +111,8 @@
     fake_cpu_probe->SetLastSample(system_cpu::CpuSample{0.63});
     auto* probes_manager = manager_impl_->GetProbesManagerForTesting();
     probes_manager->set_cpu_probe_manager(CpuProbeManager::CreateForTesting(
-        std::move(fake_cpu_probe), probes_manager->sampling_interval(),
+        std::move(fake_cpu_probe),
+        probes_manager->sampling_interval_for_testing(),
         probes_manager->cpu_probe_sampling_callback()));
     manager_.reset();
     manager_impl_->Bind(manager_.BindNewPipeAndPassReceiver());
@@ -298,12 +299,15 @@
   // PressureState::kFair.
   EXPECT_EQ(client.updates()[0].state, mojom::PressureState::kFair);
 
-  ASSERT_EQ(virtual_client.updates().size(), 1u);
-  EXPECT_EQ(virtual_client.updates()[0].source, mojom::PressureSource::kCpu);
-  EXPECT_EQ(virtual_client.updates()[0].state, mojom::PressureState::kCritical);
+  // Virtual probes run faster than real ones, so we might have more than one
+  // update.
+  ASSERT_FALSE(virtual_client.updates().empty());
+  for (const auto& update : virtual_client.updates()) {
+    EXPECT_EQ(update.source, mojom::PressureSource::kCpu);
+    EXPECT_EQ(update.state, mojom::PressureState::kCritical);
 
-  EXPECT_NE(client.updates()[0].timestamp,
-            virtual_client.updates()[0].timestamp);
+    EXPECT_NE(client.updates()[0].timestamp, update.timestamp);
+  }
 }
 
 TEST_F(PressureManagerImplTest, UpdateVirtualClientWithNoVirtualClient) {
diff --git a/services/device/compute_pressure/probes_manager.cc b/services/device/compute_pressure/probes_manager.cc
index b2dcadd..916590ad 100644
--- a/services/device/compute_pressure/probes_manager.cc
+++ b/services/device/compute_pressure/probes_manager.cc
@@ -63,7 +63,7 @@
   }
 }
 
-base::TimeDelta ProbesManager::sampling_interval() const {
+base::TimeDelta ProbesManager::sampling_interval_for_testing() const {
   return sampling_interval_;
 }
 
diff --git a/services/device/compute_pressure/probes_manager.h b/services/device/compute_pressure/probes_manager.h
index 8345f47..0cbd2e6 100644
--- a/services/device/compute_pressure/probes_manager.h
+++ b/services/device/compute_pressure/probes_manager.h
@@ -35,7 +35,7 @@
   void RegisterClientRemote(mojo::Remote<mojom::PressureClient> client,
                             mojom::PressureSource source);
 
-  base::TimeDelta sampling_interval() const;
+  base::TimeDelta sampling_interval_for_testing() const;
 
  protected:
   CpuProbeManager* cpu_probe_manager() const;
diff --git a/services/device/compute_pressure/virtual_probes_manager.cc b/services/device/compute_pressure/virtual_probes_manager.cc
index 9bcb512..37e8dbe 100644
--- a/services/device/compute_pressure/virtual_probes_manager.cc
+++ b/services/device/compute_pressure/virtual_probes_manager.cc
@@ -12,6 +12,17 @@
 
 namespace device {
 
+namespace {
+
+// We need a shorter interval when using virtual probes because some rate
+// obfuscation web tests require 50-100 updates to trigger the mitigation, and
+// sampling once per second (per PressureManagerImpl::kDefaultSamplingInterval)
+// takes too long.
+constexpr base::TimeDelta kVirtualProbeSamplingInterval =
+    base::Milliseconds(100);
+
+}  // namespace
+
 VirtualProbesManager::VirtualProbesManager(base::TimeDelta sampling_interval)
     : ProbesManager(sampling_interval) {}
 
@@ -29,7 +40,7 @@
     case mojom::PressureSource::kCpu: {
       std::unique_ptr<CpuProbeManager> manager =
           metadata->available
-              ? VirtualCpuProbeManager::Create(sampling_interval(),
+              ? VirtualCpuProbeManager::Create(kVirtualProbeSamplingInterval,
                                                cpu_probe_sampling_callback())
               : nullptr;
       set_cpu_probe_manager(std::move(manager));
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 850576e..5ab4e23a 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -13389,6 +13389,9 @@
     "scripts": [
       {
         "name": "test_traffic_annotation_auditor",
+        "precommit_args": [
+          "--no-update-sheet"
+        ],
         "script": "test_traffic_annotation_auditor.py"
       }
     ]
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index b2c238f..91967f6 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1202,6 +1202,43 @@
         "args": [
           "--as-root",
           "--validator_type=none",
+          "../../media/test/data/test-25fps.vp8",
+          "../../media/test/data/test-25fps.vp8.json",
+          "--magic-vm-cache=magic_cros_vm_cache"
+        ],
+        "ci_only": true,
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "video_decode_accelerator_tests_v4l2_vp8",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "kvm": "1",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests"
+          },
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": {
+              "caches": "cros_vm"
+            }
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "video_decode_accelerator_tests",
+        "test_id_prefix": "ninja://media/gpu/test:video_decode_accelerator_tests/"
+      },
+      {
+        "args": [
+          "--as-root",
+          "--validator_type=none",
           "../../media/test/data/test-25fps.vp9",
           "../../media/test/data/test-25fps.vp9.json",
           "--magic-vm-cache=magic_cros_vm_cache"
@@ -1211,7 +1248,7 @@
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "video_decode_accelerator_tests_v4l2",
+        "name": "video_decode_accelerator_tests_v4l2_vp9",
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 1f69f63..7f1f1f6 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -22044,7 +22044,6 @@
           "--xcode-build-version",
           "16a5230g"
         ],
-        "experiment_percentage": 100,
         "merge": {
           "args": [
             "--verbose"
@@ -38282,6 +38281,9 @@
     "scripts": [
       {
         "name": "test_traffic_annotation_auditor",
+        "precommit_args": [
+          "--no-update-sheet"
+        ],
         "script": "test_traffic_annotation_auditor.py"
       }
     ]
@@ -39169,6 +39171,9 @@
     "scripts": [
       {
         "name": "test_traffic_annotation_auditor",
+        "precommit_args": [
+          "--no-update-sheet"
+        ],
         "script": "test_traffic_annotation_auditor.py"
       }
     ]
@@ -45031,6 +45036,9 @@
     "scripts": [
       {
         "name": "test_traffic_annotation_auditor",
+        "precommit_args": [
+          "--no-update-sheet"
+        ],
         "script": "test_traffic_annotation_auditor.py"
       }
     ]
diff --git a/testing/buildbot/generate_buildbot_json.py b/testing/buildbot/generate_buildbot_json.py
index 7a25a4d1..9bf05ade 100755
--- a/testing/buildbot/generate_buildbot_json.py
+++ b/testing/buildbot/generate_buildbot_json.py
@@ -828,6 +828,9 @@
         'name': test_config['name'],
         'script': test_config['script'],
     }
+    for a in ('args', 'precommit_args', 'non_precommit_args'):
+      if a in test_config:
+        result[a] = test_config[a]
     result = self.apply_common_transformations(waterfall,
                                                tester_name,
                                                tester_config,
diff --git a/testing/buildbot/generate_buildbot_json_unittest.py b/testing/buildbot/generate_buildbot_json_unittest.py
index aa8970fc..e81df1ea 100755
--- a/testing/buildbot/generate_buildbot_json_unittest.py
+++ b/testing/buildbot/generate_buildbot_json_unittest.py
@@ -959,6 +959,9 @@
     'foo_scripts': {
       'foo_test': {
         'script': 'foo.py',
+        'args': ['common-arg'],
+        'precommit_args': ['precommit-arg'],
+        'non_precommit_args': ['non-precommit-arg'],
       },
       'bar_test': {
         'script': 'bar.py',
diff --git a/testing/buildbot/tryserver.chromium.chromiumos.json b/testing/buildbot/tryserver.chromium.chromiumos.json
deleted file mode 100644
index 7362cf15..0000000
--- a/testing/buildbot/tryserver.chromium.chromiumos.json
+++ /dev/null
@@ -1,51 +0,0 @@
-{
-  "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
-  "AAAAA2 See generate_buildbot_json.py to make changes": {},
-  "lacros-amd64-generic-rel-non-skylab": {
-    "additional_compile_targets": [
-      "chrome"
-    ],
-    "skylab_tests": [
-      {
-        "autotest_name": "chromium",
-        "bucket": "chromiumos-image-archive",
-        "cros_board": "volteer",
-        "cros_model": "voxel",
-        "dut_pool": "chromium",
-        "name": "chromeos_integration_tests VOLTEER_PUBLIC_LKGM",
-        "public_builder": "cros_test_platform_public",
-        "public_builder_bucket": "testplatform-public",
-        "run_cft": true,
-        "shard_level_retries_on_ctp": 1,
-        "test": "chromeos_integration_tests",
-        "test_id_prefix": "ninja://chrome/test:chromeos_integration_tests/",
-        "use_lkgm": true,
-        "variant_id": "VOLTEER_PUBLIC_LKGM"
-      },
-      {
-        "autotest_name": "tast.lacros-from-gcs",
-        "bucket": "chromiumos-image-archive",
-        "cros_board": "volteer",
-        "cros_model": "voxel",
-        "dut_pool": "chromium",
-        "name": "lacros_all_tast_tests VOLTEER_PUBLIC_LKGM",
-        "public_builder": "cros_test_platform_public",
-        "public_builder_bucket": "testplatform-public",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "run_cft": true,
-        "shard_level_retries_on_ctp": 1,
-        "shards": 2,
-        "tast_expr": "(\"group:mainline\" && (\"dep:lacros_stable\" || \"dep:lacros\") && !informational)",
-        "test": "lacros_all_tast_tests",
-        "test_id_prefix": "ninja://chromeos/lacros:lacros_all_tast_tests/",
-        "test_level_retries": 2,
-        "timeout_sec": 10800,
-        "use_lkgm": true,
-        "variant_id": "VOLTEER_PUBLIC_LKGM"
-      }
-    ]
-  }
-}
diff --git a/testing/buildbot/unittest_expectations/test_script/chromium.test.json b/testing/buildbot/unittest_expectations/test_script/chromium.test.json
index 5feca18..4f883132e 100644
--- a/testing/buildbot/unittest_expectations/test_script/chromium.test.json
+++ b/testing/buildbot/unittest_expectations/test_script/chromium.test.json
@@ -4,7 +4,16 @@
   "Fake Tester": {
     "scripts": [
       {
+        "args": [
+          "common-arg"
+        ],
         "name": "foo_test",
+        "non_precommit_args": [
+          "non-precommit-arg"
+        ],
+        "precommit_args": [
+          "precommit-arg"
+        ],
         "script": "foo.py"
       }
     ]
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 783426ee..96e5760 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -561,6 +561,21 @@
             ]
         }
     ],
+    "AndroidPdfInline": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AndroidOpenPdfInline"
+                    ]
+                }
+            ]
+        }
+    ],
     "AndroidSandboxRendererProcessPolicy": [
         {
             "platforms": [
@@ -1117,24 +1132,6 @@
             ]
         }
     ],
-    "ArcVmMemorySize": [
-        {
-            "platforms": [
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "EnabledVmMemSize-500_20231031",
-                    "params": {
-                        "shift_mib": "-500"
-                    },
-                    "enable_features": [
-                        "ArcVmMemorySize"
-                    ]
-                }
-            ]
-        }
-    ],
     "ArcVmS2Idle": [
         {
             "platforms": [
@@ -1161,12 +1158,14 @@
                     "name": "VirtualSwapEnabled_25PercentVmSize_10SecondSwapInterval",
                     "params": {
                         "guest_reclaim_enabled": "true",
+                        "ram_percentage": "75",
                         "size_percentage": "25",
                         "virtual_swap_enabled": "true",
                         "virtual_swap_interval_ms": "10000"
                     },
                     "enable_features": [
-                        "ArcGuestZram"
+                        "ArcGuestZram",
+                        "ArcVmMemorySize"
                     ]
                 }
             ]
@@ -12039,21 +12038,6 @@
             ]
         }
     ],
-    "IOSNewSyncOptInIllustration": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "NewSyncOptInIllustration"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSOverflowMenuCustomization": [
         {
             "platforms": [
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/3ppFetch.template b/third_party/android_deps/buildSrc/src/main/groovy/3ppFetch.template
index fc969e1..5fc5c7f 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/3ppFetch.template
+++ b/third_party/android_deps/buildSrc/src/main/groovy/3ppFetch.template
@@ -19,6 +19,5 @@
                          version_override=<%= dependency.overrideLatest ? "'${dependency.version}'" : "None" %>,
                          version_filter=<%= dependency.versionFilter ? "r'${dependency.versionFilter}'" : "None" %>)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_android_support_support_annotations/3pp/fetch.py b/third_party/android_deps/libs/com_android_support_support_annotations/3pp/fetch.py
index b81bfcb9..87ef99c 100755
--- a/third_party/android_deps/libs/com_android_support_support_annotations/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_android_support_support_annotations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_android_tools_common/3pp/fetch.py b/third_party/android_deps/libs/com_android_tools_common/3pp/fetch.py
index 9d27321..9d27a3c4 100755
--- a/third_party/android_deps/libs/com_android_tools_common/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_android_tools_common/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_android_tools_layoutlib_layoutlib_api/3pp/fetch.py b/third_party/android_deps/libs/com_android_tools_layoutlib_layoutlib_api/3pp/fetch.py
index 02892cc..75622b67 100755
--- a/third_party/android_deps/libs/com_android_tools_layoutlib_layoutlib_api/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_android_tools_layoutlib_layoutlib_api/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_android_tools_sdk_common/3pp/fetch.py b/third_party/android_deps/libs/com_android_tools_sdk_common/3pp/fetch.py
index 6ed39a9..3848f6b 100755
--- a/third_party/android_deps/libs/com_android_tools_sdk_common/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_android_tools_sdk_common/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_annotations/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_annotations/3pp/fetch.py
index 09b006e..8508c40 100755
--- a/third_party/android_deps/libs/com_google_android_annotations/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_annotations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_apps_common_testing_accessibility_framework_accessibility_test_framework/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_apps_common_testing_accessibility_framework_accessibility_test_framework/3pp/fetch.py
index 370c22e5..4672e0e0 100755
--- a/third_party/android_deps/libs/com_google_android_apps_common_testing_accessibility_framework_accessibility_test_framework/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_apps_common_testing_accessibility_framework_accessibility_test_framework/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_datatransport_transport_api/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_datatransport_transport_api/3pp/fetch.py
index 0f84b31..55c7b8d 100755
--- a/third_party/android_deps/libs/com_google_android_datatransport_transport_api/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_datatransport_transport_api/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_auth/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_auth/3pp/fetch.py
index f586feb..300c7c1 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_auth/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_auth/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_auth_api_phone/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_auth_api_phone/3pp/fetch.py
index 321895b..4dd84a5 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_auth_api_phone/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_auth_api_phone/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_auth_base/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_auth_base/3pp/fetch.py
index bdf47289..c4827ee8 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_auth_base/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_auth_base/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_base/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_base/3pp/fetch.py
index 52fce67..50e10db 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_base/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_base/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_basement/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_basement/3pp/fetch.py
index e87e6906..84157f9c 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_basement/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_basement/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_cast/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_cast/3pp/fetch.py
index 584b383..fa17135 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_cast/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_cast/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_cast_framework/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_cast_framework/3pp/fetch.py
index bd9c9f4..9371925 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_cast_framework/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_cast_framework/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_clearcut/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_clearcut/3pp/fetch.py
index b6b0db4..54f432d 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_clearcut/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_clearcut/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_cloud_messaging/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_cloud_messaging/3pp/fetch.py
index 9c805475..4dca495 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_cloud_messaging/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_cloud_messaging/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_flags/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_flags/3pp/fetch.py
index c40a7c7..e597319 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_flags/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_flags/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_gcm/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_gcm/3pp/fetch.py
index ed78005..4bfb456 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_gcm/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_gcm/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_identity_credentials/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_identity_credentials/3pp/fetch.py
index e26b882f..7ce4825 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_identity_credentials/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_identity_credentials/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_iid/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_iid/3pp/fetch.py
index 3570ba0..90d3704 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_iid/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_iid/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_instantapps/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_instantapps/3pp/fetch.py
index e36d9f9..e294e641d 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_instantapps/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_instantapps/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_location/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_location/3pp/fetch.py
index c0d28680..ac3c5794 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_location/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_location/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_phenotype/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_phenotype/3pp/fetch.py
index 02eb3af..0ab0558c 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_phenotype/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_phenotype/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_stats/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_stats/3pp/fetch.py
index caba09b0..ea0f48b2 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_stats/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_stats/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_tasks/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_tasks/3pp/fetch.py
index a40325f..0efe31cc 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_tasks/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_tasks/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_vision/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_vision/3pp/fetch.py
index 6285748..df9cfbc 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_vision/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_vision/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_gms_play_services_vision_common/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_gms_play_services_vision_common/3pp/fetch.py
index 310f1f4..5fd94264 100755
--- a/third_party/android_deps/libs/com_google_android_gms_play_services_vision_common/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_gms_play_services_vision_common/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_material_material/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_material_material/3pp/fetch.py
index 9a6f5b11..25a854f 100755
--- a/third_party/android_deps/libs/com_google_android_material_material/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_material_material/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_play_core_common/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_play_core_common/3pp/fetch.py
index da6a40c0..f0edf1c7 100755
--- a/third_party/android_deps/libs/com_google_android_play_core_common/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_play_core_common/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_android_play_feature_delivery/3pp/fetch.py b/third_party/android_deps/libs/com_google_android_play_feature_delivery/3pp/fetch.py
index 00c189e..9d5d4890 100755
--- a/third_party/android_deps/libs/com_google_android_play_feature_delivery/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_android_play_feature_delivery/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_auto_auto_common/3pp/fetch.py b/third_party/android_deps/libs/com_google_auto_auto_common/3pp/fetch.py
index edd903c..bc538c4 100755
--- a/third_party/android_deps/libs/com_google_auto_auto_common/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_auto_auto_common/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_auto_service_auto_service/3pp/fetch.py b/third_party/android_deps/libs/com_google_auto_service_auto_service/3pp/fetch.py
index c2fe3b3..bfe2fd4 100755
--- a/third_party/android_deps/libs/com_google_auto_service_auto_service/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_auto_service_auto_service/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_auto_service_auto_service_annotations/3pp/fetch.py b/third_party/android_deps/libs/com_google_auto_service_auto_service_annotations/3pp/fetch.py
index 657eabf..b402e31 100755
--- a/third_party/android_deps/libs/com_google_auto_service_auto_service_annotations/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_auto_service_auto_service_annotations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_auto_value_auto_value_annotations/3pp/fetch.py b/third_party/android_deps/libs/com_google_auto_value_auto_value_annotations/3pp/fetch.py
index caaa5ac..1e4d4d45 100755
--- a/third_party/android_deps/libs/com_google_auto_value_auto_value_annotations/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_auto_value_auto_value_annotations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_code_findbugs_jsr305/3pp/fetch.py b/third_party/android_deps/libs/com_google_code_findbugs_jsr305/3pp/fetch.py
index c8214f0b..8a5a2ea 100755
--- a/third_party/android_deps/libs/com_google_code_findbugs_jsr305/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_code_findbugs_jsr305/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_code_gson_gson/3pp/fetch.py b/third_party/android_deps/libs/com_google_code_gson_gson/3pp/fetch.py
index 9bc6cfa..a4a0f9b 100755
--- a/third_party/android_deps/libs/com_google_code_gson_gson/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_code_gson_gson/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_dagger_dagger/3pp/fetch.py b/third_party/android_deps/libs/com_google_dagger_dagger/3pp/fetch.py
index c3d154b..4878d12 100755
--- a/third_party/android_deps/libs/com_google_dagger_dagger/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_dagger_dagger/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_dagger_hilt_core/3pp/fetch.py b/third_party/android_deps/libs/com_google_dagger_hilt_core/3pp/fetch.py
index e3d9b5a..772e0a0 100755
--- a/third_party/android_deps/libs/com_google_dagger_hilt_core/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_dagger_hilt_core/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_errorprone_error_prone_annotations/3pp/fetch.py b/third_party/android_deps/libs/com_google_errorprone_error_prone_annotations/3pp/fetch.py
index feecec5..1b5497a6 100755
--- a/third_party/android_deps/libs/com_google_errorprone_error_prone_annotations/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_errorprone_error_prone_annotations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_annotations/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_annotations/3pp/fetch.py
index ccb8c07..9508d3c 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_annotations/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_annotations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_common/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_common/3pp/fetch.py
index b520a86..69e77ca 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_common/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_common/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_components/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_components/3pp/fetch.py
index 4e7ca4e..95299bf 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_components/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_components/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_encoders/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_encoders/3pp/fetch.py
index cedf4971..e63aefa 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_encoders/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_encoders/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_encoders_json/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_encoders_json/3pp/fetch.py
index dd8ab975..ee0832d 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_encoders_json/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_encoders_json/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_iid/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_iid/3pp/fetch.py
index 072910b..6c4fa41e 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_iid/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_iid/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_iid_interop/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_iid_interop/3pp/fetch.py
index 0a0c32e..c81d819 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_iid_interop/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_iid_interop/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_installations/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_installations/3pp/fetch.py
index 713f5eb..b3487d90 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_installations/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_installations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_installations_interop/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_installations_interop/3pp/fetch.py
index db1aca7..27ff97f 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_installations_interop/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_installations_interop/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_measurement_connector/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_measurement_connector/3pp/fetch.py
index 48bd437..7012d40 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_measurement_connector/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_measurement_connector/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_firebase_firebase_messaging/3pp/fetch.py b/third_party/android_deps/libs/com_google_firebase_firebase_messaging/3pp/fetch.py
index 265fccc..908753f6 100755
--- a/third_party/android_deps/libs/com_google_firebase_firebase_messaging/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_firebase_firebase_messaging/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_guava_failureaccess/3pp/fetch.py b/third_party/android_deps/libs/com_google_guava_failureaccess/3pp/fetch.py
index a658f45..9b762e9d 100755
--- a/third_party/android_deps/libs/com_google_guava_failureaccess/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_guava_failureaccess/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_guava_guava/3pp/fetch.py b/third_party/android_deps/libs/com_google_guava_guava/3pp/fetch.py
index 5620920..ed855c8 100755
--- a/third_party/android_deps/libs/com_google_guava_guava/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_guava_guava/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=r'-jre')
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_guava_guava_android/3pp/fetch.py b/third_party/android_deps/libs/com_google_guava_guava_android/3pp/fetch.py
index 806a3c1..34c48b0 100755
--- a/third_party/android_deps/libs/com_google_guava_guava_android/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_guava_guava_android/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=r'-android')
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/3pp/fetch.py b/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/3pp/fetch.py
index bbbae7e..db5b733 100755
--- a/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_j2objc_j2objc_annotations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_google_protobuf_protobuf_javalite/3pp/fetch.py b/third_party/android_deps/libs/com_google_protobuf_protobuf_javalite/3pp/fetch.py
index c5eea72e..244ddfe 100755
--- a/third_party/android_deps/libs/com_google_protobuf_protobuf_javalite/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_google_protobuf_protobuf_javalite/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_googlecode_java_diff_utils_diffutils/3pp/fetch.py b/third_party/android_deps/libs/com_googlecode_java_diff_utils_diffutils/3pp/fetch.py
index c3860f4..c4b9ca3c 100755
--- a/third_party/android_deps/libs/com_googlecode_java_diff_utils_diffutils/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_googlecode_java_diff_utils_diffutils/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_squareup_javapoet/3pp/fetch.py b/third_party/android_deps/libs/com_squareup_javapoet/3pp/fetch.py
index e1b8fd0..7874d5f6 100755
--- a/third_party/android_deps/libs/com_squareup_javapoet/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_squareup_javapoet/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_squareup_javawriter/3pp/fetch.py b/third_party/android_deps/libs/com_squareup_javawriter/3pp/fetch.py
index 47314e61..99f0c03 100755
--- a/third_party/android_deps/libs/com_squareup_javawriter/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_squareup_javawriter/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_squareup_moshi_moshi/3pp/fetch.py b/third_party/android_deps/libs/com_squareup_moshi_moshi/3pp/fetch.py
index 27f358e..1727d7b7 100755
--- a/third_party/android_deps/libs/com_squareup_moshi_moshi/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_squareup_moshi_moshi/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_squareup_moshi_moshi_adapters/3pp/fetch.py b/third_party/android_deps/libs/com_squareup_moshi_moshi_adapters/3pp/fetch.py
index 019a36761..0aa0cbc 100755
--- a/third_party/android_deps/libs/com_squareup_moshi_moshi_adapters/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_squareup_moshi_moshi_adapters/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_squareup_okio_okio_jvm/3pp/fetch.py b/third_party/android_deps/libs/com_squareup_okio_okio_jvm/3pp/fetch.py
index 2c764ab9..0969cf0b 100755
--- a/third_party/android_deps/libs/com_squareup_okio_okio_jvm/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_squareup_okio_okio_jvm/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/com_squareup_wire_wire_runtime_jvm/3pp/fetch.py b/third_party/android_deps/libs/com_squareup_wire_wire_runtime_jvm/3pp/fetch.py
index dee08a4..1519412 100755
--- a/third_party/android_deps/libs/com_squareup_wire_wire_runtime_jvm/3pp/fetch.py
+++ b/third_party/android_deps/libs/com_squareup_wire_wire_runtime_jvm/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/io_grpc_grpc_api/3pp/fetch.py b/third_party/android_deps/libs/io_grpc_grpc_api/3pp/fetch.py
index e491a49..44d20c0 100755
--- a/third_party/android_deps/libs/io_grpc_grpc_api/3pp/fetch.py
+++ b/third_party/android_deps/libs/io_grpc_grpc_api/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/io_grpc_grpc_binder/3pp/fetch.py b/third_party/android_deps/libs/io_grpc_grpc_binder/3pp/fetch.py
index 542260bf..0adc2a5 100755
--- a/third_party/android_deps/libs/io_grpc_grpc_binder/3pp/fetch.py
+++ b/third_party/android_deps/libs/io_grpc_grpc_binder/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/io_grpc_grpc_context/3pp/fetch.py b/third_party/android_deps/libs/io_grpc_grpc_context/3pp/fetch.py
index 7eb498c..1e59632 100755
--- a/third_party/android_deps/libs/io_grpc_grpc_context/3pp/fetch.py
+++ b/third_party/android_deps/libs/io_grpc_grpc_context/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/io_grpc_grpc_core/3pp/fetch.py b/third_party/android_deps/libs/io_grpc_grpc_core/3pp/fetch.py
index 42f1c6781..407c5f1 100755
--- a/third_party/android_deps/libs/io_grpc_grpc_core/3pp/fetch.py
+++ b/third_party/android_deps/libs/io_grpc_grpc_core/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/io_grpc_grpc_protobuf_lite/3pp/fetch.py b/third_party/android_deps/libs/io_grpc_grpc_protobuf_lite/3pp/fetch.py
index a068c66..60a2b9b3 100755
--- a/third_party/android_deps/libs/io_grpc_grpc_protobuf_lite/3pp/fetch.py
+++ b/third_party/android_deps/libs/io_grpc_grpc_protobuf_lite/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/io_grpc_grpc_stub/3pp/fetch.py b/third_party/android_deps/libs/io_grpc_grpc_stub/3pp/fetch.py
index 958227b..58be5b1c 100755
--- a/third_party/android_deps/libs/io_grpc_grpc_stub/3pp/fetch.py
+++ b/third_party/android_deps/libs/io_grpc_grpc_stub/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/io_perfmark_perfmark_api/3pp/fetch.py b/third_party/android_deps/libs/io_perfmark_perfmark_api/3pp/fetch.py
index bf1a90f..83b79af 100755
--- a/third_party/android_deps/libs/io_perfmark_perfmark_api/3pp/fetch.py
+++ b/third_party/android_deps/libs/io_perfmark_perfmark_api/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/jakarta_inject_jakarta_inject_api/3pp/fetch.py b/third_party/android_deps/libs/jakarta_inject_jakarta_inject_api/3pp/fetch.py
index 698f4ac..3e46eee 100755
--- a/third_party/android_deps/libs/jakarta_inject_jakarta_inject_api/3pp/fetch.py
+++ b/third_party/android_deps/libs/jakarta_inject_jakarta_inject_api/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=r'\d+\.\d+\.\d+$')
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/javax_annotation_javax_annotation_api/3pp/fetch.py b/third_party/android_deps/libs/javax_annotation_javax_annotation_api/3pp/fetch.py
index b809c15f..672436f 100755
--- a/third_party/android_deps/libs/javax_annotation_javax_annotation_api/3pp/fetch.py
+++ b/third_party/android_deps/libs/javax_annotation_javax_annotation_api/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/javax_annotation_jsr250_api/3pp/fetch.py b/third_party/android_deps/libs/javax_annotation_jsr250_api/3pp/fetch.py
index 3cbf8ef..0e4bd11 100755
--- a/third_party/android_deps/libs/javax_annotation_jsr250_api/3pp/fetch.py
+++ b/third_party/android_deps/libs/javax_annotation_jsr250_api/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/javax_inject_javax_inject/3pp/fetch.py b/third_party/android_deps/libs/javax_inject_javax_inject/3pp/fetch.py
index ddb0da5e..fcb31e2 100755
--- a/third_party/android_deps/libs/javax_inject_javax_inject/3pp/fetch.py
+++ b/third_party/android_deps/libs/javax_inject_javax_inject/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/net_bytebuddy_byte_buddy/3pp/fetch.py b/third_party/android_deps/libs/net_bytebuddy_byte_buddy/3pp/fetch.py
index f493c17..74cf1cf 100755
--- a/third_party/android_deps/libs/net_bytebuddy_byte_buddy/3pp/fetch.py
+++ b/third_party/android_deps/libs/net_bytebuddy_byte_buddy/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/net_bytebuddy_byte_buddy_agent/3pp/fetch.py b/third_party/android_deps/libs/net_bytebuddy_byte_buddy_agent/3pp/fetch.py
index 1ef2b7e..8d11600 100755
--- a/third_party/android_deps/libs/net_bytebuddy_byte_buddy_agent/3pp/fetch.py
+++ b/third_party/android_deps/libs/net_bytebuddy_byte_buddy_agent/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_bouncycastle_bcprov_jdk18on/3pp/fetch.py b/third_party/android_deps/libs/org_bouncycastle_bcprov_jdk18on/3pp/fetch.py
index 2466f33..45a27ac 100755
--- a/third_party/android_deps/libs/org_bouncycastle_bcprov_jdk18on/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_bouncycastle_bcprov_jdk18on/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_ccil_cowan_tagsoup_tagsoup/3pp/fetch.py b/third_party/android_deps/libs/org_ccil_cowan_tagsoup_tagsoup/3pp/fetch.py
index 0a384438..72c4b21 100755
--- a/third_party/android_deps/libs/org_ccil_cowan_tagsoup_tagsoup/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_ccil_cowan_tagsoup_tagsoup/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/3pp/fetch.py b/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/3pp/fetch.py
index b4869ce..353ae22 100755
--- a/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_checkerframework_checker_compat_qual/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_checkerframework_checker_qual/3pp/fetch.py b/third_party/android_deps/libs/org_checkerframework_checker_qual/3pp/fetch.py
index 18919d1..6c73144 100755
--- a/third_party/android_deps/libs/org_checkerframework_checker_qual/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_checkerframework_checker_qual/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_checkerframework_checker_util/3pp/fetch.py b/third_party/android_deps/libs/org_checkerframework_checker_util/3pp/fetch.py
index 7414929..761a8ab 100755
--- a/third_party/android_deps/libs/org_checkerframework_checker_util/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_checkerframework_checker_util/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_codehaus_mojo_animal_sniffer_annotations/3pp/fetch.py b/third_party/android_deps/libs/org_codehaus_mojo_animal_sniffer_annotations/3pp/fetch.py
index 72cb1b6..67cb0ce 100755
--- a/third_party/android_deps/libs/org_codehaus_mojo_animal_sniffer_annotations/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_codehaus_mojo_animal_sniffer_annotations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_conscrypt_conscrypt_openjdk_uber/3pp/fetch.py b/third_party/android_deps/libs/org_conscrypt_conscrypt_openjdk_uber/3pp/fetch.py
index 8e5dd0a..e15558eb9 100755
--- a/third_party/android_deps/libs/org_conscrypt_conscrypt_openjdk_uber/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_conscrypt_conscrypt_openjdk_uber/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_hamcrest_hamcrest/3pp/fetch.py b/third_party/android_deps/libs/org_hamcrest_hamcrest/3pp/fetch.py
index dde569e..10faf2c 100755
--- a/third_party/android_deps/libs/org_hamcrest_hamcrest/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_hamcrest_hamcrest/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_android_extensions_runtime/3pp/fetch.py b/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_android_extensions_runtime/3pp/fetch.py
index 484c0f9bc..7d5e697f 100755
--- a/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_android_extensions_runtime/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_android_extensions_runtime/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_parcelize_runtime/3pp/fetch.py b/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_parcelize_runtime/3pp/fetch.py
index d0563f2..ebd5c9f7 100755
--- a/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_parcelize_runtime/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_parcelize_runtime/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_jetbrains_kotlinx_atomicfu_jvm/3pp/fetch.py b/third_party/android_deps/libs/org_jetbrains_kotlinx_atomicfu_jvm/3pp/fetch.py
index 9858eb859..3429724f 100755
--- a/third_party/android_deps/libs/org_jetbrains_kotlinx_atomicfu_jvm/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_jetbrains_kotlinx_atomicfu_jvm/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_android/3pp/fetch.py b/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_android/3pp/fetch.py
index 8ac51a7..4bff100 100755
--- a/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_android/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_android/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm/3pp/fetch.py b/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm/3pp/fetch.py
index 7beeca11..e883265 100755
--- a/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_guava/3pp/fetch.py b/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_guava/3pp/fetch.py
index 9bbdf6b7..cc08f051 100755
--- a/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_guava/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_jetbrains_kotlinx_kotlinx_coroutines_guava/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_jsoup_jsoup/3pp/fetch.py b/third_party/android_deps/libs/org_jsoup_jsoup/3pp/fetch.py
index b12104e2..0fb00215 100755
--- a/third_party/android_deps/libs/org_jsoup_jsoup/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_jsoup_jsoup/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_mockito_mockito_android/3pp/fetch.py b/third_party/android_deps/libs/org_mockito_mockito_android/3pp/fetch.py
index 64b65dcc..d96f3db0 100755
--- a/third_party/android_deps/libs/org_mockito_mockito_android/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_mockito_mockito_android/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_mockito_mockito_core/3pp/fetch.py b/third_party/android_deps/libs/org_mockito_mockito_core/3pp/fetch.py
index 4fe15928c..60dffc4c 100755
--- a/third_party/android_deps/libs/org_mockito_mockito_core/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_mockito_mockito_core/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_mockito_mockito_subclass/3pp/fetch.py b/third_party/android_deps/libs/org_mockito_mockito_subclass/3pp/fetch.py
index 5fac755..ee85e7a2 100755
--- a/third_party/android_deps/libs/org_mockito_mockito_subclass/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_mockito_mockito_subclass/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_objenesis_objenesis/3pp/fetch.py b/third_party/android_deps/libs/org_objenesis_objenesis/3pp/fetch.py
index e39b32b..8135a838 100755
--- a/third_party/android_deps/libs/org_objenesis_objenesis/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_objenesis_objenesis/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_ow2_asm_asm/3pp/fetch.py b/third_party/android_deps/libs/org_ow2_asm_asm/3pp/fetch.py
index 1805cc76..4a5b299 100755
--- a/third_party/android_deps/libs/org_ow2_asm_asm/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_ow2_asm_asm/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_ow2_asm_asm_analysis/3pp/fetch.py b/third_party/android_deps/libs/org_ow2_asm_asm_analysis/3pp/fetch.py
index 0b22167..05a6438 100755
--- a/third_party/android_deps/libs/org_ow2_asm_asm_analysis/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_ow2_asm_asm_analysis/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_ow2_asm_asm_commons/3pp/fetch.py b/third_party/android_deps/libs/org_ow2_asm_asm_commons/3pp/fetch.py
index 5e3b605..77cec678 100755
--- a/third_party/android_deps/libs/org_ow2_asm_asm_commons/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_ow2_asm_asm_commons/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_ow2_asm_asm_tree/3pp/fetch.py b/third_party/android_deps/libs/org_ow2_asm_asm_tree/3pp/fetch.py
index 09d28ce0..2046b83 100755
--- a/third_party/android_deps/libs/org_ow2_asm_asm_tree/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_ow2_asm_asm_tree/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_ow2_asm_asm_util/3pp/fetch.py b/third_party/android_deps/libs/org_ow2_asm_asm_util/3pp/fetch.py
index d1b26af7..66f629d 100755
--- a/third_party/android_deps/libs/org_ow2_asm_asm_util/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_ow2_asm_asm_util/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_annotations/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_annotations/3pp/fetch.py
index 75af6fe..d63e585a 100755
--- a/third_party/android_deps/libs/org_robolectric_annotations/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_annotations/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_junit/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_junit/3pp/fetch.py
index 77b6af6..e8c2d00 100755
--- a/third_party/android_deps/libs/org_robolectric_junit/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_junit/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_nativeruntime/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_nativeruntime/3pp/fetch.py
index 62b76a2..97ec955 100755
--- a/third_party/android_deps/libs/org_robolectric_nativeruntime/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_nativeruntime/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_nativeruntime_dist_compat/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_nativeruntime_dist_compat/3pp/fetch.py
index f19470c..760c9ee 100755
--- a/third_party/android_deps/libs/org_robolectric_nativeruntime_dist_compat/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_nativeruntime_dist_compat/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_pluginapi/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_pluginapi/3pp/fetch.py
index e5c6c25c..2a06541 100755
--- a/third_party/android_deps/libs/org_robolectric_pluginapi/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_pluginapi/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_plugins_maven_dependency_resolver/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_plugins_maven_dependency_resolver/3pp/fetch.py
index 7cd8572..d74db39 100755
--- a/third_party/android_deps/libs/org_robolectric_plugins_maven_dependency_resolver/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_plugins_maven_dependency_resolver/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_resources/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_resources/3pp/fetch.py
index 8878d5d..570d3e3 100755
--- a/third_party/android_deps/libs/org_robolectric_resources/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_resources/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_robolectric/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_robolectric/3pp/fetch.py
index bb30215..7d816cbc 100755
--- a/third_party/android_deps/libs/org_robolectric_robolectric/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_robolectric/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_sandbox/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_sandbox/3pp/fetch.py
index 4d0330f..b86f7a2 100755
--- a/third_party/android_deps/libs/org_robolectric_sandbox/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_sandbox/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_shadowapi/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_shadowapi/3pp/fetch.py
index deeae4e7..cbf9cfc 100755
--- a/third_party/android_deps/libs/org_robolectric_shadowapi/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_shadowapi/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_shadows_framework/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_shadows_framework/3pp/fetch.py
index ea49eb5..fbd12d1 100755
--- a/third_party/android_deps/libs/org_robolectric_shadows_framework/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_shadows_framework/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_shadows_versioning/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_shadows_versioning/3pp/fetch.py
index d8ef037..f4eb065 100755
--- a/third_party/android_deps/libs/org_robolectric_shadows_versioning/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_shadows_versioning/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_utils/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_utils/3pp/fetch.py
index 7314345..4298d58 100755
--- a/third_party/android_deps/libs/org_robolectric_utils/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_utils/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/android_deps/libs/org_robolectric_utils_reflector/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_utils_reflector/3pp/fetch.py
index e7009ab..5ea5119 100755
--- a/third_party/android_deps/libs/org_robolectric_utils_reflector/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_utils_reflector/3pp/fetch.py
@@ -22,6 +22,5 @@
                          version_override=None,
                          version_filter=None)
 
-
 if __name__ == '__main__':
     fetch_common.main(SPEC)
diff --git a/third_party/angle b/third_party/angle
index 84492ff..10d56e6 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 84492ff334a4051d760d0d0c63ffb3fb5694f7da
+Subproject commit 10d56e63f489477d325fd9667861c06b4c5887bc
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index bb821dcd..70501a0 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -2277,11 +2277,11 @@
 
 BASE_FEATURE(kSharedStorageCrossOriginScript,
              "SharedStorageCrossOriginScript",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kSharedStorageCreateWorkletUseContextOriginByDefault,
              "SharedStorageCreateWorkletUseContextOriginByDefault",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kSharedStorageAPIEnableWALForDatabase,
              "SharedStorageAPIEnableWALForDatabase",
diff --git a/third_party/blink/common/manifest/manifest_util.cc b/third_party/blink/common/manifest/manifest_util.cc
index 2d9ecf2a..0b23f40 100644
--- a/third_party/blink/common/manifest/manifest_util.cc
+++ b/third_party/blink/common/manifest/manifest_util.cc
@@ -41,6 +41,21 @@
   return manifest && IsDefaultManifest(*manifest, document_url);
 }
 
+std::optional<blink::mojom::Manifest_TextDirection> TextDirectionFromString(
+    const std::string& dir) {
+  using TextDirection = blink::mojom::Manifest_TextDirection;
+  if (base::EqualsCaseInsensitiveASCII(dir, "auto")) {
+    return TextDirection::kAuto;
+  }
+  if (base::EqualsCaseInsensitiveASCII(dir, "ltr")) {
+    return TextDirection::kLTR;
+  }
+  if (base::EqualsCaseInsensitiveASCII(dir, "rtl")) {
+    return TextDirection::kRTL;
+  }
+  return std::nullopt;
+}
+
 std::string DisplayModeToString(blink::mojom::DisplayMode display) {
   switch (display) {
     case blink::mojom::DisplayMode::kUndefined:
diff --git a/third_party/blink/public/common/manifest/manifest_util.h b/third_party/blink/public/common/manifest/manifest_util.h
index e92789bc..07b28c86 100644
--- a/third_party/blink/public/common/manifest/manifest_util.h
+++ b/third_party/blink/public/common/manifest/manifest_util.h
@@ -30,6 +30,13 @@
 BLINK_COMMON_EXPORT bool IsDefaultManifest(const mojom::ManifestPtr& manifest,
                                            const GURL& document_url);
 
+// Returns the blink::mojom::Manifest_TextDirection which matches |dir|.
+// |dir| should be one of
+// https://www.w3.org/TR/appmanifest/#dfn-text-directions.
+// |dir| is case insensitive. Returns std::nullopt if there is no match.
+BLINK_COMMON_EXPORT std::optional<blink::mojom::Manifest_TextDirection>
+TextDirectionFromString(const std::string& dir);
+
 // Converts a blink::mojom::DisplayMode to a string. Returns one of
 // https://www.w3.org/TR/appmanifest/#dfn-display-modes-values. Return values
 // are lowercase. Returns an empty string for DisplayMode::kUndefined.
diff --git a/third_party/blink/public/mojom/manifest/manifest.mojom b/third_party/blink/public/mojom/manifest/manifest.mojom
index 852ff54e..5567e441 100644
--- a/third_party/blink/public/mojom/manifest/manifest.mojom
+++ b/third_party/blink/public/mojom/manifest/manifest.mojom
@@ -23,10 +23,21 @@
 // if the manifest failed to parse.
 [JavaClassName="WebManifest"]
 struct Manifest {
+  enum TextDirection {
+    // Text direction is unknown.
+    kAuto,
+    // Left-to-right text.
+    kLTR,
+    // Right-to-left text.
+    kRTL,
+  };
+
   // The URL this manifest was loaded from. This can be an empty URL if this is
   // an automatically generated "default" manifest.
   url.mojom.Url manifest_url;
 
+  TextDirection dir;
+
   mojo_base.mojom.String16? name;
 
   mojo_base.mojom.String16? short_name;
diff --git a/third_party/blink/renderer/core/exported/web_frame_serializer.cc b/third_party/blink/renderer/core/exported/web_frame_serializer.cc
index 5c564314..43db55ef 100644
--- a/third_party/blink/renderer/core/exported/web_frame_serializer.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_serializer.cc
@@ -91,10 +91,7 @@
   TRACE_EVENT_BEGIN0("page-serialization",
                      "WebFrameSerializer::generateMHTMLParts serializing");
   Deque<SerializedResource> resources;
-  {
-    FrameSerializer serializer(resources, web_delegate);
-    serializer.SerializeFrame(*frame);
-  }
+  FrameSerializer::SerializeFrame(resources, *web_delegate, *frame);
 
   TRACE_EVENT_END1("page-serialization",
                    "WebFrameSerializer::generateMHTMLParts serializing",
diff --git a/third_party/blink/renderer/core/frame/frame_serializer.cc b/third_party/blink/renderer/core/frame/frame_serializer.cc
index c9b5916..f9262467 100644
--- a/third_party/blink/renderer/core/frame/frame_serializer.cc
+++ b/third_party/blink/renderer/core/frame/frame_serializer.cc
@@ -115,459 +115,717 @@
 
 }  // namespace
 
-FrameSerializerDelegateImpl::FrameSerializerDelegateImpl(
-    WebFrameSerializer::MHTMLPartsGenerationDelegate& web_delegate,
-    HeapHashSet<WeakMember<const Element>>& shadow_template_elements)
-    : web_delegate_(web_delegate),
-      shadow_template_elements_(shadow_template_elements),
-      popup_overlays_skipped_(false) {}
+// Stores the list of serialized resources which constitute the frame. The
+// first resource should be the frame's content (usually HTML).
+class MultiResourcePacker {
+ public:
+  MultiResourcePacker(
+      Deque<SerializedResource>* resources,
+      WebFrameSerializer::MHTMLPartsGenerationDelegate* web_delegate)
+      : resources_(resources), web_delegate_(web_delegate) {}
 
-bool FrameSerializerDelegateImpl::ShouldIgnoreElement(const Element& element) {
-  if (ShouldIgnoreHiddenElement(element)) {
-    return true;
-  }
-  if (ShouldIgnoreMetaElement(element)) {
-    return true;
-  }
-  if (web_delegate_.RemovePopupOverlay() &&
-      ShouldIgnorePopupOverlayElement(element)) {
-    return true;
-  }
-  // Remove <link> for stylesheets that do not load.
-  auto* html_link_element = DynamicTo<HTMLLinkElement>(element);
-  if (html_link_element && html_link_element->RelAttribute().IsStyleSheet() &&
-      !html_link_element->sheet()) {
-    return true;
-  }
-  return false;
-}
-
-bool FrameSerializerDelegateImpl::ShouldIgnoreHiddenElement(
-    const Element& element) {
-  // If an iframe is in the head, it will be moved to the body when the page is
-  // being loaded. But if an iframe is injected into the head later, it will
-  // stay there and not been displayed. To prevent it from being brought to the
-  // saved page and cause it being displayed, we should not include it.
-  if (IsA<HTMLIFrameElement>(element) &&
-      Traversal<HTMLHeadElement>::FirstAncestor(element)) {
-    return true;
+  bool HasResource(const KURL& url) const {
+    return resource_urls_.Contains(url);
   }
 
-  // Do not include the element that is marked with hidden attribute.
-  if (element.FastHasAttribute(html_names::kHiddenAttr)) {
-    return true;
+  void AddMainResource(const String& mime_type,
+                       scoped_refptr<const SharedBuffer> data,
+                       const KURL& url) {
+    // The main resource must be first.
+    // We do not call `ShouldAddURL()` for the main resource.
+    resources_->push_front(SerializedResource(url, mime_type, std::move(data)));
   }
 
-  // Do not include the hidden form element.
-  auto* html_element_element = DynamicTo<HTMLInputElement>(&element);
-  return html_element_element && html_element_element->FormControlType() ==
-                                     FormControlType::kInputHidden;
-}
-
-bool FrameSerializerDelegateImpl::ShouldIgnoreMetaElement(
-    const Element& element) {
-  // Do not include meta elements that declare Content-Security-Policy
-  // directives. They should have already been enforced when the original
-  // document is loaded. Since only the rendered resources are encapsulated in
-  // the saved MHTML page, there is no need to carry the directives. If they
-  // are still kept in the MHTML, child frames that are referred to using cid:
-  // scheme could be prevented from loading.
-  if (!IsA<HTMLMetaElement>(element)) {
-    return false;
-  }
-  if (!element.FastHasAttribute(html_names::kContentAttr)) {
-    return false;
-  }
-  const AtomicString& http_equiv =
-      element.FastGetAttribute(html_names::kHttpEquivAttr);
-  return http_equiv == "Content-Security-Policy";
-}
-
-bool FrameSerializerDelegateImpl::ShouldIgnorePopupOverlayElement(
-    const Element& element) {
-  // The element should be visible.
-  LayoutBox* box = element.GetLayoutBox();
-  if (!box) {
-    return false;
+  void AddToResources(const String& mime_type,
+                      scoped_refptr<const SharedBuffer> data,
+                      const KURL& url) {
+    if (!data) {
+      DLOG(ERROR) << "No data for resource " << url.GetString();
+      return;
+    }
+    CHECK(resource_urls_.Contains(url))
+        << "ShouldAddURL() not called before AddToResources";
+    resources_->push_back(SerializedResource(url, mime_type, std::move(data)));
   }
 
-  // The bounding box of the element should contain center point of the
-  // viewport.
-  LocalDOMWindow* window = element.GetDocument().domWindow();
-  DCHECK(window);
-  int center_x = window->innerWidth() / 2;
-  int center_y = window->innerHeight() / 2;
-  if (Page* page = element.GetDocument().GetPage()) {
-    center_x = page->GetChromeClient().WindowToViewportScalar(
-        window->GetFrame(), center_x);
-    center_y = page->GetChromeClient().WindowToViewportScalar(
-        window->GetFrame(), center_y);
-  }
-  if (!PhysicalRect(box->PhysicalLocation(), box->Size())
-           .Contains(LayoutUnit(center_x), LayoutUnit(center_y))) {
-    return false;
+  void AddImageToResources(ImageResourceContent* image, const KURL& url) {
+    if (!image || !image->HasImage() || image->ErrorOccurred() ||
+        !ShouldAddURL(url)) {
+      return;
+    }
+
+    TRACE_EVENT2("page-serialization", "FrameSerializer::addImageToResources",
+                 "type", "image", "url", url.ElidedString().Utf8());
+    AddToResources(image->GetResponse().MimeType(), image->GetImage()->Data(),
+                   url);
   }
 
-  // The z-index should be greater than the threshold.
-  if (box->Style()->EffectiveZIndex() < kPopupOverlayZIndexThreshold) {
-    return false;
+  // Returns whether the resource for `url` should be added. This will return
+  // true only once for a `url`, because we only want to store each resource
+  // once.
+  bool ShouldAddURL(const KURL& url) {
+    bool should_add = url.IsValid() && !resource_urls_.Contains(url) &&
+                      !url.ProtocolIsData() &&
+                      !web_delegate_->ShouldSkipResource(url);
+    if (should_add) {
+      // Make sure that `ShouldAddURL()` returns true only once for any given
+      // URL. This is done because `ShouldSkipResource()` has the hidden
+      // behavior of tracking which resources are being added. This is why we
+      // must call it only once per url.
+      resource_urls_.insert(url);
+    }
+    return should_add;
   }
 
-  popup_overlays_skipped_ = true;
+  void AddFontToResources(FontResource& font) {
+    if (!font.IsLoaded() || !font.ResourceBuffer()) {
+      return;
+    }
+    if (!ShouldAddURL(font.Url())) {
+      return;
+    }
 
-  return true;
-}
-
-bool FrameSerializerDelegateImpl::ShouldIgnoreAttribute(
-    const Element& element,
-    const Attribute& attribute) {
-  // TODO(fgorski): Presence of srcset attribute causes MHTML to not display
-  // images, as only the value of src is pulled into the archive. Discarding
-  // srcset prevents the problem. Long term we should make sure to MHTML plays
-  // nicely with srcset.
-  if (IsA<HTMLImageElement>(element) &&
-      (attribute.LocalName() == html_names::kSrcsetAttr ||
-       attribute.LocalName() == html_names::kSizesAttr)) {
-    return true;
+    AddToResources(font.GetResponse().MimeType(), font.ResourceBuffer(),
+                   font.Url());
   }
 
-  // Do not save ping attribute since anyway the ping will be blocked from
-  // MHTML.
-  if (IsA<HTMLAnchorElement>(element) &&
-      attribute.LocalName() == html_names::kPingAttr) {
-    return true;
-  }
+ private:
+  // This hashset is only used for de-duplicating resources to be serialized.
+  HashSet<KURL> resource_urls_;
 
-  // The special attribute in a template element to denote the shadow DOM
-  // should only be generated from MHTML serialization. If it is found in the
-  // original page, it should be ignored.
-  if (IsA<HTMLTemplateElement>(element) &&
-      (attribute.LocalName() == kShadowModeAttributeName ||
-       attribute.LocalName() == kShadowDelegatesFocusAttributeName) &&
-      !shadow_template_elements_.Contains(&element)) {
-    return true;
-  }
-
-  // If srcdoc attribute for frame elements will be rewritten as src attribute
-  // containing link instead of html contents, don't ignore the attribute.
-  // Bail out now to avoid the check in Element::isScriptingAttribute.
-  bool is_src_doc_attribute = IsA<HTMLFrameElementBase>(element) &&
-                              attribute.GetName() == html_names::kSrcdocAttr;
-  String new_link_for_the_element;
-  if (is_src_doc_attribute && RewriteLink(element, new_link_for_the_element)) {
-    return false;
-  }
-
-  //  Drop integrity attribute for those links with subresource loaded.
-  auto* html_link_element = DynamicTo<HTMLLinkElement>(element);
-  if (attribute.LocalName() == html_names::kIntegrityAttr &&
-      html_link_element && html_link_element->sheet()) {
-    return true;
-  }
-
-  // Do not include attributes that contain javascript. This is because the
-  // script will not be executed when a MHTML page is being loaded.
-  return element.IsScriptingAttribute(attribute);
-}
-
-bool FrameSerializerDelegateImpl::RewriteLink(const Element& element,
-                                              String& rewritten_link) {
-  auto* frame_owner = DynamicTo<HTMLFrameOwnerElement>(element);
-  if (!frame_owner) {
-    return false;
-  }
-
-  Frame* frame = frame_owner->ContentFrame();
-  if (!frame) {
-    return false;
-  }
-
-  WebString content_id = FrameSerializer::GetContentID(frame);
-  KURL cid_uri = MHTMLParser::ConvertContentIDToURI(content_id);
-  DCHECK(cid_uri.IsValid());
-  rewritten_link = cid_uri.GetString();
-  return true;
-}
-
-bool FrameSerializerDelegateImpl::ShouldSkipResourceWithURL(const KURL& url) {
-  return web_delegate_.ShouldSkipResource(url);
-}
-
-Vector<Attribute> FrameSerializerDelegateImpl::GetCustomAttributes(
-    const Element& element) {
-  Vector<Attribute> attributes;
-
-  if (auto* image = DynamicTo<HTMLImageElement>(element)) {
-    GetCustomAttributesForImageElement(*image, &attributes);
-  }
-
-  return attributes;
-}
-
-void FrameSerializerDelegateImpl::GetCustomAttributesForImageElement(
-    const HTMLImageElement& element,
-    Vector<Attribute>* attributes) {
-  // Currently only the value of src is pulled into the archive and the srcset
-  // attribute is ignored (see shouldIgnoreAttribute() above). If the device
-  // has a higher DPR, a different image from srcset could be loaded instead.
-  // When this occurs, we should provide the rendering width and height for
-  // <img> element if not set.
-
-  // The image should be loaded and participate the layout.
-  ImageResourceContent* image = element.CachedImage();
-  if (!image || !image->HasImage() || image->ErrorOccurred() ||
-      !element.GetLayoutObject()) {
-    return;
-  }
-
-  // The width and height attributes should not be set.
-  if (element.FastHasAttribute(html_names::kWidthAttr) ||
-      element.FastHasAttribute(html_names::kHeightAttr)) {
-    return;
-  }
-
-  // Check if different image is loaded. naturalWidth/naturalHeight will return
-  // the image size adjusted with current DPR.
-  if ((static_cast<int>(element.naturalWidth())) ==
-          image->GetImage()->width() &&
-      (static_cast<int>(element.naturalHeight())) ==
-          image->GetImage()->height()) {
-    return;
-  }
-
-  Attribute width_attribute(html_names::kWidthAttr,
-                            AtomicString::Number(element.LayoutBoxWidth()));
-  attributes->push_back(width_attribute);
-  Attribute height_attribute(html_names::kHeightAttr,
-                             AtomicString::Number(element.LayoutBoxHeight()));
-  attributes->push_back(height_attribute);
-}
-
-std::pair<ShadowRoot*, HTMLTemplateElement*>
-FrameSerializerDelegateImpl::GetShadowTree(const Element& element) const {
-  ShadowRoot* shadow_root = element.GetShadowRoot();
-  if (!shadow_root || shadow_root->GetMode() == ShadowRootMode::kUserAgent) {
-    return std::pair<ShadowRoot*, HTMLTemplateElement*>();
-  }
-
-  // Put the shadow DOM content inside a template element. A special attribute
-  // is set to tell the mode of the shadow DOM.
-  HTMLTemplateElement* template_element =
-      MakeGarbageCollected<HTMLTemplateElement>(element.GetDocument());
-  template_element->setAttribute(
-      QualifiedName(AtomicString(kShadowModeAttributeName)),
-      AtomicString(shadow_root->GetMode() == ShadowRootMode::kOpen ? "open"
-                                                                   : "closed"));
-  if (shadow_root->delegatesFocus()) {
-    template_element->setAttribute(
-        QualifiedName(AtomicString(kShadowDelegatesFocusAttributeName)),
-        g_empty_atom);
-  }
-  shadow_template_elements_.insert(template_element);
-
-  return std::pair<ShadowRoot*, HTMLTemplateElement*>(shadow_root,
-                                                      template_element);
-}
+  Deque<SerializedResource>* resources_;
+  WebFrameSerializer::MHTMLPartsGenerationDelegate* web_delegate_;
+};
 
 class SerializerMarkupAccumulator : public MarkupAccumulator {
   STACK_ALLOCATED();
 
  public:
-  SerializerMarkupAccumulator(FrameSerializerDelegateImpl&,
-                              FrameSerializerResourceDelegate&,
-                              Document&);
-  ~SerializerMarkupAccumulator() override;
-
- protected:
-  void AppendCustomAttributes(const Element&) override;
-  bool ShouldIgnoreAttribute(const Element&, const Attribute&) const override;
-  bool ShouldIgnoreElement(const Element&) const override;
-  AtomicString AppendElement(const Element&) override;
-  void AppendAttribute(const Element&, const Attribute&) override;
-  std::pair<ShadowRoot*, HTMLTemplateElement*> GetShadowTree(
-      const Element&) const override;
+  SerializerMarkupAccumulator(
+      MultiResourcePacker* resource_serializer,
+      WebFrameSerializer::MHTMLPartsGenerationDelegate* web_delegate,
+      Document& document)
+      : MarkupAccumulator(kResolveAllURLs,
+                          IsA<HTMLDocument>(document) ? SerializationType::kHTML
+                                                      : SerializationType::kXML,
+                          ShadowRootInclusion()),
+        resource_serializer_(resource_serializer),
+        web_delegate_(web_delegate),
+        document_(&document) {}
+  ~SerializerMarkupAccumulator() override = default;
 
  private:
-  void AppendAttributeValue(const String& attribute_value);
-  void AppendRewrittenAttribute(const Element&,
-                                const String& attribute_name,
-                                const String& attribute_value);
-  void AppendExtraForHeadElement(const Element&);
-  void AppendStylesheets(Document* document, bool style_element_only);
+  bool ShouldIgnoreHiddenElement(const Element& element) const {
+    // If an iframe is in the head, it will be moved to the body when the page
+    // is being loaded. But if an iframe is injected into the head later, it
+    // will stay there and not been displayed. To prevent it from being brought
+    // to the saved page and cause it being displayed, we should not include it.
+    if (IsA<HTMLIFrameElement>(element) &&
+        Traversal<HTMLHeadElement>::FirstAncestor(element)) {
+      return true;
+    }
 
-  FrameSerializerDelegateImpl& delegate_;
-  FrameSerializerResourceDelegate& resource_delegate_;
+    // Do not include the element that is marked with hidden attribute.
+    if (element.FastHasAttribute(html_names::kHiddenAttr)) {
+      return true;
+    }
+
+    // Do not include the hidden form element.
+    auto* html_element_element = DynamicTo<HTMLInputElement>(&element);
+    return html_element_element && html_element_element->FormControlType() ==
+                                       FormControlType::kInputHidden;
+  }
+
+  bool ShouldIgnoreMetaElement(const Element& element) const {
+    // Do not include meta elements that declare Content-Security-Policy
+    // directives. They should have already been enforced when the original
+    // document is loaded. Since only the rendered resources are encapsulated in
+    // the saved MHTML page, there is no need to carry the directives. If they
+    // are still kept in the MHTML, child frames that are referred to using cid:
+    // scheme could be prevented from loading.
+    if (!IsA<HTMLMetaElement>(element)) {
+      return false;
+    }
+    if (!element.FastHasAttribute(html_names::kContentAttr)) {
+      return false;
+    }
+    const AtomicString& http_equiv =
+        element.FastGetAttribute(html_names::kHttpEquivAttr);
+    return http_equiv == "Content-Security-Policy";
+  }
+
+  bool ShouldIgnorePopupOverlayElement(const Element& element) const {
+    // The element should be visible.
+    LayoutBox* box = element.GetLayoutBox();
+    if (!box) {
+      return false;
+    }
+
+    // The bounding box of the element should contain center point of the
+    // viewport.
+    LocalDOMWindow* window = element.GetDocument().domWindow();
+    DCHECK(window);
+    int center_x = window->innerWidth() / 2;
+    int center_y = window->innerHeight() / 2;
+    if (Page* page = element.GetDocument().GetPage()) {
+      center_x = page->GetChromeClient().WindowToViewportScalar(
+          window->GetFrame(), center_x);
+      center_y = page->GetChromeClient().WindowToViewportScalar(
+          window->GetFrame(), center_y);
+    }
+    if (!PhysicalRect(box->PhysicalLocation(), box->Size())
+             .Contains(LayoutUnit(center_x), LayoutUnit(center_y))) {
+      return false;
+    }
+
+    // The z-index should be greater than the threshold.
+    if (box->Style()->EffectiveZIndex() < kPopupOverlayZIndexThreshold) {
+      return false;
+    }
+
+    popup_overlays_skipped_ = true;
+
+    return true;
+  }
+
+  bool ShouldIgnoreAttribute(const Element& element,
+                             const Attribute& attribute) const override {
+    // TODO(fgorski): Presence of srcset attribute causes MHTML to not display
+    // images, as only the value of src is pulled into the archive. Discarding
+    // srcset prevents the problem. Long term we should make sure to MHTML plays
+    // nicely with srcset.
+    if (IsA<HTMLImageElement>(element) &&
+        (attribute.LocalName() == html_names::kSrcsetAttr ||
+         attribute.LocalName() == html_names::kSizesAttr)) {
+      return true;
+    }
+
+    // Do not save ping attribute since anyway the ping will be blocked from
+    // MHTML.
+    if (IsA<HTMLAnchorElement>(element) &&
+        attribute.LocalName() == html_names::kPingAttr) {
+      return true;
+    }
+
+    // The special attribute in a template element to denote the shadow DOM
+    // should only be generated from MHTML serialization. If it is found in the
+    // original page, it should be ignored.
+    if (IsA<HTMLTemplateElement>(element) &&
+        (attribute.LocalName() == kShadowModeAttributeName ||
+         attribute.LocalName() == kShadowDelegatesFocusAttributeName) &&
+        !shadow_template_elements_.Contains(&element)) {
+      return true;
+    }
+
+    // If srcdoc attribute for frame elements will be rewritten as src attribute
+    // containing link instead of html contents, don't ignore the attribute.
+    // Bail out now to avoid the check in Element::isScriptingAttribute.
+    bool is_src_doc_attribute = IsA<HTMLFrameElementBase>(element) &&
+                                attribute.GetName() == html_names::kSrcdocAttr;
+    String new_link_for_the_element;
+    if (is_src_doc_attribute &&
+        RewriteLink(element, new_link_for_the_element)) {
+      return false;
+    }
+
+    //  Drop integrity attribute for those links with subresource loaded.
+    auto* html_link_element = DynamicTo<HTMLLinkElement>(element);
+    if (attribute.LocalName() == html_names::kIntegrityAttr &&
+        html_link_element && html_link_element->sheet()) {
+      return true;
+    }
+
+    // Do not include attributes that contain javascript. This is because the
+    // script will not be executed when a MHTML page is being loaded.
+    return element.IsScriptingAttribute(attribute);
+  }
+
+  bool RewriteLink(const Element& element, String& rewritten_link) const {
+    auto* frame_owner = DynamicTo<HTMLFrameOwnerElement>(element);
+    if (!frame_owner) {
+      return false;
+    }
+
+    Frame* frame = frame_owner->ContentFrame();
+    if (!frame) {
+      return false;
+    }
+
+    WebString content_id = FrameSerializer::GetContentID(frame);
+    KURL cid_uri = MHTMLParser::ConvertContentIDToURI(content_id);
+    DCHECK(cid_uri.IsValid());
+    rewritten_link = cid_uri.GetString();
+    return true;
+  }
+
+  Vector<Attribute> GetCustomAttributes(const Element& element) {
+    Vector<Attribute> attributes;
+
+    if (auto* image = DynamicTo<HTMLImageElement>(element)) {
+      GetCustomAttributesForImageElement(*image, &attributes);
+    }
+
+    return attributes;
+  }
+
+  void GetCustomAttributesForImageElement(const HTMLImageElement& element,
+                                          Vector<Attribute>* attributes) {
+    // Currently only the value of src is pulled into the archive and the srcset
+    // attribute is ignored (see shouldIgnoreAttribute() above). If the device
+    // has a higher DPR, a different image from srcset could be loaded instead.
+    // When this occurs, we should provide the rendering width and height for
+    // <img> element if not set.
+
+    // The image should be loaded and participate the layout.
+    ImageResourceContent* image = element.CachedImage();
+    if (!image || !image->HasImage() || image->ErrorOccurred() ||
+        !element.GetLayoutObject()) {
+      return;
+    }
+
+    // The width and height attributes should not be set.
+    if (element.FastHasAttribute(html_names::kWidthAttr) ||
+        element.FastHasAttribute(html_names::kHeightAttr)) {
+      return;
+    }
+
+    // Check if different image is loaded. naturalWidth/naturalHeight will
+    // return the image size adjusted with current DPR.
+    if ((static_cast<int>(element.naturalWidth())) ==
+            image->GetImage()->width() &&
+        (static_cast<int>(element.naturalHeight())) ==
+            image->GetImage()->height()) {
+      return;
+    }
+
+    Attribute width_attribute(html_names::kWidthAttr,
+                              AtomicString::Number(element.LayoutBoxWidth()));
+    attributes->push_back(width_attribute);
+    Attribute height_attribute(html_names::kHeightAttr,
+                               AtomicString::Number(element.LayoutBoxHeight()));
+    attributes->push_back(height_attribute);
+  }
+
+  std::pair<ShadowRoot*, HTMLTemplateElement*> GetShadowTree(
+      const Element& element) const override {
+    ShadowRoot* shadow_root = element.GetShadowRoot();
+    if (!shadow_root || shadow_root->GetMode() == ShadowRootMode::kUserAgent) {
+      return std::pair<ShadowRoot*, HTMLTemplateElement*>();
+    }
+
+    // Put the shadow DOM content inside a template element. A special attribute
+    // is set to tell the mode of the shadow DOM.
+    HTMLTemplateElement* template_element =
+        MakeGarbageCollected<HTMLTemplateElement>(element.GetDocument());
+    template_element->setAttribute(
+        QualifiedName(AtomicString(kShadowModeAttributeName)),
+        AtomicString(shadow_root->GetMode() == ShadowRootMode::kOpen
+                         ? "open"
+                         : "closed"));
+    if (shadow_root->delegatesFocus()) {
+      template_element->setAttribute(
+          QualifiedName(AtomicString(kShadowDelegatesFocusAttributeName)),
+          g_empty_atom);
+    }
+    shadow_template_elements_.insert(template_element);
+
+    return std::pair<ShadowRoot*, HTMLTemplateElement*>(shadow_root,
+                                                        template_element);
+  }
+
+  void AppendCustomAttributes(const Element& element) override {
+    Vector<Attribute> attributes = GetCustomAttributes(element);
+    for (const auto& attribute : attributes) {
+      AppendAttribute(element, attribute);
+    }
+  }
+
+  bool ShouldIgnoreElement(const Element& element) const override {
+    if (IsA<HTMLScriptElement>(element)) {
+      return true;
+    }
+    if (IsA<HTMLNoScriptElement>(element)) {
+      return true;
+    }
+    auto* meta = DynamicTo<HTMLMetaElement>(element);
+    if (meta && meta->ComputeEncoding().IsValid()) {
+      return true;
+    }
+    // This is done in serializing document.StyleSheets.
+    if (IsA<HTMLStyleElement>(element)) {
+      return true;
+    }
+
+    if (ShouldIgnoreHiddenElement(element)) {
+      return true;
+    }
+    if (ShouldIgnoreMetaElement(element)) {
+      return true;
+    }
+    if (web_delegate_->RemovePopupOverlay() &&
+        ShouldIgnorePopupOverlayElement(element)) {
+      return true;
+    }
+    // Remove <link> for stylesheets that do not load.
+    auto* html_link_element = DynamicTo<HTMLLinkElement>(element);
+    if (html_link_element && html_link_element->RelAttribute().IsStyleSheet() &&
+        !html_link_element->sheet()) {
+      return true;
+    }
+    return false;
+  }
+
+  AtomicString AppendElement(const Element& element) override {
+    AtomicString prefix = MarkupAccumulator::AppendElement(element);
+
+    if (IsA<HTMLHeadElement>(element)) {
+      AppendExtraForHeadElement(element);
+    }
+    AddResourceForElement(*document_, element);
+
+    // FIXME: For object (plugins) tags and video tag we could replace them by
+    // an image of their current contents.
+
+    return prefix;
+  }
+
+  void AppendExtraForHeadElement(const Element& element) {
+    DCHECK(IsA<HTMLHeadElement>(element));
+
+    // TODO(tiger): Refactor MarkupAccumulator so it is easier to append an
+    // element like this, without special cases for XHTML
+    markup_.Append("<meta http-equiv=\"Content-Type\" content=\"");
+    AppendAttributeValue(document_->SuggestedMIMEType());
+    markup_.Append("; charset=");
+    AppendAttributeValue(document_->characterSet());
+    if (document_->IsXHTMLDocument()) {
+      markup_.Append("\" />");
+    } else {
+      markup_.Append("\">");
+    }
+
+    // The CSS rules of a style element can be updated dynamically independent
+    // of the CSS text included in the style element. So we can't use the inline
+    // CSS text defined in the style element. To solve this, we serialize the
+    // working CSS rules in document.stylesheets and wrap them in link elements.
+    AppendStylesheets(document_, true /*style_element_only*/);
+  }
+
+  void AppendStylesheets(Document* document, bool style_element_only) {
+    StyleSheetList& sheets = document->StyleSheets();
+    for (unsigned i = 0; i < sheets.length(); ++i) {
+      StyleSheet* sheet = sheets.item(i);
+      if (!sheet->IsCSSStyleSheet() || sheet->disabled()) {
+        continue;
+      }
+      if (style_element_only && !IsA<HTMLStyleElement>(sheet->ownerNode())) {
+        continue;
+      }
+
+      StringBuilder pseudo_sheet_url_builder;
+      pseudo_sheet_url_builder.Append("cid:css-");
+      pseudo_sheet_url_builder.Append(WTF::CreateCanonicalUUIDString());
+      pseudo_sheet_url_builder.Append("@mhtml.blink");
+      KURL pseudo_sheet_url = KURL(pseudo_sheet_url_builder.ToString());
+
+      markup_.Append("<link rel=\"stylesheet\" type=\"text/css\" href=\"");
+      markup_.Append(pseudo_sheet_url.GetString());
+      markup_.Append("\" />");
+
+      SerializeCSSStyleSheet(static_cast<CSSStyleSheet&>(*sheet),
+                             pseudo_sheet_url);
+    }
+  }
+
+  void AppendAttribute(const Element& element,
+                       const Attribute& attribute) override {
+    // Check if link rewriting can affect the attribute.
+    bool is_link_attribute = element.HasLegalLinkAttribute(attribute.GetName());
+    bool is_src_doc_attribute = IsA<HTMLFrameElementBase>(element) &&
+                                attribute.GetName() == html_names::kSrcdocAttr;
+    if (is_link_attribute || is_src_doc_attribute) {
+      // Check if the delegate wants to do link rewriting for the element.
+      String new_link_for_the_element;
+      if (RewriteLink(element, new_link_for_the_element)) {
+        if (is_link_attribute) {
+          // Rewrite element links.
+          AppendRewrittenAttribute(element, attribute.GetName().ToString(),
+                                   new_link_for_the_element);
+        } else {
+          DCHECK(is_src_doc_attribute);
+          // Emit src instead of srcdoc attribute for frame elements - we want
+          // the serialized subframe to use html contents from the link provided
+          // by Delegate::rewriteLink rather than html contents from srcdoc
+          // attribute.
+          AppendRewrittenAttribute(element, html_names::kSrcAttr.LocalName(),
+                                   new_link_for_the_element);
+        }
+        return;
+      }
+    }
+
+    // Fallback to appending the original attribute.
+    MarkupAccumulator::AppendAttribute(element, attribute);
+  }
+
+  void AppendAttributeValue(const String& attribute_value) {
+    MarkupFormatter::AppendAttributeValue(
+        markup_, attribute_value, IsA<HTMLDocument>(document_), *document_);
+  }
+
+  void AppendRewrittenAttribute(const Element& element,
+                                const String& attribute_name,
+                                const String& attribute_value) {
+    if (elements_with_rewritten_links_.Contains(&element)) {
+      return;
+    }
+    elements_with_rewritten_links_.insert(&element);
+
+    // Append the rewritten attribute.
+    // TODO(tiger): Refactor MarkupAccumulator so it is easier to append an
+    // attribute like this.
+    markup_.Append(' ');
+    markup_.Append(attribute_name);
+    markup_.Append("=\"");
+    AppendAttributeValue(attribute_value);
+    markup_.Append("\"");
+  }
+
+  void AddResourceForElement(Document& document, const Element& element) {
+    // We have to process in-line style as it might contain some resources
+    // (typically background images).
+    if (element.IsStyledElement()) {
+      RetrieveResourcesForProperties(element.InlineStyle(), document);
+      RetrieveResourcesForProperties(
+          const_cast<Element&>(element).PresentationAttributeStyle(), document);
+    }
+
+    if (const auto* image = DynamicTo<HTMLImageElement>(element)) {
+      AtomicString image_url_value;
+      const Element* parent = element.parentElement();
+      if (parent && IsA<HTMLPictureElement>(parent)) {
+        // If parent element is <picture>, use ImageSourceURL() to get best fit
+        // image URL from sibling source.
+        image_url_value = image->ImageSourceURL();
+      } else {
+        // Otherwise, it is single <img> element. We should get image url
+        // contained in href attribute. ImageSourceURL() may return a different
+        // URL from srcset attribute.
+        image_url_value = image->FastGetAttribute(html_names::kSrcAttr);
+      }
+      ImageResourceContent* cached_image = image->CachedImage();
+      resource_serializer_->AddImageToResources(
+          cached_image, document.CompleteURL(image_url_value));
+    } else if (const auto* input = DynamicTo<HTMLInputElement>(element)) {
+      if (input->FormControlType() == FormControlType::kInputImage &&
+          input->ImageLoader()) {
+        KURL image_url = input->Src();
+        ImageResourceContent* cached_image = input->ImageLoader()->GetContent();
+        resource_serializer_->AddImageToResources(cached_image, image_url);
+      }
+    } else if (const auto* link = DynamicTo<HTMLLinkElement>(element)) {
+      if (CSSStyleSheet* sheet = link->sheet()) {
+        KURL sheet_url =
+            document.CompleteURL(link->FastGetAttribute(html_names::kHrefAttr));
+        SerializeCSSStyleSheet(*sheet, sheet_url);
+      }
+    } else if (const auto* style = DynamicTo<HTMLStyleElement>(element)) {
+      if (CSSStyleSheet* sheet = style->sheet()) {
+        SerializeCSSStyleSheet(*sheet, NullURL());
+      }
+    } else if (const auto* plugin = DynamicTo<HTMLPlugInElement>(&element)) {
+      if (plugin->IsImageType() && plugin->ImageLoader()) {
+        KURL image_url = document.CompleteURL(plugin->Url());
+        ImageResourceContent* cached_image =
+            plugin->ImageLoader()->GetContent();
+        resource_serializer_->AddImageToResources(cached_image, image_url);
+      }
+    }
+  }
+
+  void SerializeCSSStyleSheet(CSSStyleSheet& style_sheet, const KURL& url) {
+    // If the URL is invalid or if it is a data URL this means that this CSS is
+    // defined inline, respectively in a <style> tag or in the data URL itself.
+    bool is_inline_css = !url.IsValid() || url.ProtocolIsData();
+    // If this CSS is not inline then it is identifiable by its URL. So just
+    // skip it if it has already been analyzed before.
+    if (!is_inline_css && !resource_serializer_->ShouldAddURL(url)) {
+      return;
+    }
+
+    TRACE_EVENT2("page-serialization",
+                 "FrameSerializer::serializeCSSStyleSheet", "type", "CSS",
+                 "url", url.ElidedString().Utf8());
+
+    // If this CSS is inlined its definition was already serialized with the
+    // frame HTML code that was previously generated. No need to regenerate it
+    // here.
+    if (!is_inline_css) {
+      StringBuilder css_text;
+      css_text.Append("@charset \"");
+      css_text.Append(String(style_sheet.Contents()->Charset().GetName())
+                          .DeprecatedLower());
+      css_text.Append("\";\n\n");
+
+      for (unsigned i = 0; i < style_sheet.length(); ++i) {
+        CSSRule* rule = style_sheet.ItemInternal(i);
+        String item_text = rule->cssText();
+        if (!item_text.empty()) {
+          css_text.Append(item_text);
+          if (i < style_sheet.length() - 1) {
+            css_text.Append("\n\n");
+          }
+        }
+      }
+
+      WTF::TextEncoding text_encoding(style_sheet.Contents()->Charset());
+      DCHECK(text_encoding.IsValid());
+      String text_string = css_text.ToString();
+      std::string text = text_encoding.Encode(
+          text_string, WTF::kCSSEncodedEntitiesForUnencodables);
+      resource_serializer_->AddToResources(
+          String("text/css"), SharedBuffer::Create(text.c_str(), text.length()),
+          url);
+    }
+
+    // Sub resources need to be serialized even if the CSS definition doesn't
+    // need to be.
+    for (unsigned i = 0; i < style_sheet.length(); ++i) {
+      SerializeCSSRule(style_sheet.ItemInternal(i));
+    }
+  }
+
+  void SerializeCSSRule(CSSRule* rule) {
+    DCHECK(rule->parentStyleSheet()->OwnerDocument());
+    Document& document = *rule->parentStyleSheet()->OwnerDocument();
+
+    switch (rule->GetType()) {
+      case CSSRule::kStyleRule:
+        RetrieveResourcesForProperties(
+            &To<CSSStyleRule>(rule)->GetStyleRule()->Properties(), document);
+        break;
+
+      case CSSRule::kImportRule: {
+        CSSImportRule* import_rule = To<CSSImportRule>(rule);
+        KURL sheet_base_url = rule->parentStyleSheet()->BaseURL();
+        DCHECK(sheet_base_url.IsValid());
+        KURL import_url = KURL(sheet_base_url, import_rule->href());
+        if (import_rule->styleSheet()) {
+          SerializeCSSStyleSheet(*import_rule->styleSheet(), import_url);
+        }
+        break;
+      }
+
+      // Rules inheriting CSSGroupingRule
+      case CSSRule::kNestedDeclarationsRule:
+      case CSSRule::kMediaRule:
+      case CSSRule::kSupportsRule:
+      case CSSRule::kContainerRule:
+      case CSSRule::kLayerBlockRule:
+      case CSSRule::kScopeRule:
+      case CSSRule::kStartingStyleRule: {
+        CSSRuleList* rule_list = rule->cssRules();
+        for (unsigned i = 0; i < rule_list->length(); ++i) {
+          SerializeCSSRule(rule_list->item(i));
+        }
+        break;
+      }
+
+      case CSSRule::kFontFaceRule:
+        RetrieveResourcesForProperties(
+            &To<CSSFontFaceRule>(rule)->StyleRule()->Properties(), document);
+        break;
+
+      case CSSRule::kCounterStyleRule:
+        // TODO(crbug.com/1176323): Handle image symbols in @counter-style rules
+        // when we implement it.
+        break;
+
+      case CSSRule::kMarginRule:
+      case CSSRule::kPageRule:
+        // TODO(crbug.com/40341678): Both page and margin rules may contain
+        // external resources (e.g. via background-image). FrameSerializer is at
+        // the mercy of whatever resource loading has already been triggered (by
+        // regular lifecycle updates). See crbug.com/364331857 . As such, unless
+        // the user has actually tried to print the page, resources inside @page
+        // rules won't have been loaded. Rather than introducing flaky behavior
+        // (sometimes @page resources are loaded, sometimes not), let's wait for
+        // that bug to be fixed.
+        break;
+
+      // Rules in which no external resources can be referenced
+      case CSSRule::kCharsetRule:
+      case CSSRule::kFontPaletteValuesRule:
+      case CSSRule::kFontFeatureRule:
+      case CSSRule::kFontFeatureValuesRule:
+      case CSSRule::kPropertyRule:
+      case CSSRule::kKeyframesRule:
+      case CSSRule::kKeyframeRule:
+      case CSSRule::kNamespaceRule:
+      case CSSRule::kLayerStatementRule:
+      case CSSRule::kViewTransitionRule:
+      case CSSRule::kPositionTryRule:
+        break;
+    }
+  }
+
+  void RetrieveResourcesForProperties(
+      const CSSPropertyValueSet* style_declaration,
+      Document& document) {
+    if (!style_declaration) {
+      return;
+    }
+
+    // The background-image and list-style-image (for ul or ol) are the CSS
+    // properties that make use of images. We iterate to make sure we include
+    // any other image properties there might be.
+    unsigned property_count = style_declaration->PropertyCount();
+    for (unsigned i = 0; i < property_count; ++i) {
+      const CSSValue& css_value = style_declaration->PropertyAt(i).Value();
+      RetrieveResourcesForCSSValue(css_value, document);
+    }
+  }
+
+  void RetrieveResourcesForCSSValue(const CSSValue& css_value,
+                                    Document& document) {
+    if (const auto* image_value = DynamicTo<CSSImageValue>(css_value)) {
+      if (image_value->IsCachePending()) {
+        return;
+      }
+      StyleImage* style_image = image_value->CachedImage();
+      if (!style_image || !style_image->IsImageResource()) {
+        return;
+      }
+
+      resource_serializer_->AddImageToResources(
+          style_image->CachedImage(), style_image->CachedImage()->Url());
+    } else if (const auto* font_face_src_value =
+                   DynamicTo<CSSFontFaceSrcValue>(css_value)) {
+      if (font_face_src_value->IsLocal()) {
+        return;
+      }
+
+      resource_serializer_->AddFontToResources(
+          font_face_src_value->Fetch(document.GetExecutionContext(), nullptr));
+    } else if (const auto* css_value_list =
+                   DynamicTo<CSSValueList>(css_value)) {
+      for (unsigned i = 0; i < css_value_list->length(); i++) {
+        RetrieveResourcesForCSSValue(css_value_list->Item(i), document);
+      }
+    }
+  }
+
+  MultiResourcePacker* resource_serializer_;
+  WebFrameSerializer::MHTMLPartsGenerationDelegate* web_delegate_;
   Document* document_;
 
+  mutable HeapHashSet<WeakMember<const Element>> shadow_template_elements_;
+  mutable bool popup_overlays_skipped_ = false;
+
   // Elements with links rewritten via appendAttribute method.
   HeapHashSet<Member<const Element>> elements_with_rewritten_links_;
 };
 
-SerializerMarkupAccumulator::SerializerMarkupAccumulator(
-    FrameSerializerDelegateImpl& delegate,
-    FrameSerializerResourceDelegate& resource_delegate,
-    Document& document)
-    : MarkupAccumulator(kResolveAllURLs,
-                        IsA<HTMLDocument>(document) ? SerializationType::kHTML
-                                                    : SerializationType::kXML,
-                        ShadowRootInclusion()),
-      delegate_(delegate),
-      resource_delegate_(resource_delegate),
-      document_(&document) {}
-
-SerializerMarkupAccumulator::~SerializerMarkupAccumulator() = default;
-
-void SerializerMarkupAccumulator::AppendCustomAttributes(
-    const Element& element) {
-  Vector<Attribute> attributes = delegate_.GetCustomAttributes(element);
-  for (const auto& attribute : attributes)
-    AppendAttribute(element, attribute);
-}
-
-bool SerializerMarkupAccumulator::ShouldIgnoreAttribute(
-    const Element& element,
-    const Attribute& attribute) const {
-  return delegate_.ShouldIgnoreAttribute(element, attribute);
-}
-
-bool SerializerMarkupAccumulator::ShouldIgnoreElement(
-    const Element& element) const {
-  if (IsA<HTMLScriptElement>(element))
-    return true;
-  if (IsA<HTMLNoScriptElement>(element))
-    return true;
-  auto* meta = DynamicTo<HTMLMetaElement>(element);
-  if (meta && meta->ComputeEncoding().IsValid()) {
-    return true;
-  }
-  // This is done in serializing document.StyleSheets.
-  if (IsA<HTMLStyleElement>(element))
-    return true;
-  return delegate_.ShouldIgnoreElement(element);
-}
-
-AtomicString SerializerMarkupAccumulator::AppendElement(
-    const Element& element) {
-  AtomicString prefix = MarkupAccumulator::AppendElement(element);
-
-  if (IsA<HTMLHeadElement>(element))
-    AppendExtraForHeadElement(element);
-
-  resource_delegate_.AddResourceForElement(*document_, element);
-
-  // FIXME: For object (plugins) tags and video tag we could replace them by an
-  // image of their current contents.
-
-  return prefix;
-}
-
-void SerializerMarkupAccumulator::AppendExtraForHeadElement(
-    const Element& element) {
-  DCHECK(IsA<HTMLHeadElement>(element));
-
-  // TODO(tiger): Refactor MarkupAccumulator so it is easier to append an
-  // element like this, without special cases for XHTML
-  markup_.Append("<meta http-equiv=\"Content-Type\" content=\"");
-  AppendAttributeValue(document_->SuggestedMIMEType());
-  markup_.Append("; charset=");
-  AppendAttributeValue(document_->characterSet());
-  if (document_->IsXHTMLDocument())
-    markup_.Append("\" />");
-  else
-    markup_.Append("\">");
-
-  // The CSS rules of a style element can be updated dynamically independent of
-  // the CSS text included in the style element. So we can't use the inline
-  // CSS text defined in the style element. To solve this, we serialize the
-  // working CSS rules in document.stylesheets and wrap them in link elements.
-  AppendStylesheets(document_, true /*style_element_only*/);
-}
-
-void SerializerMarkupAccumulator::AppendStylesheets(Document* document,
-                                                    bool style_element_only) {
-  StyleSheetList& sheets = document->StyleSheets();
-  for (unsigned i = 0; i < sheets.length(); ++i) {
-    StyleSheet* sheet = sheets.item(i);
-    if (!sheet->IsCSSStyleSheet() || sheet->disabled())
-      continue;
-    if (style_element_only && !IsA<HTMLStyleElement>(sheet->ownerNode()))
-      continue;
-
-    StringBuilder pseudo_sheet_url_builder;
-    pseudo_sheet_url_builder.Append("cid:css-");
-    pseudo_sheet_url_builder.Append(WTF::CreateCanonicalUUIDString());
-    pseudo_sheet_url_builder.Append("@mhtml.blink");
-    KURL pseudo_sheet_url = KURL(pseudo_sheet_url_builder.ToString());
-
-    markup_.Append("<link rel=\"stylesheet\" type=\"text/css\" href=\"");
-    markup_.Append(pseudo_sheet_url.GetString());
-    markup_.Append("\" />");
-
-    resource_delegate_.SerializeCSSStyleSheet(
-        static_cast<CSSStyleSheet&>(*sheet), pseudo_sheet_url);
-  }
-}
-
-void SerializerMarkupAccumulator::AppendAttribute(const Element& element,
-                                                  const Attribute& attribute) {
-  // Check if link rewriting can affect the attribute.
-  bool is_link_attribute = element.HasLegalLinkAttribute(attribute.GetName());
-  bool is_src_doc_attribute = IsA<HTMLFrameElementBase>(element) &&
-                              attribute.GetName() == html_names::kSrcdocAttr;
-  if (is_link_attribute || is_src_doc_attribute) {
-    // Check if the delegate wants to do link rewriting for the element.
-    String new_link_for_the_element;
-    if (delegate_.RewriteLink(element, new_link_for_the_element)) {
-      if (is_link_attribute) {
-        // Rewrite element links.
-        AppendRewrittenAttribute(element, attribute.GetName().ToString(),
-                                 new_link_for_the_element);
-      } else {
-        DCHECK(is_src_doc_attribute);
-        // Emit src instead of srcdoc attribute for frame elements - we want the
-        // serialized subframe to use html contents from the link provided by
-        // Delegate::rewriteLink rather than html contents from srcdoc
-        // attribute.
-        AppendRewrittenAttribute(element, html_names::kSrcAttr.LocalName(),
-                                 new_link_for_the_element);
-      }
-      return;
-    }
-  }
-
-  // Fallback to appending the original attribute.
-  MarkupAccumulator::AppendAttribute(element, attribute);
-}
-
-std::pair<ShadowRoot*, HTMLTemplateElement*>
-SerializerMarkupAccumulator::GetShadowTree(const Element& element) const {
-  return delegate_.GetShadowTree(element);
-}
-
-void SerializerMarkupAccumulator::AppendAttributeValue(
-    const String& attribute_value) {
-  MarkupFormatter::AppendAttributeValue(
-      markup_, attribute_value, IsA<HTMLDocument>(document_), *document_);
-}
-
-void SerializerMarkupAccumulator::AppendRewrittenAttribute(
-    const Element& element,
-    const String& attribute_name,
-    const String& attribute_value) {
-  if (elements_with_rewritten_links_.Contains(&element))
-    return;
-  elements_with_rewritten_links_.insert(&element);
-
-  // Append the rewritten attribute.
-  // TODO(tiger): Refactor MarkupAccumulator so it is easier to append an
-  // attribute like this.
-  markup_.Append(' ');
-  markup_.Append(attribute_name);
-  markup_.Append("=\"");
-  AppendAttributeValue(attribute_value);
-  markup_.Append("\"");
-}
-
 // TODO(tiger): Right now there is no support for rewriting URLs inside CSS
 // documents which leads to bugs like <https://crbug.com/251898>. Not being
 // able to rewrite URLs inside CSS documents means that resources imported from
@@ -575,300 +833,41 @@
 // "Webpage, Complete" method of saving a page. It will take some work but it
 // needs to be done if we want to continue to support non-MHTML saved pages.
 
-FrameSerializer::FrameSerializer(
+// static
+void FrameSerializer::SerializeFrame(
     Deque<SerializedResource>& resources,
-    WebFrameSerializer::MHTMLPartsGenerationDelegate* web_delegate)
-    : resources_(&resources),
-      web_delegate_(web_delegate),
-      delegate_(*web_delegate, shadow_template_elements_) {}
-
-void FrameSerializer::SerializeFrame(const LocalFrame& frame) {
+    WebFrameSerializer::MHTMLPartsGenerationDelegate& web_delegate,
+    const LocalFrame& frame) {
   TRACE_EVENT0("page-serialization", "FrameSerializer::serializeFrame");
   DCHECK(frame.GetDocument());
   Document& document = *frame.GetDocument();
   KURL url = document.Url();
-
+  MultiResourcePacker resource_serializer(&resources, &web_delegate);
   // If frame is an image document, add the image and don't continue
   if (auto* image_document = DynamicTo<ImageDocument>(document)) {
-    AddImageToResources(image_document->CachedImage(), url);
+    resource_serializer.AddImageToResources(image_document->CachedImage(), url);
     return;
   }
 
   {
     TRACE_EVENT0("page-serialization", "FrameSerializer::serializeFrame HTML");
-    SerializerMarkupAccumulator accumulator(delegate_, *this, document);
+    SerializerMarkupAccumulator accumulator(&resource_serializer, &web_delegate,
+                                            document);
     String text =
         accumulator.SerializeNodes<EditingStrategy>(document, kIncludeNode);
 
     std::string frame_html =
         document.Encoding().Encode(text, WTF::kEntitiesForUnencodables);
-    // Note that the frame has to be 1st resource.
-    resources_->push_front(SerializedResource(
-        url, document.SuggestedMIMEType(),
-        SharedBuffer::Create(frame_html.c_str(), frame_html.length())));
-  }
-}
-
-void FrameSerializer::AddResourceForElement(Document& document,
-                                            const Element& element) {
-  // We have to process in-line style as it might contain some resources
-  // (typically background images).
-  if (element.IsStyledElement()) {
-    RetrieveResourcesForProperties(element.InlineStyle(), document);
-    RetrieveResourcesForProperties(
-        const_cast<Element&>(element).PresentationAttributeStyle(), document);
-  }
-
-  if (const auto* image = DynamicTo<HTMLImageElement>(element)) {
-    AtomicString image_url_value;
-    const Element* parent = element.parentElement();
-    if (parent && IsA<HTMLPictureElement>(parent)) {
-      // If parent element is <picture>, use ImageSourceURL() to get best fit
-      // image URL from sibling source.
-      image_url_value = image->ImageSourceURL();
-    } else {
-      // Otherwise, it is single <img> element. We should get image url
-      // contained in href attribute. ImageSourceURL() may return a different
-      // URL from srcset attribute.
-      image_url_value = image->FastGetAttribute(html_names::kSrcAttr);
-    }
-    ImageResourceContent* cached_image = image->CachedImage();
-    AddImageToResources(cached_image, document.CompleteURL(image_url_value));
-  } else if (const auto* input = DynamicTo<HTMLInputElement>(element)) {
-    if (input->FormControlType() == FormControlType::kInputImage &&
-        input->ImageLoader()) {
-      KURL image_url = input->Src();
-      ImageResourceContent* cached_image = input->ImageLoader()->GetContent();
-      AddImageToResources(cached_image, image_url);
-    }
-  } else if (const auto* link = DynamicTo<HTMLLinkElement>(element)) {
-    if (CSSStyleSheet* sheet = link->sheet()) {
-      KURL sheet_url =
-          document.CompleteURL(link->FastGetAttribute(html_names::kHrefAttr));
-      SerializeCSSStyleSheet(*sheet, sheet_url);
-    }
-  } else if (const auto* style = DynamicTo<HTMLStyleElement>(element)) {
-    if (CSSStyleSheet* sheet = style->sheet())
-      SerializeCSSStyleSheet(*sheet, NullURL());
-  } else if (const auto* plugin = DynamicTo<HTMLPlugInElement>(&element)) {
-    if (plugin->IsImageType() && plugin->ImageLoader()) {
-      KURL image_url = document.CompleteURL(plugin->Url());
-      ImageResourceContent* cached_image = plugin->ImageLoader()->GetContent();
-      AddImageToResources(cached_image, image_url);
-    }
-  }
-}
-
-void FrameSerializer::SerializeCSSStyleSheet(CSSStyleSheet& style_sheet,
-                                             const KURL& url) {
-  // If the URL is invalid or if it is a data URL this means that this CSS is
-  // defined inline, respectively in a <style> tag or in the data URL itself.
-  bool is_inline_css = !url.IsValid() || url.ProtocolIsData();
-  // If this CSS is not inline then it is identifiable by its URL. So just skip
-  // it if it has already been analyzed before.
-  if (!is_inline_css && (resource_urls_.Contains(url) ||
-                         delegate_.ShouldSkipResourceWithURL(url))) {
-    return;
-  }
-  if (!is_inline_css)
-    resource_urls_.insert(url);
-
-  TRACE_EVENT2("page-serialization", "FrameSerializer::serializeCSSStyleSheet",
-               "type", "CSS", "url", url.ElidedString().Utf8());
-
-  // If this CSS is inlined its definition was already serialized with the frame
-  // HTML code that was previously generated. No need to regenerate it here.
-  if (!is_inline_css) {
-    StringBuilder css_text;
-    css_text.Append("@charset \"");
-    css_text.Append(
-        String(style_sheet.Contents()->Charset().GetName()).DeprecatedLower());
-    css_text.Append("\";\n\n");
-
-    for (unsigned i = 0; i < style_sheet.length(); ++i) {
-      CSSRule* rule = style_sheet.ItemInternal(i);
-      String item_text = rule->cssText();
-      if (!item_text.empty()) {
-        css_text.Append(item_text);
-        if (i < style_sheet.length() - 1)
-          css_text.Append("\n\n");
-      }
-    }
-
-    WTF::TextEncoding text_encoding(style_sheet.Contents()->Charset());
-    DCHECK(text_encoding.IsValid());
-    String text_string = css_text.ToString();
-    std::string text = text_encoding.Encode(
-        text_string, WTF::kCSSEncodedEntitiesForUnencodables);
-    resources_->push_back(
-        SerializedResource(url, String("text/css"),
-                           SharedBuffer::Create(text.c_str(), text.length())));
-  }
-
-  // Sub resources need to be serialized even if the CSS definition doesn't
-  // need to be.
-  for (unsigned i = 0; i < style_sheet.length(); ++i)
-    SerializeCSSRule(style_sheet.ItemInternal(i));
-}
-
-void FrameSerializer::SerializeCSSRule(CSSRule* rule) {
-  DCHECK(rule->parentStyleSheet()->OwnerDocument());
-  Document& document = *rule->parentStyleSheet()->OwnerDocument();
-
-  switch (rule->GetType()) {
-    case CSSRule::kStyleRule:
-      RetrieveResourcesForProperties(
-          &To<CSSStyleRule>(rule)->GetStyleRule()->Properties(), document);
-      break;
-
-    case CSSRule::kImportRule: {
-      CSSImportRule* import_rule = To<CSSImportRule>(rule);
-      KURL sheet_base_url = rule->parentStyleSheet()->BaseURL();
-      DCHECK(sheet_base_url.IsValid());
-      KURL import_url = KURL(sheet_base_url, import_rule->href());
-      if (import_rule->styleSheet())
-        SerializeCSSStyleSheet(*import_rule->styleSheet(), import_url);
-      break;
-    }
-
-    // Rules inheriting CSSGroupingRule
-    case CSSRule::kNestedDeclarationsRule:
-    case CSSRule::kMediaRule:
-    case CSSRule::kSupportsRule:
-    case CSSRule::kContainerRule:
-    case CSSRule::kLayerBlockRule:
-    case CSSRule::kScopeRule:
-    case CSSRule::kStartingStyleRule: {
-      CSSRuleList* rule_list = rule->cssRules();
-      for (unsigned i = 0; i < rule_list->length(); ++i)
-        SerializeCSSRule(rule_list->item(i));
-      break;
-    }
-
-    case CSSRule::kFontFaceRule:
-      RetrieveResourcesForProperties(
-          &To<CSSFontFaceRule>(rule)->StyleRule()->Properties(), document);
-      break;
-
-    case CSSRule::kCounterStyleRule:
-      // TODO(crbug.com/1176323): Handle image symbols in @counter-style rules
-      // when we implement it.
-      break;
-
-    case CSSRule::kMarginRule:
-    case CSSRule::kPageRule:
-      // TODO(crbug.com/40341678): Both page and margin rules may contain
-      // external resources (e.g. via background-image). FrameSerializer is at
-      // the mercy of whatever resource loading has already been triggered (by
-      // regular lifecycle updates). See crbug.com/364331857 . As such, unless
-      // the user has actually tried to print the page, resources inside @page
-      // rules won't have been loaded. Rather than introducing flaky behavior
-      // (sometimes @page resources are loaded, sometimes not), let's wait for
-      // that bug to be fixed.
-      break;
-
-    // Rules in which no external resources can be referenced
-    case CSSRule::kCharsetRule:
-    case CSSRule::kFontPaletteValuesRule:
-    case CSSRule::kFontFeatureRule:
-    case CSSRule::kFontFeatureValuesRule:
-    case CSSRule::kPropertyRule:
-    case CSSRule::kKeyframesRule:
-    case CSSRule::kKeyframeRule:
-    case CSSRule::kNamespaceRule:
-    case CSSRule::kLayerStatementRule:
-    case CSSRule::kViewTransitionRule:
-    case CSSRule::kPositionTryRule:
-      break;
-  }
-}
-
-bool FrameSerializer::ShouldAddURL(const KURL& url) {
-  return url.IsValid() && !resource_urls_.Contains(url) &&
-         !url.ProtocolIsData() && !delegate_.ShouldSkipResourceWithURL(url);
-}
-
-void FrameSerializer::AddToResources(
-    const String& mime_type,
-    scoped_refptr<const SharedBuffer> data,
-    const KURL& url) {
-  if (!data) {
-    DLOG(ERROR) << "No data for resource " << url.GetString();
-    return;
-  }
-
-  resources_->push_back(SerializedResource(url, mime_type, std::move(data)));
-}
-
-void FrameSerializer::AddImageToResources(ImageResourceContent* image,
-                                          const KURL& url) {
-  if (!ShouldAddURL(url))
-    return;
-  resource_urls_.insert(url);
-  if (!image || !image->HasImage() || image->ErrorOccurred())
-    return;
-
-  TRACE_EVENT2("page-serialization", "FrameSerializer::addImageToResources",
-               "type", "image", "url", url.ElidedString().Utf8());
-  scoped_refptr<const SharedBuffer> data = image->GetImage()->Data();
-  AddToResources(image->GetResponse().MimeType(), data, url);
-}
-
-void FrameSerializer::AddFontToResources(FontResource& font) {
-  if (!ShouldAddURL(font.Url()))
-    return;
-  resource_urls_.insert(font.Url());
-  if (!font.IsLoaded() || !font.ResourceBuffer())
-    return;
-
-  scoped_refptr<const SharedBuffer> data(font.ResourceBuffer());
-
-  AddToResources(font.GetResponse().MimeType(), data, font.Url());
-}
-
-void FrameSerializer::RetrieveResourcesForProperties(
-    const CSSPropertyValueSet* style_declaration,
-    Document& document) {
-  if (!style_declaration)
-    return;
-
-  // The background-image and list-style-image (for ul or ol) are the CSS
-  // properties that make use of images. We iterate to make sure we include any
-  // other image properties there might be.
-  unsigned property_count = style_declaration->PropertyCount();
-  for (unsigned i = 0; i < property_count; ++i) {
-    const CSSValue& css_value = style_declaration->PropertyAt(i).Value();
-    RetrieveResourcesForCSSValue(css_value, document);
-  }
-}
-
-void FrameSerializer::RetrieveResourcesForCSSValue(const CSSValue& css_value,
-                                                   Document& document) {
-  if (const auto* image_value = DynamicTo<CSSImageValue>(css_value)) {
-    if (image_value->IsCachePending())
-      return;
-    StyleImage* style_image = image_value->CachedImage();
-    if (!style_image || !style_image->IsImageResource())
-      return;
-
-    AddImageToResources(style_image->CachedImage(),
-                        style_image->CachedImage()->Url());
-  } else if (const auto* font_face_src_value =
-                 DynamicTo<CSSFontFaceSrcValue>(css_value)) {
-    if (font_face_src_value->IsLocal())
-      return;
-
-    AddFontToResources(
-        font_face_src_value->Fetch(document.GetExecutionContext(), nullptr));
-  } else if (const auto* css_value_list = DynamicTo<CSSValueList>(css_value)) {
-    for (unsigned i = 0; i < css_value_list->length(); i++)
-      RetrieveResourcesForCSSValue(css_value_list->Item(i), document);
+    resource_serializer.AddMainResource(
+        document.SuggestedMIMEType(),
+        SharedBuffer::Create(frame_html.c_str(), frame_html.length()), url);
   }
 }
 
 // Returns MOTW (Mark of the Web) declaration before html tag which is in
 // HTML comment, e.g. "<!-- saved from url=(%04d)%s -->"
 // See http://msdn2.microsoft.com/en-us/library/ms537628(VS.85).aspx.
+// static
 String FrameSerializer::MarkOfTheWebDeclaration(const KURL& url) {
   StringBuilder builder;
   bool emits_minus = false;
diff --git a/third_party/blink/renderer/core/frame/frame_serializer.h b/third_party/blink/renderer/core/frame/frame_serializer.h
index 253b5f0..ddc61fe 100644
--- a/third_party/blink/renderer/core/frame/frame_serializer.h
+++ b/third_party/blink/renderer/core/frame/frame_serializer.h
@@ -33,120 +33,23 @@
 
 #include "third_party/blink/public/web/web_frame_serializer.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/dom/attribute.h"
-#include "third_party/blink/renderer/core/html/html_template_element.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
-#include "third_party/blink/renderer/platform/weborigin/kurl_hash.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/deque.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
-#include "third_party/blink/renderer/platform/wtf/hash_set.h"
-#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
-class CSSPropertyValueSet;
-class CSSRule;
-class CSSStyleSheet;
-class CSSValue;
-class Document;
-class Element;
-class FontResource;
-class HTMLTemplateElement;
-class ImageResourceContent;
 class LocalFrame;
 class Frame;
-class ShadowRoot;
 
 struct SerializedResource;
 
-class FrameSerializerResourceDelegate {
+// This class is used to serialize frame's contents to MHTML. It serializes
+// frame's document and resources such as images and CSS stylesheets.
+class CORE_EXPORT FrameSerializer {
  public:
-  virtual ~FrameSerializerResourceDelegate() = default;
-
-  // Adds the resource needed to serialize an element.
-  virtual void AddResourceForElement(Document&, const Element&) = 0;
-
-  // Serializes the stylesheet back to text and adds it to the resources if
-  // URL is not-empty.  It also adds any resources included in that stylesheet
-  // (including any imported stylesheets and their own resources).
-  virtual void SerializeCSSStyleSheet(CSSStyleSheet&, const KURL&) = 0;
-};
-
-// An implementation of FrameSerializer's delegate which is used to serialize a
-// frame to a MHTML file.
-class FrameSerializerDelegateImpl final {
-  STACK_ALLOCATED();
-
- public:
-  FrameSerializerDelegateImpl(WebFrameSerializer::MHTMLPartsGenerationDelegate&,
-                              HeapHashSet<WeakMember<const Element>>&);
-  FrameSerializerDelegateImpl(const FrameSerializerDelegateImpl&) = delete;
-  FrameSerializerDelegateImpl& operator=(const FrameSerializerDelegateImpl&) =
-      delete;
-  ~FrameSerializerDelegateImpl() = default;
-
-  // Controls whether HTML serialization should skip the given element.
-  bool ShouldIgnoreElement(const Element&);
-  // Controls whether HTML serialization should skip the given attribute.
-  bool ShouldIgnoreAttribute(const Element&, const Attribute&);
-  // Method allowing the Delegate control which URLs are written into the
-  // generated html document.
-  //
-  // When URL of the element needs to be rewritten, this method should
-  // return true and populate |rewrittenLink| with a desired value of the
-  // html attribute value to be used in place of the original link.
-  // (i.e. in place of img.src or iframe.src or object.data).
-  //
-  // If no link rewriting is desired, this method should return false.
-  bool RewriteLink(const Element&, String& rewritten_link);
-
-  // Tells whether to skip serialization of a subresource or CSSStyleSheet
-  // with a given URI. Used to deduplicate resources across multiple frames.
-  bool ShouldSkipResourceWithURL(const KURL&);
-
-  // Returns custom attributes that need to add in order to serialize the
-  // element.
-  Vector<Attribute> GetCustomAttributes(const Element&);
-
-  // Serializes *all* open *and* closed (non-UA) shadow roots it finds.
-  std::pair<ShadowRoot*, HTMLTemplateElement*> GetShadowTree(
-      const Element&) const;
-
- private:
-  bool ShouldIgnoreHiddenElement(const Element&);
-  bool ShouldIgnoreMetaElement(const Element&);
-  bool ShouldIgnorePopupOverlayElement(const Element&);
-  void GetCustomAttributesForImageElement(const HTMLImageElement&,
-                                          Vector<Attribute>*);
-
-  WebFrameSerializer::MHTMLPartsGenerationDelegate& web_delegate_;
-  HeapHashSet<WeakMember<const Element>>& shadow_template_elements_;
-  bool popup_overlays_skipped_;
-};
-
-// This class is used to serialize frame's contents back to text (typically
-// HTML).  It serializes frame's document and resources such as images and CSS
-// stylesheets.
-class CORE_EXPORT FrameSerializer : public FrameSerializerResourceDelegate {
-  STACK_ALLOCATED();
-
- public:
-  // Constructs a serializer that will write output to the given deque of
-  // SerializedResources and uses the Delegate for controlling some
-  // serialization aspects.  Callers need to ensure that both arguments stay
-  // alive until the FrameSerializer gets destroyed.
-  FrameSerializer(
-      Deque<SerializedResource>&,
-      WebFrameSerializer::MHTMLPartsGenerationDelegate* web_delegate);
-
-  // Initiates the serialization of the frame. All serialized content and
-  // retrieved resources are added to the Deque passed to the constructor.
-  // The first resource in that deque is the frame's serialized content.
-  // Subsequent resources are images, css, etc.
-  void SerializeFrame(const LocalFrame&);
-
-  static String MarkOfTheWebDeclaration(const KURL&);
+  FrameSerializer() = delete;
 
   // Returns a Content-ID to be used for the given frame.
   // See rfc2557 - section 8.3 - "Use of the Content-ID header and CID URLs".
@@ -154,32 +57,17 @@
   // (i.e. the strings should include the angle brackets).
   static String GetContentID(Frame* frame);
 
- private:
-  void AddResourceForElement(Document&, const Element&) override;
-  void SerializeCSSStyleSheet(CSSStyleSheet&, const KURL&) override;
+  // Serializes the frame. Writes output to the given deque of
+  // SerializedResources and uses `web_delegate` for controlling some
+  // serialization aspects. All serialized content and retrieved resources are
+  // added to `resources`. The first resource is the frame's serialized content.
+  // Subsequent resources are images, css, etc.
+  static void SerializeFrame(
+      Deque<SerializedResource>& resources,
+      WebFrameSerializer::MHTMLPartsGenerationDelegate& web_delegate,
+      const LocalFrame&);
 
-  // Serializes the css rule (including any imported stylesheets), adding
-  // referenced resources.
-  void SerializeCSSRule(CSSRule*);
-
-  bool ShouldAddURL(const KURL&);
-
-  void AddToResources(const String& mime_type,
-                      scoped_refptr<const SharedBuffer>,
-                      const KURL&);
-  void AddImageToResources(ImageResourceContent*, const KURL&);
-  void AddFontToResources(FontResource&);
-
-  void RetrieveResourcesForProperties(const CSSPropertyValueSet*, Document&);
-  void RetrieveResourcesForCSSValue(const CSSValue&, Document&);
-
-  Deque<SerializedResource>* resources_;
-  // This hashset is only used for de-duplicating resources to be serialized.
-  HashSet<KURL> resource_urls_;
-
-  HeapHashSet<WeakMember<const Element>> shadow_template_elements_;
-  WebFrameSerializer::MHTMLPartsGenerationDelegate* web_delegate_;
-  FrameSerializerDelegateImpl delegate_;
+  static String MarkOfTheWebDeclaration(const KURL&);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/frame_serializer_test.cc b/third_party/blink/renderer/core/frame/frame_serializer_test.cc
index f524473..2941dd8a 100644
--- a/third_party/blink/renderer/core/frame/frame_serializer_test.cc
+++ b/third_party/blink/renderer/core/frame/frame_serializer_test.cc
@@ -122,12 +122,12 @@
     // load.
     frame_test_helpers::PumpPendingRequestsForFrameToLoad(
         helper_.GetWebView()->MainFrameImpl());
-    FrameSerializer serializer(resources_, this);
     Frame* frame = helper_.LocalMainFrame()->GetFrame();
     for (; frame; frame = frame->Tree().TraverseNext()) {
       // This is safe, because tests do not do cross-site navigation
       // (and therefore don't have remote frames).
-      serializer.SerializeFrame(*To<LocalFrame>(frame));
+      FrameSerializer::SerializeFrame(resources_, *this,
+                                      *To<LocalFrame>(frame));
       // Don't serialize the same resource on subsequent frames. This mimics how
       // FrameSerializer is actually used.
       for (auto& res : GetResources()) {
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.h b/third_party/blink/renderer/core/scroll/scrollable_area.h
index 9e7e959a9..9ffb7a8 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.h
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.h
@@ -667,6 +667,8 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(ScrollableAreaTest,
                            PopupOverlayScrollbarShouldNotFadeOut);
+  FRIEND_TEST_ALL_PREFIXES(ScrollableAreaTest,
+                           FilterIncomingScrollDuringSmoothUserScroll);
 
   void SetScrollbarsHiddenIfOverlayInternal(bool);
 
@@ -717,10 +719,16 @@
         incoming_type == mojom::blink::ScrollType::kAnchoring) {
       return false;
     }
-    // If the current smooth scroll is UserScroll, but the incoming scroll is
-    // not, filter the incoming scroll. See crbug.com/913009 for more details.
+    // If the current smooth scroll is a kUser scroll, i.e. a smooth scroll
+    // triggered by find-in-page, filter the incoming scroll unless it is:
+    // - another find-in-page scroll (kUser),
+    // - a gesture scroll (kCompositor), or
+    // - an update from the current find-in-page scroll animation running on the
+    //   compositor (kCompositor).
+    // See crbug.com/913009, crbug.com/365493092 for more details.
     if (old_type == mojom::blink::ScrollType::kUser &&
-        incoming_type != mojom::blink::ScrollType::kUser) {
+        incoming_type != mojom::blink::ScrollType::kUser &&
+        incoming_type != mojom::blink::ScrollType::kCompositor) {
       return true;
     }
     // TODO(crbug.com/325081538, crbug.com/342093060): Ideally, if the incoming
@@ -734,6 +742,11 @@
     return false;
   }
 
+  void set_active_smooth_scroll_type_for_testing(
+      mojom::blink::ScrollType type) {
+    active_smooth_scroll_type_ = type;
+  }
+
   // This animator is used to handle painting animations for MacOS scrollbars
   // using AppKit-specific code (Cocoa APIs). It requires input from
   // ScrollableArea about changes on scrollbars. For other platforms, painting
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area_test.cc b/third_party/blink/renderer/core/scroll/scrollable_area_test.cc
index 29173610..2a7374a 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area_test.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area_test.cc
@@ -518,4 +518,30 @@
   EXPECT_EQ(offset.x(), max_horizontal_scroll_offset);
 }
 
+TEST_P(ScrollableAreaTest, FilterIncomingScrollDuringSmoothUserScroll) {
+  using mojom::blink::ScrollType;
+  MockScrollableArea* area =
+      MockScrollableArea::Create(ScrollOffset(100, 100), ScrollOffset(0, 0));
+  area->set_active_smooth_scroll_type_for_testing(ScrollType::kUser);
+  const std::vector<mojom::blink::ScrollType> scroll_types = {
+      ScrollType::kUser,       ScrollType::kProgrammatic,
+      ScrollType::kClamping,   ScrollType::kCompositor,
+      ScrollType::kAnchoring,  ScrollType::kSequenced,
+      ScrollType::kScrollStart};
+
+  // ScrollTypes which we do not filter even if there is an active
+  // kUser smooth scroll.
+  std::set<mojom::blink::ScrollType> exempted_types = {
+      ScrollType::kUser,
+      ScrollType::kCompositor,
+      ScrollType::kClamping,
+      ScrollType::kAnchoring,
+  };
+
+  for (const auto& incoming_type : scroll_types) {
+    const bool should_filter = !exempted_types.contains(incoming_type);
+    EXPECT_EQ(area->ShouldFilterIncomingScroll(incoming_type), should_filter);
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/manifest/manifest_parser.cc b/third_party/blink/renderer/modules/manifest/manifest_parser.cc
index 7728255..d679e29 100644
--- a/third_party/blink/renderer/modules/manifest/manifest_parser.cc
+++ b/third_party/blink/renderer/modules/manifest/manifest_parser.cc
@@ -297,6 +297,7 @@
   }
 
   manifest_->manifest_url = manifest_url_;
+  manifest_->dir = ParseDir(root_object.get());
   manifest_->name = ParseName(root_object.get());
   manifest_->short_name = ParseShortName(root_object.get());
   manifest_->description = ParseDescription(root_object.get());
@@ -595,6 +596,25 @@
   return invalid_value;
 }
 
+mojom::blink::Manifest::TextDirection ManifestParser::ParseDir(
+    const JSONObject* object) {
+  using TextDirection = mojom::blink::Manifest::TextDirection;
+
+  std::optional<String> dir = ParseString(object, "dir", Trim(true));
+  if (!dir.has_value()) {
+    return TextDirection::kAuto;
+  }
+
+  std::optional<TextDirection> textDirection =
+      TextDirectionFromString(dir->Utf8());
+  if (!textDirection.has_value()) {
+    AddErrorInfo("unknown 'dir' value ignored.");
+    return TextDirection::kAuto;
+  }
+
+  return *textDirection;
+}
+
 String ManifestParser::ParseName(const JSONObject* object) {
   std::optional<String> name = ParseString(object, "name", Trim(true));
   if (name.has_value()) {
diff --git a/third_party/blink/renderer/modules/manifest/manifest_parser.h b/third_party/blink/renderer/modules/manifest/manifest_parser.h
index 5c50bffe..32baaab 100644
--- a/third_party/blink/renderer/modules/manifest/manifest_parser.h
+++ b/third_party/blink/renderer/modules/manifest/manifest_parser.h
@@ -184,6 +184,11 @@
                            Enum (*parse_enum)(const std::string&),
                            Enum invalid_value);
 
+  // Parses the 'dir' field of the manifest, as defined in:
+  // https://w3c.github.io/manifest/#dfn-process-the-dir-member
+  // Returns the parsed TextDirection.
+  mojom::blink::Manifest::TextDirection ParseDir(const JSONObject* object);
+
   // Parses the 'name' field of the manifest, as defined in:
   // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-name-member
   // Returns the parsed string if any, a null string if the parsing failed.
diff --git a/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc b/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc
index 3f833a0..823d487 100644
--- a/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc
+++ b/third_party/blink/renderer/modules/manifest/manifest_parser_unittest.cc
@@ -163,6 +163,7 @@
   // Check that the fields are null or set to their default values.
   EXPECT_FALSE(IsManifestEmpty(manifest));
   EXPECT_TRUE(HasDefaultValuesWithUrls(manifest, DefaultDocumentUrl(), KURL()));
+  EXPECT_EQ(manifest->dir, mojom::blink::Manifest::TextDirection::kAuto);
   EXPECT_TRUE(manifest->name.IsNull());
   EXPECT_TRUE(manifest->short_name.IsNull());
   EXPECT_EQ(manifest->start_url, DefaultDocumentUrl());
@@ -195,8 +196,8 @@
 
 TEST_F(ManifestParserTest, MultipleErrorsReporting) {
   auto& manifest = ParseManifest(
-      R"({ "name": 42, "short_name": 4, "id": 12,
-      "orientation": {}, "display": "foo",
+      R"({ "dir": "foo", "name": 42, "short_name": 4,
+      "id": 12, "orientation": {}, "display": "foo",
       "start_url": null, "icons": {}, "theme_color": 42,
       "background_color": 42, "shortcuts": {} })");
   EXPECT_FALSE(IsManifestEmpty(manifest));
@@ -204,6 +205,7 @@
 
   EXPECT_THAT(errors(),
               testing::UnorderedElementsAre(
+                  "unknown 'dir' value ignored.",
                   "property 'name' ignored, type string expected.",
                   "property 'short_name' ignored, type string expected.",
                   "property 'start_url' ignored, type string expected.",
@@ -216,6 +218,71 @@
                   "property 'shortcuts' ignored, type array expected."));
 }
 
+TEST_F(ManifestParserTest, DirParseRules) {
+  using TextDirection = mojom::blink::Manifest::TextDirection;
+
+  // Smoke test.
+  {
+    auto& manifest = ParseManifest(R"({ "dir": "ltr" })");
+    EXPECT_EQ(manifest->dir, TextDirection::kLTR);
+    EXPECT_FALSE(IsManifestEmpty(manifest));
+    EXPECT_FALSE(HasDefaultValuesWithUrls(manifest));
+    EXPECT_EQ(0u, GetErrorCount());
+  }
+
+  // Trim whitespaces.
+  {
+    auto& manifest = ParseManifest(R"({ "dir": "  rtl  " })");
+    EXPECT_EQ(manifest->dir, TextDirection::kRTL);
+    EXPECT_EQ(0u, GetErrorCount());
+  }
+
+  // Don't parse if dir isn't a string.
+  {
+    auto& manifest = ParseManifest(R"({ "dir": {} })");
+    EXPECT_EQ(manifest->dir, TextDirection::kAuto);
+    ASSERT_EQ(1u, GetErrorCount());
+    EXPECT_EQ("property 'dir' ignored, type string expected.", errors()[0]);
+  }
+
+  // Don't parse if dir isn't a string.
+  {
+    auto& manifest = ParseManifest(R"({ "dir": 42 })");
+    EXPECT_EQ(manifest->dir, TextDirection::kAuto);
+    ASSERT_EQ(1u, GetErrorCount());
+    EXPECT_EQ("property 'dir' ignored, type string expected.", errors()[0]);
+  }
+
+  // Accept 'auto'.
+  {
+    auto& manifest = ParseManifest(R"({ "dir": "auto" })");
+    EXPECT_EQ(manifest->dir, TextDirection::kAuto);
+    EXPECT_EQ(0u, GetErrorCount());
+  }
+
+  // Accept 'ltr'.
+  {
+    auto& manifest = ParseManifest(R"({ "dir": "ltr" })");
+    EXPECT_EQ(manifest->dir, TextDirection::kLTR);
+    EXPECT_EQ(0u, GetErrorCount());
+  }
+
+  // Accept 'rtl'.
+  {
+    auto& manifest = ParseManifest(R"({ "dir": "rtl" })");
+    EXPECT_EQ(manifest->dir, TextDirection::kRTL);
+    EXPECT_EQ(0u, GetErrorCount());
+  }
+
+  // Parse fails if string isn't known.
+  {
+    auto& manifest = ParseManifest(R"({ "dir": "foo" })");
+    EXPECT_EQ(manifest->dir, TextDirection::kAuto);
+    EXPECT_EQ(1u, GetErrorCount());
+    EXPECT_EQ("unknown 'dir' value ignored.", errors()[0]);
+  }
+}
+
 TEST_F(ManifestParserTest, NameParseRules) {
   // Smoke test.
   {
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.cc b/third_party/blink/renderer/modules/webaudio/audio_context.cc
index af1c1f3..23f0856f 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.cc
@@ -1152,6 +1152,7 @@
 void AudioContext::OnDevicesChanged(mojom::blink::MediaDeviceType device_type,
                                     const Vector<WebMediaDeviceInfo>& devices) {
   DCHECK(IsMainThread());
+  SendLogMessage(String::Format("%s", __func__));
 
   if (device_type == mojom::blink::MediaDeviceType::kMediaAudioOutput) {
     output_device_ids_.clear();
@@ -1259,6 +1260,7 @@
 
 void AudioContext::HandleRenderError() {
   DCHECK(IsMainThread());
+  SendLogMessage(String::Format("%s", __func__));
 
   LocalDOMWindow* window = To<LocalDOMWindow>(GetExecutionContext());
   if (window && window->GetFrame()) {
diff --git a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.cc b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.cc
index a829d086..a3214ab 100644
--- a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_handler.cc
@@ -7,6 +7,7 @@
 #include "base/feature_list.h"
 #include "media/base/output_device_info.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
 #include "third_party/blink/public/platform/web_audio_latency_hint.h"
 #include "third_party/blink/public/platform/web_audio_sink_descriptor.h"
 #include "third_party/blink/public/web/web_local_frame.h"
@@ -404,6 +405,10 @@
             WebAudioSinkDescriptor::AudioSinkType::kAudible) {
       if (platform_destination_->MaybeCreateSinkAndGetStatus() ==
           media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK) {
+        WebRtcLogMessage(
+            String::Format("[WA]RADH::%s => (sink_descriptor_=%s)", __func__,
+                           sink_descriptor_.SinkId().Utf8().c_str())
+                .Utf8());
         if (auto* execution_context = Context()->GetExecutionContext()) {
           PeerConnectionDependencyFactory::From(*execution_context)
               .GetWebRtcAudioDevice()
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc b/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
index 87a2663..6bf89e0 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
@@ -40,6 +40,42 @@
 
 namespace blink {
 
+bool VerifyDescription(const AudioDecoderConfig& config,
+                       String* js_error_message) {
+  // https://www.w3.org/TR/webcodecs-flac-codec-registration
+  // https://www.w3.org/TR/webcodecs-vorbis-codec-registration
+  bool description_required = false;
+  if (config.codec() == "flac" || config.codec() == "vorbis") {
+    description_required = true;
+  }
+
+  if (description_required && !config.hasDescription()) {
+    *js_error_message = "Invalid config; description is required.";
+    return false;
+  }
+
+  // For Opus with more than 2 channels, we need a description. While we can
+  // guess a channel mapping for up to 8 channels, we don't know whether the
+  // encoded Opus streams will be mono or stereo streams.
+  if (config.codec() == "opus" && config.numberOfChannels() > 2 &&
+      !config.hasDescription()) {
+    *js_error_message =
+        "Invalid config; description is required for multi-channel Opus.";
+    return false;
+  }
+
+  if (config.hasDescription()) {
+    auto desc_wrapper = AsSpan<const uint8_t>(config.description());
+
+    if (!desc_wrapper.data()) {
+      *js_error_message = "Invalid config; description is detached.";
+      return false;
+    }
+  }
+
+  return true;
+}
+
 AudioDecoderConfig* CopyConfig(const AudioDecoderConfig& config) {
   AudioDecoderConfig* copy = AudioDecoderConfig::Create();
   copy->setCodec(config.codec());
@@ -165,26 +201,11 @@
     return audio_type;
   }
 
-  // https://www.w3.org/TR/webcodecs-flac-codec-registration
-  // https://www.w3.org/TR/webcodecs-vorbis-codec-registration
-  bool description_required = false;
-  if (config.codec() == "flac" || config.codec() == "vorbis")
-    description_required = true;
-
-  if (description_required && !config.hasDescription()) {
-    *js_error_message = "Invalid config; description is required.";
+  if (!VerifyDescription(config, js_error_message)) {
+    CHECK(!js_error_message->empty());
     return std::nullopt;
   }
 
-  if (config.hasDescription()) {
-    auto desc_wrapper = AsSpan<const uint8_t>(config.description());
-
-    if (!desc_wrapper.data()) {
-      *js_error_message = "Invalid config; description is detached.";
-      return std::nullopt;
-    }
-  }
-
   media::AudioCodec codec = media::AudioCodec::kUnknown;
   bool is_codec_ambiguous = true;
   const bool parse_succeeded = ParseAudioCodecString(
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py
index c30267d..ced3afac 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py
@@ -288,7 +288,7 @@
             TestharnessLine(LineType.FOOTER),
         ])
 
-    def summarize(self) -> Optional[str]:
+    def summarize(self, product: str) -> Optional[str]:
         """Generate a summary of this test result as sanitized HTML.
 
         See [1] for ResultDB-specific markup features.
@@ -300,14 +300,14 @@
         # TODO(crbug.com/1521922): Unify result sink reporting with
         # `blinkpy.web_tests.*`.
         summary = textwrap.dedent(f"""\
-            <p><strong>This WPT was run against <code>chrome</code> using
+            <p><strong>This WPT was run against <code>{product}</code> using
             <code>chromedriver</code>. See <a href="{_WPT_DOC_URL}">these
             instructions</a> about running these tests locally and triaging
             failures.</strong></p>""").replace('\n', ' ')
         url = wpt_fyi_url(self.name)
         if url:
             summary += f'<p><a href="{url}">Latest wpt.fyi results</a></p>'
-        for name in ['command', 'stderr', 'crash_log']:
+        for name in ['stderr', 'crash_log']:
             if name in self.artifacts:
                 summary += f'<h3>{name}</h3>'
                 summary += f'<p><text-artifact artifact-id="{name}"/></p>'
@@ -690,13 +690,14 @@
         result.image_diff_stats = image_diff_stats
         if result.unexpected:
             self._handle_unexpected_result(result)
+        product = self.port.get_option('product', '(unknown)')
         self.sink.report_individual_test_result(
             test_name_prefix=self.test_name_prefix,
             result=result,
             artifact_output_dir=self.fs.dirname(self.artifacts_dir),
             expectations=None,
             test_file_location=result.file_path,
-            html_summary=result.summarize())
+            html_summary=result.summarize(product))
         _log.debug(
             'Reported result for %s, iteration %d (actual: %s, '
             'expected: %s, artifacts: %s)', result.name, self._iteration,
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor_unittest.py b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor_unittest.py
index 5f32ee15..14db78a 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor_unittest.py
@@ -32,6 +32,7 @@
         self.path_finder = PathFinder(self.fs)
         port = self.host.port_factory.get('test-linux-trusty')
         port.set_option_default('manifest_update', False)
+        port.set_option_default('product', 'chrome')
         port.set_option_default('test_types', typing.get_args(TestType))
 
         # Create a testing manifest containing any test files that we
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index db6fc916..94b885a4f 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2625,6 +2625,10 @@
 crbug.com/364677549 external/wpt/css/css-properties-values-api/register-property-syntax-parsing.html [ Crash Pass Timeout ]
 
 # ====== New tests from wpt-importer added here ======
+[ Mac15 ] external/wpt/html/browsers/history/the-location-interface/location_hash_set_empty_string.html [ Failure Timeout ]
+[ Win11-arm64 ] external/wpt/html/browsers/history/the-location-interface/location_hash_set_empty_string.html [ Pass Failure Timeout ]
+[ Mac15 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Timeout ]
+[ Linux ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Timeout Crash ]
 crbug.com/364935894 [ Mac14 ] external/wpt/webrtc-encoded-transform/RTCRtpScriptTransform-encoded-transform.https.html [ Timeout ]
 crbug.com/364935894 [ Mac12 ] external/wpt/webrtc-encoded-transform/RTCRtpScriptTransform-encoded-transform.https.html [ Timeout ]
 [ Linux ] wpt_internal/html/semantics/forms/the-select-element/stylable-select/select-only-picker-opt-in.tentative.html [ Failure ]
@@ -5525,7 +5529,16 @@
 # crbug.com/364924718). I anticipate that once the crashes are fixed, the behavior will still be
 # wrong, particularly in the case that `appearance` is changed on the picker once it's open. If
 # so, please file a bug for that case with details.
-crbug.com/364924715 external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Mac11 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Mac11-arm64 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Mac12 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Mac12-arm64 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Mac13 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Mac13-arm64 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Mac14 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Mac14-arm64 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Mac15-arm64 ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
+crbug.com/364924715 [ Win ] external/wpt/html/semantics/forms/the-select-element/stylable-select/switch-picker-appearance.tentative.html [ Crash ]
 
 # Sheriff 2022-04-21
 crbug.com/1318318 external/wpt/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.html [ Failure Pass Timeout ]
@@ -7997,6 +8010,10 @@
 # TODO(crbug.com/362797911): Re-enable this test
 crbug.com/362797911 [ Linux ] fast/text-autosizing/textarea-fontsize-change.html [ Failure Pass ]
 
+# Temporarily ignore failures until https://crrev.com/c/5828823 rolls in.
+crbug.com/364508698 external/wpt/wasm/jsapi/js-string/constants.tentative.any.html [ Failure Pass ]
+crbug.com/364508698 external/wpt/wasm/jsapi/js-string/constants.tentative.any.worker.html [ Failure Pass ]
+
 # These tests only pass because wasm type reflection is implied by the
 # experimental JSPI flag. Temporarily disable them as we remove the obsolete
 # flag implication on the V8 side, and re-enable them with up-to-date
@@ -8021,3 +8038,6 @@
 crbug.com/365432212 virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https.js [ Failure Pass ]
 crbug.com/365444428 [ Linux ] fast/events/middleClickAutoscroll-event-fired.html [ Failure Pass ]
 crbug.com/365487889 [ Mac13 ] shadow-dom/focus-navigation/focus-scroller-activeElement-on-event.html [ Failure Pass ]
+
+# Gardener 2024-09-10
+crbug.com/364306254 [ Linux ] fast/events/middleClickAutoscroll-drag.html [ Failure Pass ]
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 2a55ccf..4389466 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
@@ -65140,6 +65140,32 @@
         {}
        ]
       ],
+      "table-footer-group-001.xht": [
+       "e32e2dac22df85b038237fbd59a9fb8d85708ff6",
+       [
+        null,
+        [
+         [
+          "/css/CSS2/tables/table-row-group-001-ref.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "table-header-group-001.xht": [
+       "0d8f4798052d5ff68fc2fd05e7d5ec29d107b3ec",
+       [
+        null,
+        [
+         [
+          "/css/CSS2/tables/table-row-group-001-ref.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "table-height-algorithm-008a.xht": [
        "eaaf04f759be858bbe0d8b103930748bfd4bf1e7",
        [
@@ -65205,6 +65231,19 @@
         {}
        ]
       ],
+      "table-row-group-001.xht": [
+       "a0f7966e35e68b0fea10309dcdc83b51e2e15372",
+       [
+        null,
+        [
+         [
+          "/css/CSS2/tables/table-row-group-001-ref.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "table-vertical-align-baseline-001.xht": [
        "1862eb41f7f14cb6f77320f6e1e3fa7986c9ff00",
        [
@@ -125592,6 +125631,19 @@
        ]
       ]
      },
+     "font-face-sign-function.html": [
+      "28c958c7ee704c1fa35ce838b29d9b766270862c",
+      [
+       null,
+       [
+        [
+         "/css/css-fonts/font-face-weight-auto-static-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "font-face-stretch-auto-static.html": [
       "ce3c94fa2feb25483d44883cb5a883d74fea641b",
       [
@@ -147323,12 +147375,12 @@
        ]
       ],
       "text-box-trim-float-clear-br-003.html": [
-       "e04d18ec1095449337837461f315868835542da4",
+       "cfb0c14fb65031a631e718282d8c12c230f39a1c",
        [
         null,
         [
          [
-          "/css/css-inline/text-box-trim/text-box-trim-float-clear-br-002-ref.html",
+          "/css/css-inline/text-box-trim/text-box-trim-float-clear-br-003-ref.html",
           "=="
          ]
         ],
@@ -312087,6 +312139,10 @@
        "8ea849424688a0f3a3b9a345b32e53b6ad1d76c5",
        []
       ],
+      "table-row-group-001-ref.xht": [
+       "c58134ae3594a80e3512b1aeac8957ec0d3a1ef9",
+       []
+      ],
       "table-vertical-align-baseline-001-ref.xht": [
        "9d4b753a6dd0c1cba08d83d80180459e4f2ad593",
        []
@@ -330424,19 +330480,15 @@
        []
       ],
       "at-font-face-descriptors-expected.txt": [
-       "e8f7512b9b234fbcbbe0cd4363cdfb2ca94cc90f",
+       "05f9a11a70d24e34ab797ec7182d11c1040f44c9",
        []
       ],
       "font-descriptor-range-reversed-ref.html": [
        "fc2b7aca29fe7d3be3b9cd4282129ba3565a396b",
        []
       ],
-      "font-parse-numeric-stretch-style-weight-expected.txt": [
-       "ab981255e057a5cc1a32bb7a2492580bed119a34",
-       []
-      ],
       "font-shorthand-expected.txt": [
-       "6c0211a70f203f0c0d0119e95ab186dd6d85dc51",
+       "70323d3c7213611a7f89cfcbfd2c191dc7ace385",
        []
       ],
       "font-slant-1-ref.html": [
@@ -330464,7 +330516,7 @@
        []
       ],
       "font-weight-parsing-expected.txt": [
-       "c81239cd24edb1480e0028faa9b55f31a51aba39",
+       "8039860a284671e080089d4bf0d4106e85913e94",
        []
       ],
       "resources": {
@@ -334018,7 +334070,7 @@
        []
       ],
       "text-box-trim-float-clear-br-003-ref.html": [
-       "8c14770034a782f0f557a4b282d6155fbc47a7ce",
+       "c192242a81d8769c3caba716e89cace3df5ce94c",
        []
       ],
       "text-box-trim-float-start-001-ref.html": [
@@ -337448,10 +337500,6 @@
       "max-lines-valid-expected.txt": [
        "616418e1723574341bc33a131faf44d371c5cf44",
        []
-      ],
-      "webkit-line-clamp-valid-expected.txt": [
-       "81ff66eecc6dfcbae93ed80b9d51c90262c9dd06",
-       []
       ]
      },
      "reference": {
@@ -366245,19 +366293,19 @@
       []
      ],
      "processing.any-expected.txt": [
-      "d48c619b0b6bded9718cf900d0ff27b9b6167741",
+      "e8d67b23125e0ba72c732985c953a3da3291c8e8",
       []
      ],
      "processing.any.serviceworker-expected.txt": [
-      "d48c619b0b6bded9718cf900d0ff27b9b6167741",
+      "e8d67b23125e0ba72c732985c953a3da3291c8e8",
       []
      ],
      "processing.any.sharedworker-expected.txt": [
-      "d48c619b0b6bded9718cf900d0ff27b9b6167741",
+      "e8d67b23125e0ba72c732985c953a3da3291c8e8",
       []
      ],
      "processing.any.worker-expected.txt": [
-      "d48c619b0b6bded9718cf900d0ff27b9b6167741",
+      "e8d67b23125e0ba72c732985c953a3da3291c8e8",
       []
      ],
      "resources": {
@@ -370758,6 +370806,10 @@
         "b43598f2cd8f47bcd23373075773ef245c95c21a",
         []
        ],
+       "location_hash_set_empty_string-expected.txt": [
+        "4150dc964681a70da58fdc354359c652e3c66f6e",
+        []
+       ],
        "location_reload-iframe.html": [
         "f08cf5de3e8d6220a5ace3027d789f8ac968d9e6",
         []
@@ -375805,7 +375857,7 @@
         []
        ],
        "getter-tests.js": [
-        "6e15b6524a059fbda544cfd8d50f31b083631a59",
+        "d3eca406fd867ca47df10ec9852be7203ffc0138",
         []
        ],
        "innertext-setter-tests.js": [
@@ -409421,7 +409473,7 @@
       },
       "network": {
        "__init__.py": [
-        "c06cdaa79fb2a0c4b0b164def892e278191208a2",
+        "41ddbbbff09913e07b9bb75b3fcddde26d55a3f3",
         []
        ],
        "add_intercept": {
@@ -415819,10 +415871,6 @@
      "2921202c704a2f9895c9ae828c3edaacea823bc1",
      []
     ],
-    "data-uri-expected.txt": [
-     "2f20ef3d8e8548734871f049417a85074982c207",
-     []
-    ],
     "folder.txt": [
      "bf1a1fdefa3c7f4b0180a75a951e9574662a8bc8",
      []
@@ -453263,6 +453311,62 @@
     },
     "css-align": {
      "abspos": {
+      "align-self-default-overflow-htb-ltr-htb.html": [
+       "3e5bc482a5d5384419fb32f9c26b0e103915ffff",
+       [
+        null,
+        {}
+       ]
+      ],
+      "align-self-default-overflow-htb-ltr-vrl.html": [
+       "7144bb1513c337f9776ab888af65041bc1e4c692",
+       [
+        null,
+        {}
+       ]
+      ],
+      "align-self-default-overflow-htb-rtl-htb.html": [
+       "c041d8efa9af44fdefc8f4accb39a3fa1ac821c8",
+       [
+        null,
+        {}
+       ]
+      ],
+      "align-self-default-overflow-htb-rtl-vrl.html": [
+       "0174fd0a51b5f6fe0a15b6a5bd604194543c8708",
+       [
+        null,
+        {}
+       ]
+      ],
+      "align-self-default-overflow-vrl-ltr-htb.html": [
+       "a9a641397ff2bf61cb273b6c80b79ae16c09bed4",
+       [
+        null,
+        {}
+       ]
+      ],
+      "align-self-default-overflow-vrl-ltr-vrl.html": [
+       "e3be64a87ab34ec0410398124897008858505125",
+       [
+        null,
+        {}
+       ]
+      ],
+      "align-self-default-overflow-vrl-rtl-htb.html": [
+       "9c1b779265d6aca3b1e174fe0c250810f6a2b54c",
+       [
+        null,
+        {}
+       ]
+      ],
+      "align-self-default-overflow-vrl-rtl-vrl.html": [
+       "eaebcf03acb38157abefd5bf232e5989935e5063",
+       [
+        null,
+        {}
+       ]
+      ],
       "align-self-htb-ltr-htb.html": [
        "786cec7acf76607a92b44a76880c1a2543da25e4",
        [
@@ -453389,6 +453493,62 @@
         {}
        ]
       ],
+      "justify-self-default-overflow-htb-ltr-htb.html": [
+       "753001068ea1a3ee7a6a4ab7640af2296a8e7f94",
+       [
+        null,
+        {}
+       ]
+      ],
+      "justify-self-default-overflow-htb-ltr-vrl.html": [
+       "26bba3ea30786b6bd941f02046a4ea306bae78cb",
+       [
+        null,
+        {}
+       ]
+      ],
+      "justify-self-default-overflow-htb-rtl-htb.html": [
+       "f3b46447e6f9102d7f751b720e4be88155fa0042",
+       [
+        null,
+        {}
+       ]
+      ],
+      "justify-self-default-overflow-htb-rtl-vrl.html": [
+       "adf9b305cd2f747ed1882fc1b6a4b72fd7f011c4",
+       [
+        null,
+        {}
+       ]
+      ],
+      "justify-self-default-overflow-vrl-ltr-htb.html": [
+       "cc53c9759e09828097594a9d4a34d9998dd24a75",
+       [
+        null,
+        {}
+       ]
+      ],
+      "justify-self-default-overflow-vrl-ltr-vrl.html": [
+       "92fa8002dd29e1f02f644b3322c0e75b994ee8c3",
+       [
+        null,
+        {}
+       ]
+      ],
+      "justify-self-default-overflow-vrl-rtl-htb.html": [
+       "6c117f3240dd5b69420e9e5f634c80bfd43bc9fa",
+       [
+        null,
+        {}
+       ]
+      ],
+      "justify-self-default-overflow-vrl-rtl-vrl.html": [
+       "ea1f8cc9ebe7d4e0ec69aad7850d65cffaad29f2",
+       [
+        null,
+        {}
+       ]
+      ],
       "justify-self-htb-ltr-htb.html": [
        "cfef344e041ed1029f4d44ee9bf91745744527d9",
        [
@@ -454825,14 +454985,14 @@
       ]
      ],
      "anchor-center-htb-htb.html": [
-      "20abb2ed096c934bb47447d64d5b717012f8ef47",
+      "50fc09061073a7bcd16d9669b3fd6ad85234e783",
       [
        null,
        {}
       ]
      ],
      "anchor-center-htb-vrl.html": [
-      "099d9cd15b43ae33024fc1fb659563bb5141baa2",
+      "eba72d649133da882a9fa095f0433f426b54bf8e",
       [
        null,
        {}
@@ -454846,14 +455006,14 @@
       ]
      ],
      "anchor-center-vrl-htb.html": [
-      "3e4f485cec3d730ecbadb695fc214269f2e101ea",
+      "8f7af0414edd4eb606e7be3437f81468696522b9",
       [
        null,
        {}
       ]
      ],
      "anchor-center-vrl-vrl.html": [
-      "fe40c731419c42ff286e24d653082220358cf137",
+      "d8e90057dc3372c5ccca659132e708c678678a56",
       [
        null,
        {}
@@ -455538,6 +455698,13 @@
        {}
       ]
      ],
+     "auto-margins-position-area.html": [
+      "83014100b9b000bd418f8179bcdca260e96942e3",
+      [
+       null,
+       {}
+      ]
+     ],
      "base-style-invalidation.html": [
       "e4e12785790391e595b300b670939b9f0547665b",
       [
@@ -459519,7 +459686,7 @@
        ]
       ],
       "color-computed-relative-color.html": [
-       "4997d6585d831f12341269e87f6a673e6376748b",
+       "83806e23a6afb9e3f24a5aae1b000ea431e61d08",
        [
         null,
         {}
@@ -464539,6 +464706,13 @@
        {}
       ]
      ],
+     "font-size-sign-function.html": [
+      "a4c58a45c56452dd196fd2c621f7290d275def9e",
+      [
+       null,
+       {}
+      ]
+     ],
      "font-stretch-interpolation-math-functions.html": [
       "74c42587f5ef9586a443ebbcce72ac5ec97a3523",
       [
@@ -465259,7 +465433,7 @@
      ],
      "variations": {
       "at-font-face-descriptors.html": [
-       "1935422d7c29df467a1c44bb4327e8fd79772508",
+       "bb41c24ad07a4404bb570817a37091ff46eb5295",
        [
         null,
         {}
@@ -469660,6 +469834,13 @@
         {}
        ]
       ],
+      "initial-letter-sign-function.html": [
+       "118ee2d0e1a4216aa436d75b8ad2dc51cc97eb13",
+       [
+        null,
+        {}
+       ]
+      ],
       "initial-letter-valid.html": [
        "b02c564504f4fcbea363c6780b27b0be2353ac68",
        [
@@ -486928,6 +487109,13 @@
        {}
       ]
      ],
+     "attr-security.html": [
+      "fef9d80be449816ab2be0624cdd818a2945da5a2",
+      [
+       null,
+       {}
+      ]
+     ],
      "calc-angle-values.html": [
       "699af7a5cdcb3b4cfa106f53eb416a15427f0a91",
       [
@@ -524406,7 +524594,7 @@
        ]
       ],
       "cors-filtering.sub.any.js": [
-       "a26eaccf2a5c79a52eb5fde233756f3f1888c538",
+       "5f9492487f136f8d83893b599fc6c493ce9387f7",
        [
         "fetch/api/cors/cors-filtering.sub.any.html",
         {
@@ -556700,6 +556888,20 @@
          {}
         ]
        ],
+       "location_hash_set_empty_string.html": [
+        "bfde4e638290c0ef1b4068b399e7428d090544b2",
+        [
+         null,
+         {}
+        ]
+       ],
+       "location_hashchange_infinite_loop.html": [
+        "694c619ecabdb8b8e2f69fc6144e59f5a8279818",
+        [
+         null,
+         {}
+        ]
+       ],
        "location_host.html": [
         "d93bf47e506f131e29f2009e1f4446bc5844aee5",
         [
@@ -599553,6 +599755,13 @@
         {}
        ]
       ],
+      "imperative-invokers.tentative.html": [
+       "f810d11b01a0da298c377e2ad55ec25948139523",
+       [
+        null,
+        {}
+       ]
+      ],
       "label-in-invoker.html": [
        "bf8ab9710dce07085b930b1a48a656a565703f3c",
        [
@@ -654889,7 +655098,7 @@
        ]
       ],
       "property-reflection.html": [
-       "b182087aaedbb8fe5aaca4a107ebf06ed11df30e",
+       "7ebbc0c4c585c5870a6434f535505f868c4de948",
        [
         null,
         {
@@ -695262,15 +695471,15 @@
        }
       ]
      ],
-     "buffer.https.any.js": [
-      "279a82194d1d03d6c10488f2cde7ef76a639c314",
+     "byob_readtensor.https.any.js": [
+      "2f51753f66ae64c3cb1acef9c331773c602e4185",
       [
-       "webnn/conformance_tests/buffer.https.any.html?cpu",
+       "webnn/conformance_tests/byob_readtensor.https.any.html?cpu",
        {
         "script_metadata": [
          [
           "title",
-          "test WebNN API buffer operations"
+          "test WebNN API tensor operations"
          ],
          [
           "global",
@@ -695305,12 +695514,12 @@
        }
       ],
       [
-       "webnn/conformance_tests/buffer.https.any.html?gpu",
+       "webnn/conformance_tests/byob_readtensor.https.any.html?gpu",
        {
         "script_metadata": [
          [
           "title",
-          "test WebNN API buffer operations"
+          "test WebNN API tensor operations"
          ],
          [
           "global",
@@ -695345,12 +695554,12 @@
        }
       ],
       [
-       "webnn/conformance_tests/buffer.https.any.html?npu",
+       "webnn/conformance_tests/byob_readtensor.https.any.html?npu",
        {
         "script_metadata": [
          [
           "title",
-          "test WebNN API buffer operations"
+          "test WebNN API tensor operations"
          ],
          [
           "global",
@@ -695385,12 +695594,12 @@
        }
       ],
       [
-       "webnn/conformance_tests/buffer.https.any.worker.html?cpu",
+       "webnn/conformance_tests/byob_readtensor.https.any.worker.html?cpu",
        {
         "script_metadata": [
          [
           "title",
-          "test WebNN API buffer operations"
+          "test WebNN API tensor operations"
          ],
          [
           "global",
@@ -695425,12 +695634,12 @@
        }
       ],
       [
-       "webnn/conformance_tests/buffer.https.any.worker.html?gpu",
+       "webnn/conformance_tests/byob_readtensor.https.any.worker.html?gpu",
        {
         "script_metadata": [
          [
           "title",
-          "test WebNN API buffer operations"
+          "test WebNN API tensor operations"
          ],
          [
           "global",
@@ -695465,255 +695674,12 @@
        }
       ],
       [
-       "webnn/conformance_tests/buffer.https.any.worker.html?npu",
+       "webnn/conformance_tests/byob_readtensor.https.any.worker.html?npu",
        {
         "script_metadata": [
          [
           "title",
-          "test WebNN API buffer operations"
-         ],
-         [
-          "global",
-          "window,dedicatedworker"
-         ],
-         [
-          "variant",
-          "?cpu"
-         ],
-         [
-          "variant",
-          "?gpu"
-         ],
-         [
-          "variant",
-          "?npu"
-         ],
-         [
-          "script",
-          "../resources/utils_validation.js"
-         ],
-         [
-          "script",
-          "../resources/utils.js"
-         ],
-         [
-          "timeout",
-          "long"
-         ]
-        ],
-        "timeout": "long"
-       }
-      ]
-     ],
-     "byob_readbuffer.https.any.js": [
-      "d0d721a8c0ee684ee5d754dc3bf162368233e34e",
-      [
-       "webnn/conformance_tests/byob_readbuffer.https.any.html?cpu",
-       {
-        "script_metadata": [
-         [
-          "title",
-          "test WebNN API buffer operations"
-         ],
-         [
-          "global",
-          "window,dedicatedworker"
-         ],
-         [
-          "variant",
-          "?cpu"
-         ],
-         [
-          "variant",
-          "?gpu"
-         ],
-         [
-          "variant",
-          "?npu"
-         ],
-         [
-          "script",
-          "../resources/utils_validation.js"
-         ],
-         [
-          "script",
-          "../resources/utils.js"
-         ],
-         [
-          "timeout",
-          "long"
-         ]
-        ],
-        "timeout": "long"
-       }
-      ],
-      [
-       "webnn/conformance_tests/byob_readbuffer.https.any.html?gpu",
-       {
-        "script_metadata": [
-         [
-          "title",
-          "test WebNN API buffer operations"
-         ],
-         [
-          "global",
-          "window,dedicatedworker"
-         ],
-         [
-          "variant",
-          "?cpu"
-         ],
-         [
-          "variant",
-          "?gpu"
-         ],
-         [
-          "variant",
-          "?npu"
-         ],
-         [
-          "script",
-          "../resources/utils_validation.js"
-         ],
-         [
-          "script",
-          "../resources/utils.js"
-         ],
-         [
-          "timeout",
-          "long"
-         ]
-        ],
-        "timeout": "long"
-       }
-      ],
-      [
-       "webnn/conformance_tests/byob_readbuffer.https.any.html?npu",
-       {
-        "script_metadata": [
-         [
-          "title",
-          "test WebNN API buffer operations"
-         ],
-         [
-          "global",
-          "window,dedicatedworker"
-         ],
-         [
-          "variant",
-          "?cpu"
-         ],
-         [
-          "variant",
-          "?gpu"
-         ],
-         [
-          "variant",
-          "?npu"
-         ],
-         [
-          "script",
-          "../resources/utils_validation.js"
-         ],
-         [
-          "script",
-          "../resources/utils.js"
-         ],
-         [
-          "timeout",
-          "long"
-         ]
-        ],
-        "timeout": "long"
-       }
-      ],
-      [
-       "webnn/conformance_tests/byob_readbuffer.https.any.worker.html?cpu",
-       {
-        "script_metadata": [
-         [
-          "title",
-          "test WebNN API buffer operations"
-         ],
-         [
-          "global",
-          "window,dedicatedworker"
-         ],
-         [
-          "variant",
-          "?cpu"
-         ],
-         [
-          "variant",
-          "?gpu"
-         ],
-         [
-          "variant",
-          "?npu"
-         ],
-         [
-          "script",
-          "../resources/utils_validation.js"
-         ],
-         [
-          "script",
-          "../resources/utils.js"
-         ],
-         [
-          "timeout",
-          "long"
-         ]
-        ],
-        "timeout": "long"
-       }
-      ],
-      [
-       "webnn/conformance_tests/byob_readbuffer.https.any.worker.html?gpu",
-       {
-        "script_metadata": [
-         [
-          "title",
-          "test WebNN API buffer operations"
-         ],
-         [
-          "global",
-          "window,dedicatedworker"
-         ],
-         [
-          "variant",
-          "?cpu"
-         ],
-         [
-          "variant",
-          "?gpu"
-         ],
-         [
-          "variant",
-          "?npu"
-         ],
-         [
-          "script",
-          "../resources/utils_validation.js"
-         ],
-         [
-          "script",
-          "../resources/utils.js"
-         ],
-         [
-          "timeout",
-          "long"
-         ]
-        ],
-        "timeout": "long"
-       }
-      ],
-      [
-       "webnn/conformance_tests/byob_readbuffer.https.any.worker.html?npu",
-       {
-        "script_metadata": [
-         [
-          "title",
-          "test WebNN API buffer operations"
+          "test WebNN API tensor operations"
          ],
          [
           "global",
@@ -705136,7 +705102,7 @@
       ]
      ],
      "parallel-dispatch.https.any.js": [
-      "11ff41a5dc12d855369bad78721f70bfb33e9b71",
+      "1264fa82eabd0afc84db4babe4c42c8580fd71a2",
       [
        "webnn/conformance_tests/parallel-dispatch.https.any.html?cpu",
        {
@@ -711705,6 +711671,249 @@
        }
       ]
      ],
+     "tensor.https.any.js": [
+      "468506746619800277a752c5498b632ef91c1bb2",
+      [
+       "webnn/conformance_tests/tensor.https.any.html?cpu",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "test WebNN API tensor operations"
+         ],
+         [
+          "global",
+          "window,dedicatedworker"
+         ],
+         [
+          "variant",
+          "?cpu"
+         ],
+         [
+          "variant",
+          "?gpu"
+         ],
+         [
+          "variant",
+          "?npu"
+         ],
+         [
+          "script",
+          "../resources/utils_validation.js"
+         ],
+         [
+          "script",
+          "../resources/utils.js"
+         ],
+         [
+          "timeout",
+          "long"
+         ]
+        ],
+        "timeout": "long"
+       }
+      ],
+      [
+       "webnn/conformance_tests/tensor.https.any.html?gpu",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "test WebNN API tensor operations"
+         ],
+         [
+          "global",
+          "window,dedicatedworker"
+         ],
+         [
+          "variant",
+          "?cpu"
+         ],
+         [
+          "variant",
+          "?gpu"
+         ],
+         [
+          "variant",
+          "?npu"
+         ],
+         [
+          "script",
+          "../resources/utils_validation.js"
+         ],
+         [
+          "script",
+          "../resources/utils.js"
+         ],
+         [
+          "timeout",
+          "long"
+         ]
+        ],
+        "timeout": "long"
+       }
+      ],
+      [
+       "webnn/conformance_tests/tensor.https.any.html?npu",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "test WebNN API tensor operations"
+         ],
+         [
+          "global",
+          "window,dedicatedworker"
+         ],
+         [
+          "variant",
+          "?cpu"
+         ],
+         [
+          "variant",
+          "?gpu"
+         ],
+         [
+          "variant",
+          "?npu"
+         ],
+         [
+          "script",
+          "../resources/utils_validation.js"
+         ],
+         [
+          "script",
+          "../resources/utils.js"
+         ],
+         [
+          "timeout",
+          "long"
+         ]
+        ],
+        "timeout": "long"
+       }
+      ],
+      [
+       "webnn/conformance_tests/tensor.https.any.worker.html?cpu",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "test WebNN API tensor operations"
+         ],
+         [
+          "global",
+          "window,dedicatedworker"
+         ],
+         [
+          "variant",
+          "?cpu"
+         ],
+         [
+          "variant",
+          "?gpu"
+         ],
+         [
+          "variant",
+          "?npu"
+         ],
+         [
+          "script",
+          "../resources/utils_validation.js"
+         ],
+         [
+          "script",
+          "../resources/utils.js"
+         ],
+         [
+          "timeout",
+          "long"
+         ]
+        ],
+        "timeout": "long"
+       }
+      ],
+      [
+       "webnn/conformance_tests/tensor.https.any.worker.html?gpu",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "test WebNN API tensor operations"
+         ],
+         [
+          "global",
+          "window,dedicatedworker"
+         ],
+         [
+          "variant",
+          "?cpu"
+         ],
+         [
+          "variant",
+          "?gpu"
+         ],
+         [
+          "variant",
+          "?npu"
+         ],
+         [
+          "script",
+          "../resources/utils_validation.js"
+         ],
+         [
+          "script",
+          "../resources/utils.js"
+         ],
+         [
+          "timeout",
+          "long"
+         ]
+        ],
+        "timeout": "long"
+       }
+      ],
+      [
+       "webnn/conformance_tests/tensor.https.any.worker.html?npu",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "test WebNN API tensor operations"
+         ],
+         [
+          "global",
+          "window,dedicatedworker"
+         ],
+         [
+          "variant",
+          "?cpu"
+         ],
+         [
+          "variant",
+          "?gpu"
+         ],
+         [
+          "variant",
+          "?npu"
+         ],
+         [
+          "script",
+          "../resources/utils_validation.js"
+         ],
+         [
+          "script",
+          "../resources/utils.js"
+         ],
+         [
+          "timeout",
+          "long"
+         ]
+        ],
+        "timeout": "long"
+       }
+      ]
+     ],
      "tile.https.any.js": [
       "427fd2160159cfc2df2c9e60027c2d7b516a1bc8",
       [
@@ -715100,7 +715309,7 @@
       ]
      ],
      "destroyContext.https.any.js": [
-      "d50725ac4b7e2d48ed892b3063bce13f2eba3e1a",
+      "5cba921c30681dc796134ff41a5719736e131081",
       [
        "webnn/validation_tests/destroyContext.https.any.html?cpu",
        {
@@ -715295,7 +715504,7 @@
       ]
      ],
      "destroyGraph.https.any.js": [
-      "7dfadfe3f8e0af7061ad2ffdf1b64564e41427b0",
+      "3724d6f0d34a678788a47558413a3191d580f776",
       [
        "webnn/validation_tests/destroyGraph.https.any.html?cpu",
        {
@@ -723845,10 +724054,12 @@
      ]
     ],
     "RTCPeerConnection-generateCertificate.html": [
-     "4cda97e9b7ee5d5c8ec9534edf57f316172af373",
+     "534df68083924ab630c3c5560f877be0a40a960a",
      [
       null,
-      {}
+      {
+       "timeout": "long"
+      }
      ]
     ],
     "RTCPeerConnection-getStats.https.html": [
@@ -758158,13 +758369,6 @@
         {}
        ]
       ],
-      "table-footer-group-001.xht": [
-       "fb2338caf47a312e9580e04333ada107762c4a9b",
-       [
-        null,
-        {}
-       ]
-      ],
       "table-footer-group-002.xht": [
        "a041b45789125f283f5f033417ec5d397b73f29c",
        [
@@ -758186,13 +758390,6 @@
         {}
        ]
       ],
-      "table-header-group-001.xht": [
-       "0b3a595cb77ba8f90ab87c2ec34cbdf29ddf0a06",
-       [
-        null,
-        {}
-       ]
-      ],
       "table-header-group-002.xht": [
        "0e03b0a045f8ff3bee300288f0fac4f4c3469021",
        [
@@ -758732,13 +758929,6 @@
         {}
        ]
       ],
-      "table-row-group-001.xht": [
-       "d5a85400ae64a21080c424aeae2b7f919b818426",
-       [
-        null,
-        {}
-       ]
-      ],
       "table-valign-001.xht": [
        "021a3e76e04eee628237b1a8473ab0d7b5e9c282",
        [
@@ -763537,7 +763727,14 @@
        },
        "navigation_started": {
         "navigation_started.py": [
-         "bc711814d3aa11b52db1d2f98774650802a17024",
+         "24fcb48d29a1865b69f7cf668e3d4a81ac7efc79",
+         [
+          null,
+          {}
+         ]
+        ],
+        "navigation_started_initial_navigation_tentative.py": [
+         "87b18a8b9090cce6ad79a2cc813ffe2e958096e8",
          [
           null,
           {}
@@ -763814,7 +764011,7 @@
          ]
         ],
         "pointer_mouse.py": [
-         "7077d7bba4678b8825cbe83556f18b4b6db1e0ea",
+         "511bda663d99f2f83f74505c39a51024efd92afc",
          [
           null,
           {}
@@ -763851,21 +764048,21 @@
          ]
         ],
         "pointer_pen.py": [
-         "7b68d3de057db102ccaec01282a202721b16af24",
+         "44ac435a21ec2c26d9a02bb22c08c2702820ec8f",
          [
           null,
           {}
          ]
         ],
         "pointer_touch.py": [
-         "f036de7c744c1d5bb3376640bdffa9ef5ca79daa",
+         "6edc7f832026703512869f9015294e0a2425504d",
          [
           null,
           {}
          ]
         ],
         "wheel.py": [
-         "3129e9b0e63ca15092f6264ce8eecfef471d0abd",
+         "0a1af029b1e92fa1ba94bdb60679edc8b93dd0ba",
          [
           null,
           {}
@@ -764143,7 +764340,7 @@
        },
        "continue_response": {
         "cookies.py": [
-         "e77bf83cde14d4cd304350b2fc72820f5112645e",
+         "d9280067a0f7fb7871ddafc71bc9bb66c26abe83",
          [
           null,
           {}
@@ -766207,7 +766404,7 @@
         ]
        ],
        "pointer_mouse.py": [
-        "f8683ce45156c7d79c49753eca04c55ffc2b4f16",
+        "53ec6d2c13530654ec752f9da913e348a2996d92",
         [
          null,
          {}
@@ -766228,14 +766425,14 @@
         ]
        ],
        "pointer_pen.py": [
-        "bf71a20c4d2ce89ffc40c4338deaefb5a4f41ddd",
+        "7c384ca97d8f66bc22b4f77f7fbfc7b1db10cbe3",
         [
          null,
          {}
         ]
        ],
        "pointer_touch.py": [
-        "b85b2e6ef38195d26e0e580edf34a141efc19eba",
+        "70ffd68e9ea8f3206886ddeb1dcd06d22ba359d8",
         [
          null,
          {}
@@ -766265,7 +766462,7 @@
         ]
        ],
        "wheel.py": [
-        "1c9bf082270429406080baab3373d5ff3f506aed",
+        "0840327ff902ad146ca26637e9c9ac8d481da3a1",
         [
          null,
          {}
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-footer-group-001.xht b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-footer-group-001.xht
index fb2338c..e32e2dac 100644
--- a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-footer-group-001.xht
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-footer-group-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Table-footer-group</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#table-display" />
+        <link rel="match" href="table-row-group-001-ref.xht" />
         <meta name="assert" content="An element with 'display: table-footer-group' is rendered as if it were a table footer group." />
         <style type="text/css">
             .table
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-header-group-001.xht b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-header-group-001.xht
index 0b3a595..0d8f4798 100644
--- a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-header-group-001.xht
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-header-group-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Table-header-group</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#table-display" />
+        <link rel="match" href="table-row-group-001-ref.xht" />
         <meta name="assert" content="An element with 'display: table-header-group' is rendered as if it were a table header group." />
         <style type="text/css">
             .table
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-row-group-001-ref.xht b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-row-group-001-ref.xht
new file mode 100644
index 0000000..c58134a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-row-group-001-ref.xht
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+    <body>
+        <p>Test passes if there is a square below, and the top half of the square is blue.</p>
+        <div style="width: 8em; height: 8em; border: 2px solid black">
+            <div style="height: 4em; background: blue"></div>
+        </div>
+    </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-row-group-001.xht b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-row-group-001.xht
index d5a8540..a0f7966 100644
--- a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-row-group-001.xht
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/table-row-group-001.xht
@@ -4,6 +4,7 @@
         <title>CSS Test: Table-row-group</title>
         <link rel="author" title="Microsoft" href="http://www.microsoft.com/" />
         <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#table-display" />
+        <link rel="match" href="table-row-group-001-ref.xht" />
         <meta name="assert" content="An element with 'display: table-row-group' is rendered as if it were a table row group." />
         <style type="text/css">
             .table
diff --git a/third_party/blink/web_tests/external/wpt/file-system-access/getDirectory.https.any.js b/third_party/blink/web_tests/external/wpt/file-system-access/getDirectory.https.any.js
index 6921ab6..69fd015 100644
--- a/third_party/blink/web_tests/external/wpt/file-system-access/getDirectory.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/file-system-access/getDirectory.https.any.js
@@ -3,28 +3,30 @@
 
 promise_test(async t => {
   const fileName = 'testFile';
+  const directory = await navigator.storage.getDirectory();
+
   t.add_cleanup(async () => {
     try {
-      await parent.removeEntry(fileName);
+      await directory.removeEntry(fileName);
     } catch {
       // Ignore any errors in case the test failed.
     }
   });
 
-  const directory = await navigator.storage.getDirectory();
   return directory.getFileHandle(fileName, {create: true});
 }, 'Call getFileHandle successfully');
 
 promise_test(async t => {
   const directoryName = 'testDirectory';
+  const directory = await navigator.storage.getDirectory();
+
   t.add_cleanup(async () => {
     try {
-      await parent.removeEntry(fileName, {recursive: true});
+      await directory.removeEntry(directoryName, {recursive: true});
     } catch {
       // Ignore any errors in case the test failed.
     }
   });
 
-  const directory = await navigator.storage.getDirectory();
   return directory.getDirectoryHandle(directoryName, {create: true});
 }, 'Call getDirectoryHandle successfully');
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location_hash_set_empty_string-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location_hash_set_empty_string-expected.txt
new file mode 100644
index 0000000..4150dc96
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location_hash_set_empty_string-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] window.location.hash is an empty string
+  assert_true: expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location_hash_set_empty_string.html b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location_hash_set_empty_string.html
new file mode 100644
index 0000000..bfde4e63
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location_hash_set_empty_string.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Set window.location.hash to an empty string</title>
+<link rel="author" href="mailto:cristianb@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-location-hash">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const orig_location = window.location.href;
+
+window.location.hash = '';
+
+test(() => {
+    assert_true(window.location.hash === '');
+    assert_true(window.location.href === orig_location + '#');
+}, 'window.location.hash is an empty string');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location_hashchange_infinite_loop.html b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location_hashchange_infinite_loop.html
new file mode 100644
index 0000000..694c619e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/location_hashchange_infinite_loop.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Set window.location.hash to same value while handling event hashchange</title>
+<link rel="author" href="mailto:cristianb@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-location-hash">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+let count = 0;
+
+window.onhashchange = () => {
+    count += 1;
+    window.location.hash = 'running';
+}
+
+promise_test(async t => {
+    window.location.hash = 'running';
+
+    await t.step_wait(() => count === 1);
+}, 'Setting window.location.hash to same value while handling hashchange event shouldn\'t cause an infinite loop.');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/elements/the-innertext-and-outertext-properties/getter-tests.js b/third_party/blink/web_tests/external/wpt/html/dom/elements/the-innertext-and-outertext-properties/getter-tests.js
index 6e15b65..d3eca40 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/elements/the-innertext-and-outertext-properties/getter-tests.js
+++ b/third_party/blink/web_tests/external/wpt/html/dom/elements/the-innertext-and-outertext-properties/getter-tests.js
@@ -56,8 +56,14 @@
 testText("<div>abc <input> def", "abc  def", "Whitespace around <input> should not be collapsed");
 testText("<div>abc <span style='display:inline-block'></span> def", "abc  def", "Whitespace around inline-block should not be collapsed");
 testText("<div>abc <span style='display:inline-block'> def </span> ghi", "abc def ghi", "Trailing space at end of inline-block should be collapsed");
+testText("<div>abc <span style='display:inline-flex'></span> def", "abc  def", "Whitespace around inline-flex should not be collapsed");
+testText("<div>abc <span style='display:inline-flex'> def </span> ghi", "abc def ghi", "Trailing space at end of inline-flex should be collapsed");
+testText("<div>abc <span style='display:inline-grid'></span> def", "abc  def", "Whitespace around inline-grid should not be collapsed");
+testText("<div>abc <span style='display:inline-grid'> def </span> ghi", "abc def ghi", "Trailing space at end of grid-flex should be collapsed");
 testText("<div><input> <div>abc</div>", "abc", "Whitespace between <input> and block should be collapsed");
 testText("<div><span style='inline-block'></span> <div>abc</div>", "abc", "Whitespace between inline-block and block should be collapsed");
+testText("<div><span style='inline-flex'></span> <div>abc</div>", "abc", "Whitespace between inline-flex and block should be collapsed");
+testText("<div><span style='inline-grid'></span> <div>abc</div>", "abc", "Whitespace between inline-grid and block should be collapsed");
 testText("<div>abc <img> def", "abc  def", "Whitespace around <img> should not be collapsed");
 testText("<div>abc <img width=1 height=1> def", "abc  def", "Whitespace around <img> should not be collapsed");
 testText("<div><img> abc", " abc", "Leading whitesapce should not be collapsed");
@@ -237,6 +243,8 @@
 testText("<div>abc<div style='margin:2em'>def", "abc\ndef", "No blank lines around <div> with margin");
 testText("<div>123<span style='display:inline-block'>abc</span>def", "123abcdef", "No newlines at display:inline-block boundary");
 testText("<div>123<span style='display:inline-block'> abc </span>def", "123abcdef", "Leading/trailing space removal at display:inline-block boundary");
+testText("<div>123<span style='display:inline-flex'> abc </span>def", "123abcdef", "Leading/trailing space removal at display:inline-flex boundary");
+testText("<div>123<span style='display:inline-grid'> abc </span>def", "123abcdef", "Leading/trailing space removal at display:inline-grid boundary");
 testText("<div>123<p style='margin:0px'>abc</p>def", "123\n\nabc\n\ndef", "Blank lines around <p> even without margin");
 testText("<div>123<h1>abc</h1>def", "123\nabc\ndef", "No blank lines around <h1>");
 testText("<div>123<h2>abc</h2>def", "123\nabc\ndef", "No blank lines around <h2>");
diff --git a/third_party/blink/web_tests/external/wpt/shadow-dom/reference-target/tentative/property-reflection.html b/third_party/blink/web_tests/external/wpt/shadow-dom/reference-target/tentative/property-reflection.html
index b182087..7ebbc0c 100644
--- a/third_party/blink/web_tests/external/wpt/shadow-dom/reference-target/tentative/property-reflection.html
+++ b/third_party/blink/web_tests/external/wpt/shadow-dom/reference-target/tentative/property-reflection.html
@@ -75,7 +75,7 @@
         test_property_reflection(referencing_element_type, referenced_element_type, "aria-details", "ariaDetailsElements", Behavior.ReflectsHostInArray);
         test_property_reflection(referencing_element_type, referenced_element_type, "aria-errormessage", "ariaErrorMessageElements", Behavior.ReflectsHostInArray);
         test_property_reflection(referencing_element_type, referenced_element_type, "aria-flowto", "ariaFlowToElements", Behavior.ReflectsHostInArray);
-        test_property_reflection(referencing_element_type, referenced_element_type, "aria-labeledby", "ariaLabelledByElements", Behavior.ReflectsHostInArray);
+        test_property_reflection(referencing_element_type, referenced_element_type, "aria-labelledby", "ariaLabelledByElements", Behavior.ReflectsHostInArray);
         test_property_reflection(referencing_element_type, referenced_element_type, "aria-owns", "ariaOwnsElements", Behavior.ReflectsHostInArray);
 
         test_property_reflection(referencing_element_type, referenced_element_type, "anchor", "anchorElement", Behavior.ReflectsHost);
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/audio-decoder.https.any.js b/third_party/blink/web_tests/external/wpt/webcodecs/audio-decoder.https.any.js
index 98ed498..fd828d4 100644
--- a/third_party/blink/web_tests/external/wpt/webcodecs/audio-decoder.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/audio-decoder.https.any.js
@@ -54,6 +54,14 @@
     },
   },
   {
+    comment: 'Opus with >2 channels but no description',
+    config: {
+      codec: 'opus',
+      sampleRate: 48000,
+      numberOfChannels: 6,
+    }
+  },
+  {
     comment: 'Valid configuration except detached description',
     config: {
       codec: 'opus',
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started.py
index bc711814..24fcb48d 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started.py
@@ -213,22 +213,6 @@
     remove_listener()
 
 
-@pytest.mark.parametrize("type_hint", ["tab", "window"])
-async def test_new_context(bidi_session, subscribe_events, wait_for_event, wait_for_future_safe, type_hint):
-    await subscribe_events(events=[NAVIGATION_STARTED_EVENT])
-
-    on_entry = wait_for_event(NAVIGATION_STARTED_EVENT)
-    top_level_context = await bidi_session.browsing_context.create(type_hint="tab")
-    navigation_info = await wait_for_future_safe(on_entry)
-    assert_navigation_info(
-        navigation_info,
-        {
-            "context": top_level_context["context"],
-            "url": "about:blank",
-        },
-    )
-
-
 async def test_same_document_navigation(bidi_session, new_tab, url, subscribe_events):
     await bidi_session.browsing_context.navigate(
         context=new_tab["context"], url=url(PAGE_EMPTY), wait="complete"
@@ -253,31 +237,6 @@
     remove_listener()
 
 
-async def test_window_open(bidi_session, subscribe_events, wait_for_event, wait_for_future_safe, top_context):
-    await subscribe_events(events=[NAVIGATION_STARTED_EVENT])
-
-    on_entry = wait_for_event(NAVIGATION_STARTED_EVENT)
-
-    await bidi_session.script.evaluate(
-        expression="""window.open('about:blank');""",
-        target=ContextTarget(top_context["context"]),
-        await_promise=False,
-    )
-
-    navigation_info = await wait_for_future_safe(on_entry)
-    assert_navigation_info(
-        navigation_info,
-        {
-            "url": "about:blank",
-        },
-    )
-    assert navigation_info["navigation"] is not None
-
-    # Retrieve all contexts to get the context for the new window.
-    contexts = await bidi_session.browsing_context.get_tree()
-    assert navigation_info["context"] == contexts[-1]["context"]
-
-
 async def test_document_write(bidi_session, subscribe_events, top_context):
     await subscribe_events(events=[NAVIGATION_STARTED_EVENT])
 
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started_initial_navigation_tentative.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started_initial_navigation_tentative.py
new file mode 100644
index 0000000..87b18a8b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started_initial_navigation_tentative.py
@@ -0,0 +1,55 @@
+import pytest
+
+from webdriver.bidi.modules.script import ContextTarget
+
+from .. import assert_navigation_info
+
+pytestmark = pytest.mark.asyncio
+
+NAVIGATION_STARTED_EVENT = "browsingContext.navigationStarted"
+
+
+# Tentative: https://github.com/web-platform-tests/wpt/issues/47942
+
+@pytest.mark.parametrize("type_hint", ["tab", "window"])
+async def test_new_context(bidi_session, subscribe_events, wait_for_event,
+      wait_for_future_safe, type_hint):
+    await subscribe_events(events=[NAVIGATION_STARTED_EVENT])
+
+    on_entry = wait_for_event(NAVIGATION_STARTED_EVENT)
+    top_level_context = await bidi_session.browsing_context.create(
+        type_hint="tab")
+    navigation_info = await wait_for_future_safe(on_entry)
+    assert_navigation_info(
+        navigation_info,
+        {
+            "context": top_level_context["context"],
+            "url": "about:blank",
+        },
+    )
+
+
+async def test_window_open(bidi_session, subscribe_events, wait_for_event,
+      wait_for_future_safe, top_context):
+    await subscribe_events(events=[NAVIGATION_STARTED_EVENT])
+
+    on_entry = wait_for_event(NAVIGATION_STARTED_EVENT)
+
+    await bidi_session.script.evaluate(
+        expression="""window.open('about:blank');""",
+        target=ContextTarget(top_context["context"]),
+        await_promise=False,
+    )
+
+    navigation_info = await wait_for_future_safe(on_entry)
+    assert_navigation_info(
+        navigation_info,
+        {
+            "url": "about:blank",
+        },
+    )
+    assert navigation_info["navigation"] is not None
+
+    # Retrieve all contexts to get the context for the new window.
+    contexts = await bidi_session.browsing_context.get_tree()
+    assert navigation_info["context"] == contexts[-1]["context"]
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_mouse.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_mouse.py
index 7077d7bb..511bda66 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_mouse.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_mouse.py
@@ -54,34 +54,26 @@
     assert expected == filtered_events[1:]
 
 
-@pytest.mark.parametrize("origin", ["pointer", "viewport"])
-async def test_params_actions_origin_outside_viewport(bidi_session, top_context, origin):
-    actions = Actions()
-    actions.add_pointer().pointer_move(x=-50, y=-50, origin=origin)
-
-    with pytest.raises(MoveTargetOutOfBoundsException):
-        await bidi_session.input.perform_actions(
-            actions=actions, context=top_context["context"]
+@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
+async def test_params_actions_origin_outside_viewport(
+    bidi_session, top_context, get_actions_origin_page, get_element, origin
+):
+    if origin == "element":
+        url = get_actions_origin_page(
+            """width: 100px; height: 50px; background: green;
+            position: relative; left: -200px; top: -100px;"""
+        )
+        await bidi_session.browsing_context.navigate(
+            context=top_context["context"],
+            url=url,
+            wait="complete",
         )
 
-
-async def test_params_actions_origin_element_outside_viewport(
-    bidi_session, top_context, get_actions_origin_page, get_element
-):
-    url = get_actions_origin_page(
-        """width: 100px; height: 50px; background: green;
-           position: relative; left: -200px; top: -100px;"""
-    )
-    await bidi_session.browsing_context.navigate(
-        context=top_context["context"],
-        url=url,
-        wait="complete",
-    )
-
-    elem = await get_element("#inner")
+        element = await get_element("#inner")
+        origin = get_element_origin(element)
 
     actions = Actions()
-    actions.add_pointer().pointer_move(x=0, y=0, origin=get_element_origin(elem))
+    actions.add_pointer().pointer_move(x=-100, y=-100, origin=origin)
 
     with pytest.raises(MoveTargetOutOfBoundsException):
         await bidi_session.input.perform_actions(
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_pen.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_pen.py
index 7b68d3d..44ac435a 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_pen.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_pen.py
@@ -1,5 +1,6 @@
 import pytest
 
+from webdriver.bidi.error import MoveTargetOutOfBoundsException
 from webdriver.bidi.modules.input import Actions, get_element_origin
 
 from .. import get_events
@@ -13,6 +14,36 @@
 pytestmark = pytest.mark.asyncio
 
 
+@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
+async def test_params_actions_origin_outside_viewport(
+    bidi_session, get_actions_origin_page, top_context, get_element, origin
+):
+    if origin == "element":
+        url = get_actions_origin_page(
+            """width: 100px; height: 50px; background: green;
+            position: relative; left: -200px; top: -100px;"""
+        )
+        await bidi_session.browsing_context.navigate(
+            context=top_context["context"],
+            url=url,
+            wait="complete",
+        )
+
+        element = await get_element("#inner")
+        origin = get_element_origin(element)
+
+    actions = Actions()
+    (
+        actions.add_pointer(pointer_type="pen")
+        .pointer_move(x=-100, y=-100, origin=origin)
+    )
+
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        await bidi_session.input.perform_actions(
+            actions=actions, context=top_context["context"]
+        )
+
+
 @pytest.mark.parametrize("mode", ["open", "closed"])
 @pytest.mark.parametrize("nested", [False, True], ids=["outer", "inner"])
 async def test_pen_pointer_in_shadow_tree(
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_touch.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_touch.py
index f036de7c..6edc7f8 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_touch.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/pointer_touch.py
@@ -1,5 +1,6 @@
 import pytest
 
+from webdriver.bidi.error import MoveTargetOutOfBoundsException
 from webdriver.bidi.modules.input import Actions, get_element_origin
 
 from .. import get_events
@@ -13,6 +14,36 @@
 pytestmark = pytest.mark.asyncio
 
 
+@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
+async def test_params_actions_origin_outside_viewport(
+    bidi_session, get_actions_origin_page, top_context, get_element, origin
+):
+    if origin == "element":
+        url = get_actions_origin_page(
+            """width: 100px; height: 50px; background: green;
+            position: relative; left: -200px; top: -100px;"""
+        )
+        await bidi_session.browsing_context.navigate(
+            context=top_context["context"],
+            url=url,
+            wait="complete",
+        )
+
+        element = await get_element("#inner")
+        origin = get_element_origin(element)
+
+    actions = Actions()
+    (
+        actions.add_pointer(pointer_type="touch")
+        .pointer_move(x=-100, y=-100, origin=origin)
+    )
+
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        await bidi_session.input.perform_actions(
+            actions=actions, context=top_context["context"]
+        )
+
+
 @pytest.mark.parametrize("mode", ["open", "closed"])
 @pytest.mark.parametrize("nested", [False, True], ids=["outer", "inner"])
 async def test_touch_pointer_in_shadow_tree(
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/wheel.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/wheel.py
index 3129e9b0..0a1af02 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/wheel.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/input/perform_actions/wheel.py
@@ -1,6 +1,6 @@
 import pytest
 
-from webdriver.bidi.error import NoSuchFrameException
+from webdriver.bidi.error import MoveTargetOutOfBoundsException, NoSuchFrameException
 from webdriver.bidi.modules.input import Actions, get_element_origin
 from webdriver.bidi.modules.script import ContextTarget
 
@@ -20,6 +20,23 @@
         await bidi_session.input.perform_actions(actions=actions, context="foo")
 
 
+@pytest.mark.parametrize("origin", ["element", "viewport"])
+async def test_params_actions_origin_outside_viewport(
+    bidi_session, setup_wheel_test, top_context, get_element, origin
+):
+    if origin == "element":
+        element = await get_element("#scrollable")
+        origin = get_element_origin(element)
+
+    actions = Actions()
+    actions.add_wheel().scroll(x=-100, y=-100, delta_x=10, delta_y=20, origin=origin)
+
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        await bidi_session.input.perform_actions(
+            actions=actions, context=top_context["context"]
+        )
+
+
 @pytest.mark.parametrize("delta_x, delta_y", [(0, 10), (5, 0), (5, 10)])
 async def test_scroll_not_scrollable(
     bidi_session, setup_wheel_test, top_context, get_element, delta_x, delta_y
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/__init__.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/__init__.py
index c06cdaa..41ddbbbf 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/__init__.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/__init__.py
@@ -8,6 +8,7 @@
     any_string,
     any_string_or_null,
     assert_cookies,
+    int_interval,
     recursive_compare,
 )
 
@@ -357,6 +358,13 @@
 
 expires_a_day_from_now = datetime.now(timezone.utc) + timedelta(days=1)
 expires_a_day_from_now_timestamp = int(expires_a_day_from_now.timestamp())
+# Bug 1916221, the parsed expiry can have a slightly different value than the
+# computed timestamp as Firefox tries to accommodate for the difference between
+# the server clock and the system clock.
+expires_interval = int_interval(
+    expires_a_day_from_now_timestamp - 1,
+    expires_a_day_from_now_timestamp + 1,
+)
 
 # Common parameters for Set-Cookie headers tests used for network interception
 # commands.
@@ -484,7 +492,7 @@
         ),
         None,
         {
-            "expiry": expires_a_day_from_now_timestamp,
+            "expiry": expires_interval,
             "httpOnly": False,
             "name": "foo",
             "path": "/",
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/continue_response/cookies.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/continue_response/cookies.py
index e77bf83c..d928006 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/continue_response/cookies.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/continue_response/cookies.py
@@ -98,7 +98,7 @@
     SET_COOKIE_TEST_PARAMETERS,
     ids=SET_COOKIE_TEST_IDS,
 )
-async def test_cookie_attributes_before_request_sent(
+async def test_cookie_attributes_response_started(
     setup_blocked_request,
     subscribe_events,
     bidi_session,
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_mouse.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_mouse.py
index f8683ce..53ec6d2 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_mouse.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_mouse.py
@@ -1,6 +1,11 @@
 import pytest
 
-from webdriver.error import InvalidArgumentException, NoSuchWindowException, StaleElementReferenceException
+from webdriver.error import (
+    InvalidArgumentException,
+    MoveTargetOutOfBoundsException,
+    NoSuchWindowException,
+    StaleElementReferenceException,
+)
 
 from tests.classic.perform_actions.support.mouse import (
     get_inview_center,
@@ -37,6 +42,15 @@
         mouse_chain.click(element=element).perform()
 
 
+@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
+def test_params_actions_origin_outside_viewport(session, test_actions_page, mouse_chain, origin):
+    if origin == "element":
+        origin = session.find.css("#outer", all=False)
+
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        mouse_chain.pointer_move(-100, -100, origin=origin).perform()
+
+
 def test_click_at_coordinates(session, test_actions_page, mouse_chain):
     div_point = {
         "x": 82,
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_pen.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_pen.py
index bf71a20..7c384ca9 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_pen.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_pen.py
@@ -1,6 +1,10 @@
 import pytest
 
-from webdriver.error import NoSuchWindowException, StaleElementReferenceException
+from webdriver.error import (
+    MoveTargetOutOfBoundsException,
+    NoSuchWindowException,
+    StaleElementReferenceException,
+)
 
 from tests.classic.perform_actions.support.mouse import (
     get_inview_center,
@@ -34,6 +38,15 @@
         pen_chain.click(element=element).perform()
 
 
+@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
+def test_params_actions_origin_outside_viewport(session, test_actions_page, pen_chain, origin):
+    if origin == "element":
+        origin = session.find.css("#outer", all=False)
+
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        pen_chain.pointer_move(-100, -100, origin=origin).perform()
+
+
 @pytest.mark.parametrize("mode", ["open", "closed"])
 @pytest.mark.parametrize("nested", [False, True], ids=["outer", "inner"])
 def test_pen_pointer_in_shadow_tree(
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_touch.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_touch.py
index b85b2e6e..70ffd68 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_touch.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/pointer_touch.py
@@ -1,6 +1,10 @@
 import pytest
 
-from webdriver.error import NoSuchWindowException, StaleElementReferenceException
+from webdriver.error import (
+    MoveTargetOutOfBoundsException,
+    NoSuchWindowException,
+    StaleElementReferenceException
+)
 from tests.classic.perform_actions.support.mouse import (
     get_inview_center,
     get_viewport_rect,
@@ -9,6 +13,7 @@
 
 from . import assert_pointer_events, record_pointer_events
 
+
 def test_null_response_value(session, touch_chain):
     value = touch_chain.click().perform()
     assert value is None
@@ -32,6 +37,15 @@
         touch_chain.click(element=element).perform()
 
 
+@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
+def test_params_actions_origin_outside_viewport(session, test_actions_page, touch_chain, origin):
+    if origin == "element":
+        origin = session.find.css("#outer", all=False)
+
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        touch_chain.pointer_move(-100, -100, origin=origin).perform()
+
+
 @pytest.mark.parametrize("mode", ["open", "closed"])
 @pytest.mark.parametrize("nested", [False, True], ids=["outer", "inner"])
 def test_touch_pointer_in_shadow_tree(
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/wheel.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/wheel.py
index 1c9bf08..0840327 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/wheel.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/perform_actions/wheel.py
@@ -1,6 +1,6 @@
 import pytest
 
-from webdriver.error import NoSuchWindowException
+from webdriver.error import MoveTargetOutOfBoundsException, NoSuchWindowException
 
 import time
 from tests.classic.perform_actions.support.refine import get_events
@@ -23,6 +23,17 @@
         wheel_chain.scroll(0, 0, 0, 10).perform()
 
 
+@pytest.mark.parametrize("origin", ["element", "viewport"])
+def test_params_actions_origin_outside_viewport(
+    session, test_actions_scroll_page, wheel_chain, origin
+):
+    if origin == "element":
+        origin = session.find.css("#scrollable", all=False)
+
+    with pytest.raises(MoveTargetOutOfBoundsException):
+        wheel_chain.scroll(-100, -100, 10, 20, origin="viewport").perform()
+
+
 def test_scroll_not_scrollable(session, test_actions_scroll_page, wheel_chain):
     target = session.find.css("#not-scrollable", all=False)
 
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started_initial_navigation_tentative-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started_initial_navigation_tentative-expected.txt
new file mode 100644
index 0000000..ad6c9ae
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/browsing_context/navigation_started/navigation_started_initial_navigation_tentative-expected.txt
@@ -0,0 +1,8 @@
+This is a wdspec test.
+[FAIL] test_new_context[tab]
+  asyncio.exceptions.TimeoutError
+[FAIL] test_new_context[window]
+  asyncio.exceptions.TimeoutError
+[FAIL] test_window_open
+  AssertionError
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/network/continue_response/cookies-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/network/continue_response/cookies-expected.txt
index f023de80..8fe0485 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/network/continue_response/cookies-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/network/continue_response/cookies-expected.txt
@@ -1,27 +1,27 @@
 This is a wdspec test.
 [FAIL] test_cookie_response_started
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[no domain]
+[FAIL] test_cookie_attributes_response_started[no domain]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[default domain]
+[FAIL] test_cookie_attributes_response_started[default domain]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[alt domain]
+[FAIL] test_cookie_attributes_response_started[alt domain]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[custom path]
+[FAIL] test_cookie_attributes_response_started[custom path]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[http only]
+[FAIL] test_cookie_attributes_response_started[http only]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[secure]
+[FAIL] test_cookie_attributes_response_started[secure]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[expiry]
+[FAIL] test_cookie_attributes_response_started[expiry]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[max age]
+[FAIL] test_cookie_attributes_response_started[max age]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[same site none]
+[FAIL] test_cookie_attributes_response_started[same site none]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[same site lax]
+[FAIL] test_cookie_attributes_response_started[same site lax]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
-[FAIL] test_cookie_attributes_before_request_sent[same site strict]
+[FAIL] test_cookie_attributes_response_started[same site strict]
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
 [FAIL] test_no_cookie_before_request_sent
   webdriver.bidi.error.UnknownErrorException: unknown error (Cannot override only status or headers, both should be provided)
diff --git a/third_party/blink/web_tests/webexposed/feature-policy-features.html b/third_party/blink/web_tests/webexposed/feature-policy-features.html
index dc29290e..c90c348f 100644
--- a/third_party/blink/web_tests/webexposed/feature-policy-features.html
+++ b/third_party/blink/web_tests/webexposed/feature-policy-features.html
@@ -2,6 +2,12 @@
 <pre id="output"></pre>
 <script src="../resources/feature-policy-features-listing.js"></script>
 <script>
+
+// This test verifies that in a normal (non-isolated) context, a set of
+// expected permissions policies appears. There is an isolated version of this
+// test at:
+// //third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/
+
 if (window.testRunner) {
   testRunner.dumpAsText();
 }
diff --git a/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html b/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html
index 2135cd6..0ebc64a 100644
--- a/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html
+++ b/third_party/blink/web_tests/wpt_internal/isolated-permissions-policy/permissions_policy.https.html
@@ -3,6 +3,12 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="feature-policy-features-listing.js"></script>
 <script>
+
+// This test verifies that in an isolated context, additional permissions
+// policies appear as expected. There is a non-isolated version of this test
+// at:
+// //third_party/blink/web_tests/webexposed/feature-policy-features.html
+
 'use strict';
 
 const non_isolated_policies = [
diff --git a/third_party/boringssl/src b/third_party/boringssl/src
index f10c1dc..604868d 160000
--- a/third_party/boringssl/src
+++ b/third_party/boringssl/src
@@ -1 +1 @@
-Subproject commit f10c1dc37174843c504a80e94c252e35b7b1eb61
+Subproject commit 604868d748c06d2c5c03505915e125ec5131eca0
diff --git a/third_party/catapult b/third_party/catapult
index a16df60..c46e0e5 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit a16df6024bef64291769b0bc3cad19bec4d34178
+Subproject commit c46e0e5bd633c5329040345a87aace0b2a0de1b3
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index ef4ec64..c514979 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit ef4ec64a119b1c436a5b4be38da0ce3d679b5ed4
+Subproject commit c514979254d611ff61da404ed4f5d720efa11a5b
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 46f5de6..c36eb43 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 46f5de67e137de4101c1337dd9b95252d14523e9
+Subproject commit c36eb432d9887c0872ba39e582a97dd3b76c0c22
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 15d5840..660f366 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 15d5840612c59d0ff69bf9324966f2037e060c38
+Subproject commit 660f3660a28aaf564aeff5015f013cec4f17f72c
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 7084bf9..b20e8d5 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 7084bf9f43136499038fb62beb7914e434ff4248
+Subproject commit b20e8d5ddd78f6d04a0b1fd4147aaa821f1bb6a2
diff --git a/third_party/glslang/src b/third_party/glslang/src
index 79c4235..12a17b7 160000
--- a/third_party/glslang/src
+++ b/third_party/glslang/src
@@ -1 +1 @@
-Subproject commit 79c4235085c5eb86ed78b034d94e03f7b3b5daef
+Subproject commit 12a17b7ce41436427e358608183100b1103274da
diff --git a/third_party/libc++/src b/third_party/libc++/src
index 9be1056..6ae6f38 160000
--- a/third_party/libc++/src
+++ b/third_party/libc++/src
@@ -1 +1 @@
-Subproject commit 9be1056e883d3fe5cd6666ac69702a6c5e9a39df
+Subproject commit 6ae6f38d10eda881c16d91932348fc6d4ee98332
diff --git a/third_party/perfetto b/third_party/perfetto
index 4eb5915..2e577c6 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 4eb5915c04991f3c8db021120a5638c3c0742604
+Subproject commit 2e577c61f31b9bca076afe1585b1e3b0192deef6
diff --git a/third_party/skia b/third_party/skia
index 69f5d3d..618beab 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 69f5d3d5cd7a5d6c59416c7e56735966bb69d70d
+Subproject commit 618beab475b6ee5b33a4c3040dc330359b960eab
diff --git a/third_party/spirv-headers/src b/third_party/spirv-headers/src
index efb6b40..2a9b6f9 160000
--- a/third_party/spirv-headers/src
+++ b/third_party/spirv-headers/src
@@ -1 +1 @@
-Subproject commit efb6b4099ddb8fa60f62956dee592c4b94ec6a49
+Subproject commit 2a9b6f951c7d6b04b6c21fe1bf3f475b68b84801
diff --git a/third_party/spirv-tools/src b/third_party/spirv-tools/src
index 2a67ced..d160e170 160000
--- a/third_party/spirv-tools/src
+++ b/third_party/spirv-tools/src
@@ -1 +1 @@
-Subproject commit 2a67ced4331d22f2fc1b17f2d54b2330c5ad8765
+Subproject commit d160e170d74ff45cb2a88dfb365bdfd896016f7c
diff --git a/third_party/subresource-filter-ruleset/README.chromium b/third_party/subresource-filter-ruleset/README.chromium
index 53f2db4..99574f7 100644
--- a/third_party/subresource-filter-ruleset/README.chromium
+++ b/third_party/subresource-filter-ruleset/README.chromium
@@ -1,6 +1,6 @@
 Name: EasyList
 URL: https://easylist.to/easylist/easylist.txt
-Version: 202309182330
+Version: 202407312019
 License: Creative Commons Attribution-ShareAlike 3.0 Unported
 License Android Compatible: yes
 License File: LICENSE
diff --git a/third_party/subresource-filter-ruleset/manifest.json b/third_party/subresource-filter-ruleset/manifest.json
index ca8252e..7948e5d 100644
--- a/third_party/subresource-filter-ruleset/manifest.json
+++ b/third_party/subresource-filter-ruleset/manifest.json
@@ -2,5 +2,5 @@
   "manifest_version": 2,
   "name": "Subresource Filtering Rules",
   "ruleset_format": 1,
-  "version": "9.49.1"
+  "version": "9.51.0"
 }
diff --git a/third_party/swiftshader b/third_party/swiftshader
index 8dd40811..3239872 160000
--- a/third_party/swiftshader
+++ b/third_party/swiftshader
@@ -1 +1 @@
-Subproject commit 8dd40811c5715061393f1b36999139ef0813c291
+Subproject commit 3239872f9c5f1f7a1d029733dd1036d1247cfb4f
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index a0dffec..3b92cef 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit a0dffec9be81ca97ea8e9eb14e162ac285271a32
+Subproject commit 3b92cef97febae53dc21de79c51ec3ddf2d4390e
diff --git a/third_party/vulkan-loader/src b/third_party/vulkan-loader/src
index c758bac..1108bba 160000
--- a/third_party/vulkan-loader/src
+++ b/third_party/vulkan-loader/src
@@ -1 +1 @@
-Subproject commit c758bac8bf1580b5018adafd3a2ec709237b0134
+Subproject commit 1108bba6c97174d172d45470a7470a3d6a564647
diff --git a/third_party/vulkan-utility-libraries/src b/third_party/vulkan-utility-libraries/src
index fbb4db9..ea5774a 160000
--- a/third_party/vulkan-utility-libraries/src
+++ b/third_party/vulkan-utility-libraries/src
@@ -1 +1 @@
-Subproject commit fbb4db92c6b2ac09003b2b8e5ceb978f4f2dda71
+Subproject commit ea5774a13e3017b6d5d79af6fba9f0d72ca5c61a
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src
index 6f3d6d7..99de3c1 160000
--- a/third_party/vulkan-validation-layers/src
+++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@
-Subproject commit 6f3d6d7f55bdd92c4c3e117206eea57cbba6b62c
+Subproject commit 99de3c17fbc2db6b6da0347916c9e01a383c2758
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index c71e026..d113bf1 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -45440,6 +45440,12 @@
       label="For iOSTabGridSwipeRightForIncognito feature"/>
   <suffix name="iOSTabGridToolbarItemFeature"
       label="For iOSTabGridToolbarItemFeature feature"/>
+  <suffix name="IPH_DefaultBrowserPromoMagicStack"
+      label="For Default browser promo in Magic Stack."/>
+  <suffix name="IPH_DefaultBrowserPromoMessages"
+      label="For Default browser promo with messages on omnibox paste."/>
+  <suffix name="IPH_DefaultBrowserPromoSettingCard"
+      label="For Default browser promo in Chrome settings."/>
   <suffix name="KeyboardAccessoryAddressFilling"
       label="For KeyboardAccessoryAddressFilling feature."/>
   <suffix name="KeyboardAccessoryBarSwiping"
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 36e33efd..ed0b66e 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -15549,6 +15549,7 @@
   <int value="-2108866112" label="FeedHeaderStickToTop:enabled"/>
   <int value="-2108836417" label="ArcvmSwapoutKeyboardShortcut:enabled"/>
   <int value="-2108564200" label="AutofillUpstream:disabled"/>
+  <int value="-2107751597" label="CertVerificationNetworkTime:disabled"/>
   <int value="-2106960993" label="HarfBuzzPDFSubsetter:disabled"/>
   <int value="-2106763275" label="ArcRtVcpuQuadCore:enabled"/>
   <int value="-2106575511"
@@ -17091,6 +17092,7 @@
   <int value="-1478562612"
       label="TrustedVaultFrequentDegradedRecoverabilityPolling:enabled"/>
   <int value="-1478137998" label="lite-video-default-downlink-bandwidth-kbps"/>
+  <int value="-1477936741" label="CertVerificationNetworkTime:enabled"/>
   <int value="-1477686864" label="OmniboxRichAutocompletion:enabled"/>
   <int value="-1477560322" label="kiosk"/>
   <int value="-1477491889" label="cast-streaming-force-enable-hardware-vp9"/>
diff --git a/tools/metrics/histograms/metadata/ash/enums.xml b/tools/metrics/histograms/metadata/ash/enums.xml
index 6492036..755d251 100644
--- a/tools/metrics/histograms/metadata/ash/enums.xml
+++ b/tools/metrics/histograms/metadata/ash/enums.xml
@@ -1643,27 +1643,6 @@
   <int value="14" label="Entering overview by clicking on wallpaper."/>
 </enum>
 
-<enum name="PaletteModeCancelType">
-  <int value="0" label="Palette laser pointer mode is cancelled."/>
-  <int value="1" label="Palette laser pointer mode is switched out of"/>
-  <int value="2" label="Palette magnify mode is cancelled."/>
-  <int value="3" label="Palette magnify mode is switched out of."/>
-</enum>
-
-<enum name="PaletteTrayOptions">
-  <int value="0" label="Palette being closed or dismissed with no action"/>
-  <int value="1" label="Click on the settings button"/>
-  <int value="2" label="Click on the help button"/>
-  <int value="3" label="Capture region"/>
-  <int value="4" label="Capture screen"/>
-  <int value="5" label="Add new note"/>
-  <int value="6" label="Magnifying glass mode"/>
-  <int value="7" label="Laser pointer mode"/>
-  <int value="8" label="Assistant mode"/>
-  <int value="9" label="Enter capture mode"/>
-  <int value="10" label="Marker mode"/>
-</enum>
-
 <enum name="PanelButton">
   <int value="0" label="Close button"/>
   <int value="1" label="Learn more link"/>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 7fd74a2d..3c3ac77 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -8461,66 +8461,6 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.Shelf.Palette.InLaserPointerMode" units="ms"
-    expires_after="2024-10-06">
-  <owner>dmblack@google.com</owner>
-  <owner>alexandrahill@google.com</owner>
-  <summary>
-    The amount of time spend in Palette Laser pointer mode. Recorded when the
-    Laser pointer mode is exited.
-  </summary>
-</histogram>
-
-<histogram name="Ash.Shelf.Palette.InMagnifyMode" units="ms"
-    expires_after="2024-10-06">
-  <owner>dmblack@google.com</owner>
-  <owner>alexandrahill@google.com</owner>
-  <summary>
-    The amount of time spend in Palette Magnify mode. Recorded when the Magnify
-    mode is exited.
-  </summary>
-</histogram>
-
-<histogram name="Ash.Shelf.Palette.ModeCancellation"
-    enum="PaletteModeCancelType" expires_after="2024-10-06">
-  <owner>dmblack@google.com</owner>
-  <owner>alexandrahill@google.com</owner>
-  <summary>
-    Tracks the number of times a palette mode is explicitly cancelled or
-    switched out of.
-  </summary>
-</histogram>
-
-<histogram name="Ash.Shelf.Palette.Usage" enum="PaletteTrayOptions"
-    expires_after="2025-02-10">
-  <owner>dmblack@google.com</owner>
-  <owner>alexandrahill@google.com</owner>
-  <summary>
-    Recorded every time that the palette option has been selected from the
-    palette that has been opened manually (not via a stylus eject event).
-  </summary>
-</histogram>
-
-<histogram name="Ash.Shelf.Palette.Usage.AutoOpened" enum="PaletteTrayOptions"
-    expires_after="2024-10-06">
-  <owner>dmblack@google.com</owner>
-  <owner>alexandrahill@google.com</owner>
-  <summary>
-    Recorded every time that the palette option has been selected from the
-    palette that has been opened automatically (by a stylus eject event).
-  </summary>
-</histogram>
-
-<histogram name="Ash.Shelf.Palette.Usage.Shortcut" enum="PaletteTrayOptions"
-    expires_after="2024-10-06">
-  <owner>dmblack@google.com</owner>
-  <owner>alexandrahill@google.com</owner>
-  <summary>
-    Recorded every time that the palette option has been selected by means other
-    that the palette menu (e.g. stylus barrel button or a keyboard accelerator).
-  </summary>
-</histogram>
-
 <histogram name="Ash.Shelf.ShowStackedHotseat" enum="Boolean"
     expires_after="2024-09-24">
   <owner>jiamingc@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index 7bb5aa82..8fb5936 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -110,9 +110,9 @@
 </histogram>
 
 <histogram name="Compositing.BufferQueue.TimeUntilBuffersRecreatedMs"
-    units="ms" expires_after="2024-10-20">
-  <owner>khaslett@chromium.org</owner>
+    units="ms" expires_after="2025-07-01">
   <owner>petermcneeley@chromium.org</owner>
+  <owner>kylechar@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
   <summary>
     The time since a BufferQueue's buffers were destroyed until they were
diff --git a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
index 851d0fff..72d1f53 100644
--- a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
+++ b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
@@ -137,6 +137,12 @@
   <variant name="IPH_DataSaverMilestonePromo"
       summary="data saver milestone promo"/>
   <variant name="IPH_DataSaverPreview" summary="data saver preview"/>
+  <variant name="IPH_DefaultBrowserPromoMagicStack"
+      summary="Default browser promo in Magic Stack"/>
+  <variant name="IPH_DefaultBrowserPromoMessages"
+      summary="Default browser promo with messages on omnibox paste"/>
+  <variant name="IPH_DefaultBrowserPromoSettingCard"
+      summary="Default browser promo in Chrome settings"/>
   <variant name="IPH_DefaultSiteView" summary="default site view info on iOS"/>
   <variant name="IPH_DesktopCustomizeChrome"
       summary="customizing new tab page"/>
diff --git a/tools/metrics/histograms/metadata/holding_space/histograms.xml b/tools/metrics/histograms/metadata/holding_space/histograms.xml
index 61d68bd..63851f73 100644
--- a/tools/metrics/histograms/metadata/holding_space/histograms.xml
+++ b/tools/metrics/histograms/metadata/holding_space/histograms.xml
@@ -67,7 +67,7 @@
 </variants>
 
 <histogram name="HoldingSpace.Animation.BubbleResize.Smoothness" units="%"
-    expires_after="2024-10-06">
+    expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -79,7 +79,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Animation.PodResize.Smoothness" units="%"
-    expires_after="2024-10-06">
+    expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -123,7 +123,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.FilesAppChip.Action.All"
-    enum="HoldingSpaceFilesAppChipAction" expires_after="2024-10-06">
+    enum="HoldingSpaceFilesAppChipAction" expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -143,7 +143,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Action.Launch.Empty"
-    enum="HoldingSpaceItemType" expires_after="2024-10-06">
+    enum="HoldingSpaceItemType" expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -153,7 +153,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Action.Launch.Empty.Extension"
-    enum="HoldingSpaceExtension" expires_after="2024-10-06">
+    enum="HoldingSpaceExtension" expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -163,7 +163,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Action.Launch.Failure"
-    enum="HoldingSpaceItemType" expires_after="2024-10-06">
+    enum="HoldingSpaceItemType" expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -173,7 +173,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Action.Launch.Failure.Extension"
-    enum="HoldingSpaceExtension" expires_after="2024-10-06">
+    enum="HoldingSpaceExtension" expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -183,7 +183,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Action.Launch.Failure.Reason"
-    enum="HoldingSpaceItemLaunchFailureReason" expires_after="2024-10-06">
+    enum="HoldingSpaceItemLaunchFailureReason" expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -204,7 +204,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Action.{action}.Extension"
-    enum="HoldingSpaceExtension" expires_after="2024-10-06">
+    enum="HoldingSpaceExtension" expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -288,7 +288,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Suggestions.Action.All"
-    enum="HoldingSpaceSuggestionsAction" expires_after="2024-10-06">
+    enum="HoldingSpaceSuggestionsAction" expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -298,7 +298,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.TimeFromFirstAvailabilityToFirstAdd" units="ms"
-    expires_after="2024-10-06">
+    expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -308,7 +308,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.TimeFromFirstAvailabilityToFirstEntry" units="ms"
-    expires_after="2024-10-06">
+    expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
@@ -318,7 +318,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.TimeFromFirstEntryToFirstPin" units="ms"
-    expires_after="2024-10-06">
+    expires_after="2025-02-10">
   <owner>dmblack@google.com</owner>
   <owner>alexandrahill@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/optimization/enums.xml b/tools/metrics/histograms/metadata/optimization/enums.xml
index 62052df..6812cbb 100644
--- a/tools/metrics/histograms/metadata/optimization/enums.xml
+++ b/tools/metrics/histograms/metadata/optimization/enums.xml
@@ -719,6 +719,7 @@
   <int value="68" label="AUTOFILL_PREDICTION_IMPROVEMENTS_ALLOWLIST"/>
   <int value="69" label="BUY_NOW_PAY_LATER_ALLOWLIST_AFFIRM"/>
   <int value="70" label="BUY_NOW_PAY_LATER_ALLOWLIST_ZIP"/>
+  <int value="71" label="SAVED_TAB_GROUP"/>
 </enum>
 
 <enum name="PageContentAnnotationsStorageStatus">
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index 4c1eda7..07a04946 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -249,6 +249,9 @@
       summary="Returns price related data for shopping websites"/>
   <variant name="SalientImage"
       summary="Provides information about images for a URL."/>
+  <variant name="SavedTabGroup"
+      summary="Provides information about a URL can be synced across devices
+               for saved tab groups featuer."/>
   <variant name="SharedCreditCardDiningBenefits"
       summary="This optimization provides information about whether a
                merchant url is eligible for dining credit card benefits.
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index bbb5629..3e375b2c 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,7 +6,7 @@
         },
         "win": {
             "hash": "125266f5f5cf85ec29dd380485af0bc49a44fe3a",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/7eb1a22d053b93c127020cf373d14d4e2ad8b805/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/4eb5915c04991f3c8db021120a5638c3c0742604/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "53f993fc3201982cd949c5b8de884682bb56de83",
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "69c4112543442298896da720b1089d378323e10d",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/e88c3c2a69f3a7f767dcd7ab2cba328aa622cbbb/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/2e577c61f31b9bca076afe1585b1e3b0192deef6/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index 8da1e3e..975973f 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -16,6 +16,7 @@
 #include "base/syslog_logging.h"
 #include "base/system/sys_info.h"
 #include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/display/display.h"
 #include "ui/display/display_features.h"
@@ -657,6 +658,7 @@
 }
 
 void DisplayConfigurator::TakeControl(DisplayControlCallback callback) {
+  TRACE_EVENT0("ui", "DisplayConfigurator::TakeControl");
   if (display_control_changing_) {
     LOG(ERROR) << __func__
                << " failed. There is another RelinquishControl() or "
@@ -680,6 +682,8 @@
 
 void DisplayConfigurator::OnDisplayControlTaken(DisplayControlCallback callback,
                                                 bool success) {
+  TRACE_EVENT1("ui", "DisplayConfigurator::OnDisplayControlTaken", "success",
+               success);
   display_control_changing_ = false;
   display_externally_controlled_ = !success;
   if (success) {
@@ -696,6 +700,7 @@
 }
 
 void DisplayConfigurator::RelinquishControl(DisplayControlCallback callback) {
+  TRACE_EVENT0("ui", "DisplayConfigurator::RelinquishControl");
   if (display_control_changing_) {
     LOG(ERROR) << __func__
                << " failed. There is another RelinquishControl() or "
@@ -739,6 +744,8 @@
 void DisplayConfigurator::SendRelinquishDisplayControl(
     DisplayControlCallback callback,
     bool success) {
+  TRACE_EVENT1("ui", "DisplayConfigurator::SendRelinquishDisplayControl",
+               "success", success);
   if (success) {
     // Set the flag early such that an incoming configuration event won't start
     // while we're releasing control of the displays.
@@ -758,6 +765,9 @@
 void DisplayConfigurator::OnDisplayControlRelinquished(
     DisplayControlCallback callback,
     bool success) {
+  TRACE_EVENT1("ui", "DisplayConfigurator::OnDisplayControlRelinquished",
+               "success", success);
+
   display_control_changing_ = false;
   display_externally_controlled_ = success;
   if (!success) {
diff --git a/ui/ozone/platform/drm/host/drm_display_host_manager.cc b/ui/ozone/platform/drm/host/drm_display_host_manager.cc
index fbd1e449..a9beaaf 100644
--- a/ui/ozone/platform/drm/host/drm_display_host_manager.cc
+++ b/ui/ozone/platform/drm/host/drm_display_host_manager.cc
@@ -25,6 +25,7 @@
 #include "base/task/thread_pool.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "base/threading/thread_restrictions.h"
+#include "base/trace_event/trace_event.h"
 #include "ui/display/types/display_configuration_params.h"
 #include "ui/display/types/display_snapshot.h"
 #include "ui/events/ozone/device/device_event.h"
@@ -359,6 +360,7 @@
 
 void DrmDisplayHostManager::TakeDisplayControl(
     display::DisplayControlCallback callback) {
+  TRACE_EVENT0("drm", "DrmDisplayHostManager::TakeDisplayControl");
   if (display_control_change_pending_) {
     LOG(ERROR) << "TakeDisplayControl called while change already pending";
     std::move(callback).Run(false);
@@ -380,6 +382,7 @@
 
 void DrmDisplayHostManager::RelinquishDisplayControl(
     display::DisplayControlCallback callback) {
+  TRACE_EVENT0("drm", "DrmDisplayHostManager::RelinquishDisplayControl");
   if (display_control_change_pending_) {
     LOG(ERROR)
         << "RelinquishDisplayControl called while change already pending";
diff --git a/ui/views/controls/menu/menu_controller_unittest.cc b/ui/views/controls/menu/menu_controller_unittest.cc
index ecbfc3f..7b4068cf 100644
--- a/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/ui/views/controls/menu/menu_controller_unittest.cc
@@ -1099,6 +1099,25 @@
   check_has_command(FindInitialSelectableMenuItemUp(menu_item()), 2);
 }
 
+// Verifies that the scroll arrow is shown when the menu content
+// does not fit within the available bounds.
+// (https://crbug.com/338585369)
+TEST_F(MenuControllerTest, VerifyScrollArrowShown) {
+  SubmenuView* const submenu = menu_item()->GetSubmenu();
+  auto* const scroll_container = submenu->GetScrollViewContainer();
+
+  MenuHost::InitParams params;
+  params.parent = owner();
+  params.bounds = gfx::Rect(GetPreferredSizeForSubmenu(*submenu));
+  // Show the menu at its preferred size without restriction
+  submenu->ShowAt(params);
+  EXPECT_FALSE(scroll_container->scroll_down_button()->GetVisible());
+  // decrease the available space by 1 so the contents no longer fit
+  params.bounds.set_height(params.bounds.height() - 1);
+  submenu->ShowAt(params);
+  EXPECT_TRUE(scroll_container->scroll_down_button()->GetVisible());
+}
+
 // Verifies that the context menu bubble should prioritize its cached menu
 // position (above or below the anchor) after its size updates
 // (https://crbug.com/1126244).
diff --git a/ui/views/controls/menu/menu_scroll_view_container.cc b/ui/views/controls/menu/menu_scroll_view_container.cc
index 026c58d..37135f8 100644
--- a/ui/views/controls/menu/menu_scroll_view_container.cc
+++ b/ui/views/controls/menu/menu_scroll_view_container.cc
@@ -366,8 +366,8 @@
   // offset is always reset to 0, so always hide the scroll-up control, and only
   // show the scroll-down control if it's going to be useful.
   scroll_up_button_->SetVisible(false);
-  scroll_down_button_->SetVisible(
-      scroll_view_->GetContents()->GetPreferredSize({}).height() > height());
+  scroll_down_button_->SetVisible(content_view_->GetPreferredSize({}).height() >
+                                  GetContentsBounds().height());
 
   const bool any_scroll_button_visible =
       scroll_up_button_->GetVisible() || scroll_down_button_->GetVisible();
diff --git a/v8 b/v8
index 5172b5d..7264a6e 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 5172b5da4316bd2e96fa1d3b962c6cf6782109be
+Subproject commit 7264a6ecc7eb4ccbcef3acf035ab962f987fa4ea