diff --git a/.gitmodules b/.gitmodules
index fdc9de9..c830bb4d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -488,7 +488,7 @@
 	url = https://chromium.googlesource.com/external/github.com/protocolbuffers/protobuf-javascript
 [submodule "third_party/pthreadpool/src"]
 	path = third_party/pthreadpool/src
-	url = https://chromium.googlesource.com/external/github.com/Maratyszcza/pthreadpool
+	url = https://chromium.googlesource.com/external/github.com/google/pthreadpool
 [submodule "third_party/pyelftools"]
 	path = third_party/pyelftools
 	url = https://chromium.googlesource.com/chromiumos/third_party/pyelftools
diff --git a/DEPS b/DEPS
index 862fec78..b68e5a61 100644
--- a/DEPS
+++ b/DEPS
@@ -253,7 +253,7 @@
   'screen_ai_windows_386': 'version:127.9',
 
   # siso CIPD package version.
-  'siso_version': 'git_revision:f9bfa081826a4ffb6f2a96b640d009cef900e69f',
+  'siso_version': 'git_revision:67a8780a72f407b3dffe9e338c368f0b799d004b',
 
   # download libaom test data
   'download_libaom_testdata': False,
@@ -280,15 +280,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '539c311a3a7fa76bf50a9c83822815abdb825a18',
+  'skia_revision': 'c24b41eebcc69f4f8156716a411de04a80b6c3bc',
   # 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': 'fc91f65bbd2f5dfa85e65a007ec01a71a10a45b4',
+  'v8_revision': '897d8f21c242c88045314d105862e4c88e78b859',
   # 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': '4c60a308592af56dc745d8d1ba994e89ebb053db',
+  'angle_revision': 'b3af2e86f4b75b3c8f8053175fe97c0372926675',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -300,7 +300,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': '7db3433bd4466b20ade77494cd3bb03396441aef',
+  'boringssl_revision': 'bf4cf6938a77f1aca83ef529dce96681efd1e6c5',
   # 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.
@@ -348,7 +348,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '580dbb72d8984e972a02874d06ace7b13295798a',
+  'catapult_revision': 'd25caed4b98fea8a30ecf85a9c2b056cb139809e',
   # 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.
@@ -364,7 +364,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling fuzztest
   # and whatever else without interference from each other.
-  'fuzztest_revision': 'ae6208fc45a09da94d9c0925e26cd9bbca92154b',
+  'fuzztest_revision': 'b86e98ff1149313e43333d1017c3a87baa6721c5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling domato
   # 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 feed
   # and whatever else without interference from each other.
-  'dawn_revision': '041b072241d75850d18f441b53bacf2d317e9818',
+  'dawn_revision': '60a78b1ae59d6724d658e1eb3d8e2813afb29cb9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -468,7 +468,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'libcxxabi_revision':    '7681005c6233e8a21b97e24c1a3c5c6979927d5a',
+  'libcxxabi_revision':    'cbada99a33f015cb8333d63a88ff0c10cbbc6f38',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1439,12 +1439,12 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'ff16da349347e90984ba1bc206cafccad280e3c6',
+    '13571b2b78ee3c8179518064da9ed1a3f5da47ec',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'fb143e8ce76fcf5f67792fbbd6c219c5d03ec2d9',
+    'url': Var('chromium_git') + '/website.git' + '@' + '54467c5b22ad5d81a5468007092db3e5d33d7f5b',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -1598,7 +1598,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'fo4-wfWpq9bmst8F64v9PNRzBj6NBcDIlW9IT490PPkC',
+          'version': 'wo7iIVgF-B5pVgthj7EDcFUn84oMIQo4Z1ZNtYHK-QIC',
       },
     ],
     'condition': 'checkout_android and non_git_source',
@@ -1842,7 +1842,7 @@
   },
 
   'src/third_party/barhopper': {
-      'url': Var('chrome_git') + '/chrome/deps/barhopper.git' + '@' + '865bd06ef4a839b0a15d17e38e25f8911e4cdf9f',
+      'url': Var('chrome_git') + '/chrome/deps/barhopper.git' + '@' + '9230af4dc38c6d2cc9c0841692267762ebfca991',
       'condition': 'checkout_src_internal and checkout_chromeos',
   },
 
@@ -2436,7 +2436,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '38f6220c882b08ed8bd8cbb47cff50cfa790e41b',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '2eab41ff134151a618b4f54d4b8913d770c4ac22',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -2591,7 +2591,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/qemu/linux-arm64',
-              'version': 'hOpuGIMj1FAtBWGDlXARkCm2srxY4enn8iI3AgrDna4C'
+              'version': 'MDf3sCxn9kct3Tg1oVRHch1hkzO6-9qZZFBRPT6jDuoC'
           },
       ],
       # TODO(b/351926334): Do not add `non_git_source` to this condition until the bug is fixed.
@@ -2752,16 +2752,16 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@ee1a15d510c031acaff02138a922ccdb6f85c2a7',
-  'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@e43514866f7e0f8265c677039d2fe773c892d44b',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@5b43f9496300bdac77c67501dc3b3738cb3d21c5',
+  'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@c16e6a34b72de51d07c121a1c202806bac0be9dc',
   '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@a380cd25433092dbce9a455a3feb1242138febee',
-  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@9064fe8637daf9f694c8a2e035a34da94022f2be',
+  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@995922d48149384766cc646159a9e28701f01f0c',
   'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@d4a196d8c84e032d27f999adcea3075517c1c97f',
   'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@369f59ad598b60d6ed9f553af651c5cccd20234c',
   'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@315964ad5aabd5b148a484e5fbea8a365c8d1eb3',
   'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@5a88b6042edb8f03eefc8de73bd73a899989373f',
-  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@5cceb78082833556789a64f3237b04df7a826d93',
+  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@cddf2371ee3ef9c31deea06ce14df558c20ece04',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21',
@@ -2806,7 +2806,7 @@
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'da8a535ad4e41fbb587b38346d324a2892ba4183',
+    Var('webrtc_git') + '/src.git' + '@' + '2911627ab3cb5e2549d7e65d176a01ed398b874a',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -4568,7 +4568,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        'c37133f0c4465e77570c88c93940ae58a3d35819',
+        'd2dce193b98bc200e7b9d6c6894fe09fdcb16b39',
       'condition': 'checkout_src_internal',
   },
 
@@ -4634,7 +4634,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '53efd8418bb70bf06616f417a7090f9d3b659343',
+        'a48abcfc293d8e695d52adafe5a76954066a0ab8',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index d1877fb..e44039c 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -1263,8 +1263,7 @@
 
 bool AwContentBrowserClient::SuppressDifferentOriginSubframeJSDialogs(
     content::BrowserContext* browser_context) {
-  return base::FeatureList::IsEnabled(
-      features::kWebViewSuppressDifferentOriginSubframeJSDialogs);
+  return false;
 }
 
 bool AwContentBrowserClient::ShouldPreconnectNavigation(
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index d9ae230..50d97c69 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -118,12 +118,6 @@
              "WebViewSupervisedUserSiteBlock",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Disallows window.{alert, prompt, confirm} if triggered inside a subframe that
-// is not same origin with the main frame.
-BASE_FEATURE(kWebViewSuppressDifferentOriginSubframeJSDialogs,
-             "WebViewSuppressDifferentOriginSubframeJSDialogs",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // A Feature used for WebView variations tests. Not used in production. Please
 // do not clean up this stale feature: we intentionally keep this feature flag
 // around for testing purposes.
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index b4815c3..9c9f0ae 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -35,7 +35,6 @@
 BASE_DECLARE_FEATURE(kWebViewRestrictSensitiveContent);
 BASE_DECLARE_FEATURE(kWebViewSupervisedUserSiteDetection);
 BASE_DECLARE_FEATURE(kWebViewSupervisedUserSiteBlock);
-BASE_DECLARE_FEATURE(kWebViewSuppressDifferentOriginSubframeJSDialogs);
 BASE_DECLARE_FEATURE(kWebViewTestFeature);
 BASE_DECLARE_FEATURE(kWebViewUseMetricsUploadService);
 BASE_DECLARE_FEATURE(kWebViewUseMetricsUploadServiceOnlySdkRuntime);
diff --git a/ash/capture_mode/capture_mode_menu_group.cc b/ash/capture_mode/capture_mode_menu_group.cc
index bea4c64..e03c464 100644
--- a/ash/capture_mode/capture_mode_menu_group.cc
+++ b/ash/capture_mode/capture_mode_menu_group.cc
@@ -305,7 +305,7 @@
 
     const auto checked_icon_enabled_color =
         color_provider->GetColor(kColorAshButtonLabelColorBlue);
-    checked_icon_view_->SetImage(gfx::CreateVectorIcon(
+    checked_icon_view_->SetImage(ui::ImageModel::FromVectorIcon(
         kHollowCheckCircleIcon,
         is_disabled ? ColorUtil::GetDisabledColor(checked_icon_enabled_color)
                     : checked_icon_enabled_color));
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index 95dc7cf..1ee168b 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -5,6 +5,7 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <tuple>
 #include <vector>
 
 #include "ash/accessibility/magnifier/docked_magnifier_controller.h"
@@ -35,6 +36,7 @@
 #include "ash/capture_mode/test_capture_mode_delegate.h"
 #include "ash/capture_mode/user_nudge_controller.h"
 #include "ash/capture_mode/video_recording_watcher.h"
+#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/display/cursor_window_controller.h"
 #include "ash/display/output_protection_delegate.h"
@@ -78,9 +80,11 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
+#include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "chromeos/ash/services/recording/recording_service_test_api.h"
 #include "chromeos/dbus/power/fake_power_manager_client.h"
 #include "chromeos/dbus/power_manager/suspend.pb.h"
@@ -92,6 +96,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/capture_client.h"
 #include "ui/aura/client/capture_client_observer.h"
@@ -203,14 +208,14 @@
 
 }  // namespace
 
-class CaptureModeTest : public AshTestBase {
+class CaptureModeTestBase : public AshTestBase {
  public:
-  CaptureModeTest() = default;
-  explicit CaptureModeTest(base::test::TaskEnvironment::TimeSource time)
+  CaptureModeTestBase() = default;
+  explicit CaptureModeTestBase(base::test::TaskEnvironment::TimeSource time)
       : AshTestBase(time) {}
-  CaptureModeTest(const CaptureModeTest&) = delete;
-  CaptureModeTest& operator=(const CaptureModeTest&) = delete;
-  ~CaptureModeTest() override = default;
+  CaptureModeTestBase(const CaptureModeTestBase&) = delete;
+  CaptureModeTestBase& operator=(const CaptureModeTestBase&) = delete;
+  ~CaptureModeTestBase() override = default;
 
   // AshTestBase:
   void SetUp() override {
@@ -362,8 +367,58 @@
     return controller->video_recording_watcher_for_testing()
         ->window_being_recorded();
   }
+
+ protected:
+  // Used in other fixtures to more easily initialise Sunfish and Scanner
+  // features.
+  void InitFeatures(bool sunfish_enabled, bool scanner_enabled) {
+    std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
+
+    (sunfish_enabled ? enabled_features : disabled_features)
+        .push_back(features::kSunfishFeature);
+    (scanner_enabled ? enabled_features : disabled_features)
+        .push_back(features::kScannerUpdate);
+    (scanner_enabled ? enabled_features : disabled_features)
+        .push_back(features::kScannerDogfood);
+
+    feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
+
+  base::test::ScopedFeatureList feature_list_;
 };
 
+std::string SunfishScannerTestName(bool sunfish_enabled, bool scanner_enabled) {
+  return base::StrCat({"Sunfish", sunfish_enabled ? "Enabled" : "Disabled",
+                       "Scanner", scanner_enabled ? "Enabled" : "Disabled"});
+}
+
+class CaptureModeTest
+    : public CaptureModeTestBase,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  CaptureModeTest() = default;
+  explicit CaptureModeTest(base::test::TaskEnvironment::TimeSource time)
+      : CaptureModeTestBase(time) {}
+
+  // CaptureModeTestBase:
+  void SetUp() override {
+    auto [sunfish_enabled, scanner_enabled] = GetParam();
+    InitFeatures(sunfish_enabled, scanner_enabled);
+    CaptureModeTestBase::SetUp();
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    CaptureModeTest,
+    testing::Combine(testing::Bool(), testing::Bool()),
+    [](const testing::TestParamInfo<CaptureModeTest::ParamType>& info) {
+      bool sunfish_enabled = std::get<0>(info.param);
+      bool scanner_enabled = std::get<1>(info.param);
+      return SunfishScannerTestName(sunfish_enabled, scanner_enabled);
+    });
+
 class CaptureSessionWidgetClosed {
  public:
   explicit CaptureSessionWidgetClosed(views::Widget* widget) {
@@ -381,7 +436,7 @@
   base::WeakPtr<views::Widget> widget_;
 };
 
-TEST_F(CaptureModeTest, StartStop) {
+TEST_P(CaptureModeTest, StartStop) {
   auto* controller = CaptureModeController::Get();
   controller->Start(CaptureModeEntryType::kQuickSettings);
   EXPECT_TRUE(controller->IsActive());
@@ -398,7 +453,7 @@
   EXPECT_FALSE(controller->IsActive());
 }
 
-TEST_F(CaptureModeTest, CheckCursorVisibility) {
+TEST_P(CaptureModeTest, CheckCursorVisibility) {
   // Hide cursor before entering capture mode.
   auto* cursor_manager = Shell::Get()->cursor_manager();
   cursor_manager->SetCursor(CursorType::kPointer);
@@ -423,7 +478,7 @@
   EXPECT_TRUE(cursor_manager->IsCursorVisible());
 }
 
-TEST_F(CaptureModeTest, CheckCursorVisibilityOnTabletMode) {
+TEST_P(CaptureModeTest, CheckCursorVisibilityOnTabletMode) {
   auto* cursor_manager = Shell::Get()->cursor_manager();
 
   // Enter tablet mode.
@@ -443,13 +498,13 @@
 }
 
 // Regression test for https://crbug.com/1172425.
-TEST_F(CaptureModeTest, NoCrashOnClearingCapture) {
+TEST_P(CaptureModeTest, NoCrashOnClearingCapture) {
   TestCaptureClientObserver observer(CreateTestWindow(gfx::Rect(200, 200)));
   auto* controller = StartImageRegionCapture();
   EXPECT_TRUE(controller->IsActive());
 }
 
-TEST_F(CaptureModeTest, CheckWidgetClosed) {
+TEST_P(CaptureModeTest, CheckWidgetClosed) {
   auto* controller = CaptureModeController::Get();
   controller->Start(CaptureModeEntryType::kQuickSettings);
   EXPECT_TRUE(controller->IsActive());
@@ -463,7 +518,7 @@
   EXPECT_TRUE(observer.GetWidgetClosed());
 }
 
-TEST_F(CaptureModeTest, StartWithMostRecentTypeAndSource) {
+TEST_P(CaptureModeTest, StartWithMostRecentTypeAndSource) {
   auto* controller = CaptureModeController::Get();
   controller->SetSource(CaptureModeSource::kFullscreen);
   controller->SetType(CaptureModeType::kVideo);
@@ -480,7 +535,7 @@
   EXPECT_FALSE(controller->IsActive());
 }
 
-TEST_F(CaptureModeTest, AccessibleCheckedState) {
+TEST_P(CaptureModeTest, AccessibleCheckedState) {
   auto* controller = CaptureModeController::Get();
   controller->Start(CaptureModeEntryType::kQuickSettings);
   ui::AXNodeData data;
@@ -494,7 +549,7 @@
   EXPECT_EQ(data.GetCheckedState(), ax::mojom::CheckedState::kFalse);
 }
 
-TEST_F(CaptureModeTest, ChangeTypeAndSourceFromUI) {
+TEST_P(CaptureModeTest, ChangeTypeAndSourceFromUI) {
   auto* controller = CaptureModeController::Get();
   controller->Start(CaptureModeEntryType::kQuickSettings);
   EXPECT_TRUE(controller->IsActive());
@@ -520,7 +575,7 @@
   EXPECT_EQ(controller->source(), CaptureModeSource::kFullscreen);
 }
 
-TEST_F(CaptureModeTest, VideoRecordingUiBehavior) {
+TEST_P(CaptureModeTest, VideoRecordingUiBehavior) {
   // Start Capture Mode in a fullscreen video recording mode.
   CaptureModeController* controller = StartCaptureSession(
       CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
@@ -557,7 +612,7 @@
       EndRecordingReason::kStopRecordingButton, 1);
 }
 
-TEST_F(CaptureModeTest, NoCrashOnMultipleClicksOnStopRecordingButton) {
+TEST_P(CaptureModeTest, NoCrashOnMultipleClicksOnStopRecordingButton) {
   ash::CaptureModeTestApi test_api;
   test_api.StartForFullscreen(/*for_video=*/true);
   test_api.PerformCapture();
@@ -581,7 +636,7 @@
 }
 
 // Tests the behavior of repositioning a region with capture mode.
-TEST_F(CaptureModeTest, CaptureRegionRepositionBehavior) {
+TEST_P(CaptureModeTest, CaptureRegionRepositionBehavior) {
   // Use a set display size as we will be choosing points in this test.
   UpdateDisplay("800x700");
 
@@ -610,7 +665,7 @@
 
 // Tests the behavior of resizing a region with capture mode using the corner
 // drag affordances.
-TEST_F(CaptureModeTest, CaptureRegionCornerResizeBehavior) {
+TEST_P(CaptureModeTest, CaptureRegionCornerResizeBehavior) {
   // Use a set display size as we will be choosing points in this test.
   UpdateDisplay("800x700");
 
@@ -673,7 +728,7 @@
 
 // Tests the behavior of resizing a region with capture mode using the edge drag
 // affordances.
-TEST_F(CaptureModeTest, CaptureRegionEdgeResizeBehavior) {
+TEST_P(CaptureModeTest, CaptureRegionEdgeResizeBehavior) {
   // Use a set display size as we will be choosing points in this test.
   UpdateDisplay("800x700");
 
@@ -755,7 +810,7 @@
 
 // Tests that the capture region persists after exiting and reentering capture
 // mode.
-TEST_F(CaptureModeTest, CaptureRegionPersistsAfterExit) {
+TEST_P(CaptureModeTest, CaptureRegionPersistsAfterExit) {
   auto* controller = StartImageRegionCapture();
   const gfx::Rect region(100, 100, 200, 200);
   SelectRegion(region);
@@ -767,7 +822,7 @@
 
 // Tests that the capture region resets when clicking outside the current
 // capture regions bounds.
-TEST_F(CaptureModeTest, CaptureRegionResetsOnClickOutside) {
+TEST_P(CaptureModeTest, CaptureRegionResetsOnClickOutside) {
   auto* controller = StartImageRegionCapture();
   SelectRegion(gfx::Rect(100, 100, 200, 200));
 
@@ -781,7 +836,14 @@
 
 // Tests that buttons on the capture mode bar still work when a region is
 // "covering" them.
-TEST_F(CaptureModeTest, CaptureRegionCoversCaptureModeBar) {
+TEST_P(CaptureModeTest, CaptureRegionCoversCaptureModeBar) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled || scanner_enabled) {
+    // This test fails when either Sunfish or Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   UpdateDisplay("800x700");
 
   auto* controller = StartImageRegionCapture();
@@ -809,7 +871,7 @@
 
 // Tests that the magnifying glass appears while fine tuning the capture region,
 // and that the cursor is hidden if the magnifying glass is present.
-TEST_F(CaptureModeTest, CaptureRegionMagnifierWhenFineTuning) {
+TEST_P(CaptureModeTest, CaptureRegionMagnifierWhenFineTuning) {
   const gfx::Vector2d kDragDelta(50, 50);
   UpdateDisplay("800x700");
 
@@ -887,7 +949,7 @@
 }
 
 // Tests that the dimensions label properly renders for capture regions.
-TEST_F(CaptureModeTest, CaptureRegionDimensionsLabelLocation) {
+TEST_P(CaptureModeTest, CaptureRegionDimensionsLabelLocation) {
   UpdateDisplay("900x800");
 
   // Start Capture Mode in a region in image mode.
@@ -946,7 +1008,7 @@
   EXPECT_EQ(nullptr, GetDimensionsLabelWindow());
 }
 
-TEST_F(CaptureModeTest, CaptureRegionCaptureButtonLocation) {
+TEST_P(CaptureModeTest, CaptureRegionCaptureButtonLocation) {
   UpdateDisplay("900x800");
 
   auto* controller = StartImageRegionCapture();
@@ -986,7 +1048,7 @@
 // Tests some edge cases to ensure the capture button does not intersect the
 // capture bar and end up unclickable since it is stacked below the capture bar.
 // Regression test for https://crbug.com/1186462.
-TEST_F(CaptureModeTest, CaptureRegionCaptureButtonDoesNotIntersectCaptureBar) {
+TEST_P(CaptureModeTest, CaptureRegionCaptureButtonDoesNotIntersectCaptureBar) {
   UpdateDisplay("800x700");
 
   StartImageRegionCapture();
@@ -1036,7 +1098,7 @@
 // Tests that pressing on the capture bar and releasing the press outside of the
 // capture bar, the capture region could still be draggable and set. Regression
 // test for https://crbug.com/1325028.
-TEST_F(CaptureModeTest, SetCaptureRegionAfterPressOnCaptureBar) {
+TEST_P(CaptureModeTest, SetCaptureRegionAfterPressOnCaptureBar) {
   UpdateDisplay("800x600");
 
   auto* controller =
@@ -1058,7 +1120,7 @@
   EXPECT_EQ(controller->user_capture_region(), region_bounds);
 }
 
-TEST_F(CaptureModeTest, WindowCapture) {
+TEST_P(CaptureModeTest, WindowCapture) {
   // Create 2 windows that overlap with each other.
   const gfx::Rect bounds1(0, 0, 200, 200);
   std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds1));
@@ -1096,7 +1158,7 @@
   controller->Stop();
 }
 
-TEST_F(CaptureModeTest, WindowCaptureConfineBoundsDoNotOverlapWindowCaption) {
+TEST_P(CaptureModeTest, WindowCaptureConfineBoundsDoNotOverlapWindowCaption) {
   std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(200, 200)));
   auto* controller =
       StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
@@ -1122,7 +1184,7 @@
 
 // Tests that the capture bar is located on the root with the cursor when
 // starting capture mode.
-TEST_F(CaptureModeTest, MultiDisplayCaptureBarInitialLocation) {
+TEST_P(CaptureModeTest, MultiDisplayCaptureBarInitialLocation) {
   UpdateDisplay("800x700,801+0-800x700");
 
   auto* event_generator = GetEventGenerator();
@@ -1140,7 +1202,7 @@
 }
 
 // Tests behavior of a capture mode session if the active display is removed.
-TEST_F(CaptureModeTest, DisplayRemoval) {
+TEST_P(CaptureModeTest, DisplayRemoval) {
   UpdateDisplay("1200x700,1201+0-800x700");
 
   // Start capture mode on the secondary display.
@@ -1166,7 +1228,7 @@
 
 // Tests behavior of a capture mode session if the active display is removed
 // and countdown running.
-TEST_F(CaptureModeTest, DisplayRemovalWithCountdownVisible) {
+TEST_P(CaptureModeTest, DisplayRemovalWithCountdownVisible) {
   UpdateDisplay("800x700,801+0-800x700");
 
   // Start capture mode on the secondary display.
@@ -1186,7 +1248,7 @@
 
 // Tests behavior of a capture mode session if the active display is removed,
 // countdown running, fullscreen window, and in overview mode.
-TEST_F(CaptureModeTest,
+TEST_P(CaptureModeTest,
        DisplayRemovalWithCountdownVisibleFullscreenWindowAndInOverview) {
   UpdateDisplay("800x700,801+0-800x700");
 
@@ -1213,7 +1275,7 @@
 
 // Tests that using fullscreen or window source, moving the mouse across
 // displays will change the root window of the capture session.
-TEST_F(CaptureModeTest, MultiDisplayFullscreenOrWindowSourceRootWindow) {
+TEST_P(CaptureModeTest, MultiDisplayFullscreenOrWindowSourceRootWindow) {
   UpdateDisplay("800x700,801+0-800x700");
   ASSERT_EQ(2u, Shell::GetAllRootWindows().size());
 
@@ -1241,7 +1303,7 @@
 
 // Tests that in region mode, moving the mouse across displays will not change
 // the root window of the capture session, but clicking on a new display will.
-TEST_F(CaptureModeTest, MultiDisplayRegionSourceRootWindow) {
+TEST_P(CaptureModeTest, MultiDisplayRegionSourceRootWindow) {
   UpdateDisplay("800x700,801+0-800x700");
   ASSERT_EQ(2u, Shell::GetAllRootWindows().size());
 
@@ -1272,7 +1334,7 @@
 
 // Tests that using touch on multi display setups works as intended. Regression
 // test for https://crbug.com/1159512.
-TEST_F(CaptureModeTest, MultiDisplayTouch) {
+TEST_P(CaptureModeTest, MultiDisplayTouch) {
   UpdateDisplay("800x700,801+0-800x700");
   ASSERT_EQ(2u, Shell::GetAllRootWindows().size());
 
@@ -1290,7 +1352,14 @@
   EXPECT_EQ(gfx::Size(200, 200), controller->user_capture_region().size());
 }
 
-TEST_F(CaptureModeTest, RegionCursorStates) {
+TEST_P(CaptureModeTest, RegionCursorStates) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled || scanner_enabled) {
+    // This test fails when either Sunfish or Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   UpdateDisplay("800x700,801+0-800x700");
 
   auto* cursor_manager = Shell::Get()->cursor_manager();
@@ -1424,7 +1493,7 @@
 }
 
 // Regression testing for https://crbug.com/1334824.
-TEST_F(CaptureModeTest, CursorShouldNotChangeWhileAdjustingRegion) {
+TEST_P(CaptureModeTest, CursorShouldNotChangeWhileAdjustingRegion) {
   UpdateDisplay("800x600");
 
   auto* cursor_manager = Shell::Get()->cursor_manager();
@@ -1446,7 +1515,7 @@
   EXPECT_EQ(CursorType::kSouthEastResize, cursor_manager->GetCursor().type());
 }
 
-TEST_F(CaptureModeTest, FullscreenCursorStates) {
+TEST_P(CaptureModeTest, FullscreenCursorStates) {
   auto* cursor_manager = Shell::Get()->cursor_manager();
   CursorType original_cursor_type = cursor_manager->GetCursor().type();
   EXPECT_FALSE(cursor_manager->IsCursorLocked());
@@ -1518,7 +1587,7 @@
   EXPECT_TRUE(cursor_manager->IsCursorVisible());
 }
 
-TEST_F(CaptureModeTest, WindowCursorStates) {
+TEST_P(CaptureModeTest, WindowCursorStates) {
   std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(200, 200)));
 
   auto* cursor_manager = Shell::Get()->cursor_manager();
@@ -1596,7 +1665,7 @@
 }
 
 // Tests that nothing crashes when windows are destroyed while being observed.
-TEST_F(CaptureModeTest, WindowDestruction) {
+TEST_P(CaptureModeTest, WindowDestruction) {
   // Create 2 windows that overlap with each other.
   const gfx::Rect bounds1(0, 0, 200, 200);
   const gfx::Rect bounds2(150, 150, 200, 200);
@@ -1671,7 +1740,7 @@
   EXPECT_FALSE(controller->IsActive());
 }
 
-TEST_F(CaptureModeTest, CursorUpdatedOnDisplayRotation) {
+TEST_P(CaptureModeTest, CursorUpdatedOnDisplayRotation) {
   UpdateDisplay("600x400");
   const int64_t display_id =
       display::Screen::GetScreen()->GetPrimaryDisplay().id();
@@ -1703,7 +1772,7 @@
 
 // Tests that in Region mode, cursor compositing is used instead of the system
 // cursor when the cursor is being dragged.
-TEST_F(CaptureModeTest, RegionDragCursorCompositing) {
+TEST_P(CaptureModeTest, RegionDragCursorCompositing) {
   auto* event_generator = GetEventGenerator();
   auto* session = StartImageRegionCapture()->capture_mode_session();
   auto* cursor_manager = Shell::Get()->cursor_manager();
@@ -1749,7 +1818,7 @@
 
 // Test that during countdown, capture mode session should not handle any
 // incoming input events.
-TEST_F(CaptureModeTest, DoNotHandleEventDuringCountDown) {
+TEST_P(CaptureModeTest, DoNotHandleEventDuringCountDown) {
   // We need a non-zero duration to avoid infinite loop on countdown.
   ui::ScopedAnimationDurationScaleMode animation_scale(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
@@ -1783,7 +1852,7 @@
 }
 
 // Test that during countdown, window changes or crashes are handled.
-TEST_F(CaptureModeTest, WindowChangesDuringCountdown) {
+TEST_P(CaptureModeTest, WindowChangesDuringCountdown) {
   // We need a non-zero duration to avoid infinite loop on countdown.
   ui::ScopedAnimationDurationScaleMode animation_scale(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
@@ -1831,7 +1900,7 @@
 
 // Verifies that the video notification will show the same thumbnail image as
 // sent by recording service.
-TEST_F(CaptureModeTest, VideoNotificationThumbnail) {
+TEST_P(CaptureModeTest, VideoNotificationThumbnail) {
   auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                          CaptureModeType::kVideo);
   StartVideoRecordingImmediately();
@@ -1863,7 +1932,7 @@
       gfx::test::AreBitmapsEqual(notification_thumbnail, service_thumbnail));
 }
 
-TEST_F(CaptureModeTest, LowDriveFsSpace) {
+TEST_P(CaptureModeTest, LowDriveFsSpace) {
   auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                          CaptureModeType::kVideo);
   const base::FilePath drive_fs_folder = CreateFolderOnDriveFS("test");
@@ -1887,7 +1956,7 @@
       EndRecordingReason::kLowDriveFsQuota, 1);
 }
 
-TEST_F(CaptureModeTest, WindowRecordingCaptureId) {
+TEST_P(CaptureModeTest, WindowRecordingCaptureId) {
   auto window = CreateTestWindow(gfx::Rect(200, 200));
   StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
 
@@ -1906,7 +1975,7 @@
   EXPECT_FALSE(window->subtree_capture_id().is_valid());
 }
 
-TEST_F(CaptureModeTest, ClosingDimmedWidgetAboveRecordedWindow) {
+TEST_P(CaptureModeTest, ClosingDimmedWidgetAboveRecordedWindow) {
   views::Widget* widget = TestWidgetBuilder().BuildOwnedByNativeWidget();
   auto* window = widget->GetNativeWindow();
   auto recorded_window = CreateTestWindow(gfx::Rect(200, 200));
@@ -1925,7 +1994,7 @@
   widget->Close();
 }
 
-TEST_F(CaptureModeTest, DimmingOfUnRecordedWindows) {
+TEST_P(CaptureModeTest, DimmingOfUnRecordedWindows) {
   auto win1 = CreateTestWindow(gfx::Rect(200, 200));
   auto win2 = CreateTestWindow(gfx::Rect(200, 200));
   auto recorded_window = CreateTestWindow(gfx::Rect(200, 200));
@@ -1982,7 +2051,7 @@
   EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win2.get()));
 }
 
-TEST_F(CaptureModeTest, DimmingWithDesks) {
+TEST_P(CaptureModeTest, DimmingWithDesks) {
   auto recorded_window = CreateAppWindow(gfx::Rect(250, 100));
   auto* controller = StartSessionAndRecordWindow(recorded_window.get());
   auto* recording_watcher = controller->video_recording_watcher_for_testing();
@@ -2016,7 +2085,7 @@
   EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(win1.get()));
 }
 
-TEST_F(CaptureModeTest, DimmingWithDisplays) {
+TEST_P(CaptureModeTest, DimmingWithDisplays) {
   UpdateDisplay("500x400,401+0-800x700");
   auto recorded_window = CreateAppWindow(gfx::Rect(250, 100));
   auto* controller = StartSessionAndRecordWindow(recorded_window.get());
@@ -2041,7 +2110,7 @@
   EXPECT_FALSE(recording_watcher->IsWindowDimmedForTesting(window.get()));
 }
 
-TEST_F(CaptureModeTest, MultiDisplayWindowRecording) {
+TEST_P(CaptureModeTest, MultiDisplayWindowRecording) {
   UpdateDisplay("500x400,401+0-800x700");
   auto roots = Shell::GetAllRootWindows();
   ASSERT_EQ(2u, roots.size());
@@ -2093,7 +2162,7 @@
 }
 
 // Flaky especially on MSan: https://crbug.com/1293188
-TEST_F(CaptureModeTest, DISABLED_WindowResizing) {
+TEST_P(CaptureModeTest, DISABLED_WindowResizing) {
   UpdateDisplay("700x600");
   auto window = CreateTestWindow(gfx::Rect(200, 200));
   auto* controller =
@@ -2145,7 +2214,7 @@
   EXPECT_EQ(window->bounds().size(), test_delegate->GetCurrentVideoSize());
 }
 
-TEST_F(CaptureModeTest, RotateDisplayWhileRecording) {
+TEST_P(CaptureModeTest, RotateDisplayWhileRecording) {
   UpdateDisplay("600x800");
 
   auto* controller =
@@ -2177,7 +2246,7 @@
 
 // Regression test for https://crbug.com/1331095.
 // This is disabled due to flakiness: b/318349807
-TEST_F(CaptureModeTest, DISABLED_CornerRegionWithScreenRotation) {
+TEST_P(CaptureModeTest, DISABLED_CornerRegionWithScreenRotation) {
   UpdateDisplay("800x600");
 
   // Pick a region at the bottom right corner of the landscape screen, so that
@@ -2234,7 +2303,7 @@
 // This is a regression test for https://crbug.com/1214023.
 //
 // TODO(crbug.com/1439950): This test is flaky.
-TEST_F(CaptureModeTest, DISABLED_VerifyWindowRecordingVideoFrames) {
+TEST_P(CaptureModeTest, DISABLED_VerifyWindowRecordingVideoFrames) {
   auto window = CreateTestWindow(gfx::Rect(100, 50, 200, 200));
   StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
 
@@ -2332,7 +2401,7 @@
 
 // Tests that the focus should be on the `Settings` button after closing the
 // settings menu.
-TEST_F(CaptureModeTest, ReturnFocusToSettingsButtonAfterSettingsMenuIsClosed) {
+TEST_P(CaptureModeTest, ReturnFocusToSettingsButtonAfterSettingsMenuIsClosed) {
   auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                          CaptureModeType::kImage);
   auto* capture_mode_session =
@@ -2385,7 +2454,7 @@
 // Tests that minimized window(s) will be ignored whereas four corners occluded
 // but overall partially occluded window will be focusable while tabbing through
 // in `kWindow` mode.
-TEST_F(CaptureModeTest, IgnoreMinimizeWindowsInKWindow) {
+TEST_P(CaptureModeTest, IgnoreMinimizeWindowsInKWindow) {
   // Layout of three windows: four corners of `window3` are occluded by
   // `window1` and `window2`.
   //
@@ -2441,7 +2510,7 @@
 
 // Tests that partially occluded window(s) will be focusable even when four
 // edges are occluded by other windows while tabbing through in `kWindow` mode.
-TEST_F(CaptureModeTest, PartiallyOccludedWindowIsFocusableInKWindow) {
+TEST_P(CaptureModeTest, PartiallyOccludedWindowIsFocusableInKWindow) {
   // Layout of five windows: four edges of `window3` is occluded by `window1`,
   // `window2`, `window4` and `window5` respectively, but the middle part is not
   // occluded.
@@ -2503,7 +2572,7 @@
 
 // Tests that fully occluded window(s) will be ignored while tabbing in
 // `kWindow`.
-TEST_F(CaptureModeTest, IgnoreFullyOccludedWindowWhileTabbingInKWindow) {
+TEST_P(CaptureModeTest, IgnoreFullyOccludedWindowWhileTabbingInKWindow) {
   // Layout of six windows: `window3` is fully occluded by `window1`, `window2`,
   // `window4`, `window5` and `window6`.
   //        +-----------+
@@ -2567,7 +2636,7 @@
 // Tests that only Tab and Shift + Tab events advance/reverse focus and stop
 // event propagation. Other events like Alt + Tab should still behave as
 // intended.
-TEST_F(CaptureModeTest, OnlyAdvanceFocusWhenTabShiftPressed) {
+TEST_P(CaptureModeTest, OnlyAdvanceFocusWhenTabShiftPressed) {
   auto window1 = CreateTestWindow();
   auto window2 = CreateTestWindow();
 
@@ -2631,7 +2700,7 @@
 
 // Tests that the capture region will be refreshed if in overview to reflect the
 // bounds of the overview item for this window in `kWindow` mode.
-TEST_F(CaptureModeTest, RefreshCaptureRegionInOverviewForKWindow) {
+TEST_P(CaptureModeTest, RefreshCaptureRegionInOverviewForKWindow) {
   auto window = CreateAppWindow(gfx::Rect(100, 50, 200, 200));
   auto* controller =
       StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
@@ -2658,7 +2727,7 @@
 }
 
 class CaptureModeSaveFileTest
-    : public CaptureModeTest,
+    : public CaptureModeTestBase,
       public testing::WithParamInterface<CaptureModeType> {
  public:
   CaptureModeSaveFileTest() = default;
@@ -2849,13 +2918,24 @@
   std::unique_ptr<aura::Window> window_;
 };
 
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    CaptureModeRecordingSizeTest,
+    testing::Combine(testing::Bool(), testing::Bool()),
+    [](const testing::TestParamInfo<CaptureModeRecordingSizeTest::ParamType>&
+           info) {
+      bool sunfish_enabled = std::get<0>(info.param);
+      bool scanner_enabled = std::get<1>(info.param);
+      return SunfishScannerTestName(sunfish_enabled, scanner_enabled);
+    });
+
 // TODO(crbug.com/1291073): Flaky on ChromeOS.
 #if BUILDFLAG(IS_CHROMEOS)
 #define MAYBE_CaptureAtPixelsFullscreen DISABLED_CaptureAtPixelsFullscreen
 #else
 #define MAYBE_CaptureAtPixelsFullscreen CaptureAtPixelsFullscreen
 #endif
-TEST_F(CaptureModeRecordingSizeTest, MAYBE_CaptureAtPixelsFullscreen) {
+TEST_P(CaptureModeRecordingSizeTest, MAYBE_CaptureAtPixelsFullscreen) {
   float dsf = 1.6f;
   SetDeviceScaleFactor(dsf);
   EXPECT_EQ(dsf, window_->GetHost()->device_scale_factor());
@@ -2914,7 +2994,7 @@
 }
 
 // The test is flaky. https://crbug.com/1287724.
-TEST_F(CaptureModeRecordingSizeTest, DISABLED_CaptureAtPixelsRegion) {
+TEST_P(CaptureModeRecordingSizeTest, DISABLED_CaptureAtPixelsRegion) {
   float dsf = 1.6f;
   SetDeviceScaleFactor(dsf);
   EXPECT_EQ(dsf, window_->GetHost()->device_scale_factor());
@@ -2962,7 +3042,7 @@
 }
 
 // The test is flaky. https://crbug.com/1287724.
-TEST_F(CaptureModeRecordingSizeTest, DISABLED_CaptureAtPixelsWindow) {
+TEST_P(CaptureModeRecordingSizeTest, DISABLED_CaptureAtPixelsWindow) {
   float dsf = 1.6f;
   SetDeviceScaleFactor(dsf);
   EXPECT_EQ(dsf, window_->GetHost()->device_scale_factor());
@@ -3015,15 +3095,15 @@
 // content on the screen in all capture mode sources (fullscreen, region, and
 // window) depending on the test param.
 class CaptureModeHdcpTest
-    : public CaptureModeTest,
+    : public CaptureModeTestBase,
       public ::testing::WithParamInterface<CaptureModeSource> {
  public:
   CaptureModeHdcpTest() = default;
   ~CaptureModeHdcpTest() override = default;
 
-  // CaptureModeTest:
+  // CaptureModeTestBase:
   void SetUp() override {
-    CaptureModeTest::SetUp();
+    CaptureModeTestBase::SetUp();
     window_ = CreateTestWindow(gfx::Rect(200, 200));
     // Create a child window with protected content. This simulates the real
     // behavior of a browser window hosting a page with protected content, where
@@ -3041,7 +3121,7 @@
     protection_delegate_.reset();
     protected_content_window_.reset();
     window_.reset();
-    CaptureModeTest::TearDown();
+    CaptureModeTestBase::TearDown();
   }
 
   // Enters the capture mode session.
@@ -3096,7 +3176,7 @@
       EndRecordingReason::kHdcpInterruption, 1);
 }
 
-TEST_F(CaptureModeHdcpTest, ProtectedTabBecomesActiveAfterRecordingStarts) {
+TEST_P(CaptureModeHdcpTest, ProtectedTabBecomesActiveAfterRecordingStarts) {
   // Simulate protected content being on an inactive tab.
   protection_delegate_->SetProtection(display::CONTENT_PROTECTION_METHOD_HDCP,
                                       base::DoNothing());
@@ -3213,7 +3293,7 @@
                                          CaptureModeSource::kRegion,
                                          CaptureModeSource::kWindow));
 
-TEST_F(CaptureModeTest, ClosingWindowBeingRecorded) {
+TEST_P(CaptureModeTest, ClosingWindowBeingRecorded) {
   auto window = CreateTestWindow(gfx::Rect(200, 200));
   StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
 
@@ -3248,7 +3328,7 @@
       EndRecordingReason::kDisplayOrWindowClosing, 1);
 }
 
-TEST_F(CaptureModeTest, DetachDisplayWhileWindowRecording) {
+TEST_P(CaptureModeTest, DetachDisplayWhileWindowRecording) {
   UpdateDisplay("500x400,401+0-500x400");
   // Create a window on the second display.
   auto window = CreateTestWindow(gfx::Rect(450, 20, 200, 200));
@@ -3282,7 +3362,7 @@
   EXPECT_TRUE(stop_recording_button->visible_preferred());
 }
 
-TEST_F(CaptureModeTest, SuspendWhileSessionIsActive) {
+TEST_P(CaptureModeTest, SuspendWhileSessionIsActive) {
   auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                          CaptureModeType::kVideo);
   EXPECT_TRUE(controller->IsActive());
@@ -3291,7 +3371,7 @@
   EXPECT_FALSE(controller->IsActive());
 }
 
-TEST_F(CaptureModeTest, SuspendAfterCountdownStarts) {
+TEST_P(CaptureModeTest, SuspendAfterCountdownStarts) {
   // User NORMAL_DURATION for the countdown animation so we can have predictable
   // timings.
   ui::ScopedAnimationDurationScaleMode animation_scale(
@@ -3308,7 +3388,7 @@
   EXPECT_FALSE(controller->is_recording_in_progress());
 }
 
-TEST_F(CaptureModeTest, SuspendAfterRecordingStarts) {
+TEST_P(CaptureModeTest, SuspendAfterRecordingStarts) {
   auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                          CaptureModeType::kVideo);
   StartVideoRecordingImmediately();
@@ -3322,7 +3402,7 @@
       EndRecordingReason::kImminentSuspend, 1);
 }
 
-TEST_F(CaptureModeTest, SwitchUsersWhileRecording) {
+TEST_P(CaptureModeTest, SwitchUsersWhileRecording) {
   auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                          CaptureModeType::kVideo);
   StartVideoRecordingImmediately();
@@ -3335,7 +3415,7 @@
       EndRecordingReason::kActiveUserChange, 1);
 }
 
-TEST_F(CaptureModeTest, SwitchUsersAfterCountdownStarts) {
+TEST_P(CaptureModeTest, SwitchUsersAfterCountdownStarts) {
   // User NORMAL_DURATION for the countdown animation so we can have predictable
   // timings.
   ui::ScopedAnimationDurationScaleMode animation_scale(
@@ -3351,7 +3431,7 @@
   EXPECT_FALSE(controller->is_recording_in_progress());
 }
 
-TEST_F(CaptureModeTest, ClosingDisplayBeingFullscreenRecorded) {
+TEST_P(CaptureModeTest, ClosingDisplayBeingFullscreenRecorded) {
   UpdateDisplay("500x400,401+0-500x400");
   auto roots = Shell::GetAllRootWindows();
   ASSERT_EQ(2u, roots.size());
@@ -3385,7 +3465,7 @@
       EndRecordingReason::kDisplayOrWindowClosing, 1);
 }
 
-TEST_F(CaptureModeTest, ShuttingDownWhileRecording) {
+TEST_P(CaptureModeTest, ShuttingDownWhileRecording) {
   StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
 
   auto* controller = CaptureModeController::Get();
@@ -3398,7 +3478,7 @@
 }
 
 // Tests that metrics are recorded properly for capture mode bar buttons.
-TEST_F(CaptureModeTest, CaptureModeBarButtonTypeHistograms) {
+TEST_P(CaptureModeTest, CaptureModeBarButtonTypeHistograms) {
   constexpr char kClamshellHistogram[] =
       "Ash.CaptureModeController.BarButtons.ClamshellMode";
   constexpr char kTabletHistogram[] =
@@ -3454,7 +3534,14 @@
                                      CaptureModeBarButtonType::kWindow, 1);
 }
 
-TEST_F(CaptureModeTest, CaptureSessionSwitchedModeMetric) {
+TEST_P(CaptureModeTest, CaptureSessionSwitchedModeMetric) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled || scanner_enabled) {
+    // This test fails when either Sunfish or Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   constexpr char kHistogramName[] =
       "Ash.CaptureModeController.SwitchesFromInitialCaptureMode";
   base::HistogramTester histogram_tester;
@@ -3488,7 +3575,7 @@
 }
 
 // Test that cancel recording during countdown won't cause crash.
-TEST_F(CaptureModeTest, CancelCaptureDuringCountDown) {
+TEST_P(CaptureModeTest, CancelCaptureDuringCountDown) {
   ui::ScopedAnimationDurationScaleMode animation_scale(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
   StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
@@ -3505,7 +3592,7 @@
   EXPECT_FALSE(test_api.IsVideoRecordingInProgress());
 }
 
-TEST_F(CaptureModeTest, EscDuringCountDownWhileSettingsOpen) {
+TEST_P(CaptureModeTest, EscDuringCountDownWhileSettingsOpen) {
   ui::ScopedAnimationDurationScaleMode animation_scale(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
   StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
@@ -3526,7 +3613,7 @@
 }
 
 // Tests that metrics are recorded properly for capture region adjustments.
-TEST_F(CaptureModeTest, NumberOfCaptureRegionAdjustmentsHistogram) {
+TEST_P(CaptureModeTest, NumberOfCaptureRegionAdjustmentsHistogram) {
   constexpr char kClamshellHistogram[] =
       "Ash.CaptureModeController.CaptureRegionAdjusted.ClamshellMode";
   constexpr char kTabletHistogram[] =
@@ -3607,7 +3694,7 @@
   histogram_tester.ExpectBucketCount(kTabletHistogram, 0, 1);
 }
 
-TEST_F(CaptureModeTest, ResizeRegionBoundedByDisplay) {
+TEST_P(CaptureModeTest, ResizeRegionBoundedByDisplay) {
   UpdateDisplay("800x700");
 
   auto* controller = StartImageRegionCapture();
@@ -3641,7 +3728,7 @@
   EXPECT_EQ(gfx::Rect(0, 0, 800, 700), controller->user_capture_region());
 }
 
-TEST_F(CaptureModeTest, FullscreenCapture) {
+TEST_P(CaptureModeTest, FullscreenCapture) {
   ui::ScopedAnimationDurationScaleMode animation_scale(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
   CaptureModeController* controller = StartCaptureSession(
@@ -3663,7 +3750,7 @@
 
 // Tests that there is no crash when touching the capture label widget in tablet
 // mode when capturing a window. Regression test for https://crbug.com/1152938.
-TEST_F(CaptureModeTest, TabletTouchCaptureLabelWidgetWindowMode) {
+TEST_P(CaptureModeTest, TabletTouchCaptureLabelWidgetWindowMode) {
   SwitchToTabletMode();
 
   // Enter capture window mode.
@@ -3687,7 +3774,14 @@
 
 // Tests that after rotating a display, the capture session widgets are updated
 // and the capture region is reset.
-TEST_F(CaptureModeTest, DisplayRotation) {
+TEST_P(CaptureModeTest, DisplayRotation) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled || scanner_enabled) {
+    // This test crashes when either Sunfish or Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   UpdateDisplay("1200x600");
 
   auto* controller = StartImageRegionCapture();
@@ -3718,7 +3812,7 @@
             capture_label_widget->GetWindowBoundsInScreen().CenterPoint());
 }
 
-TEST_F(CaptureModeTest, DisplayBoundsChange) {
+TEST_P(CaptureModeTest, DisplayBoundsChange) {
   UpdateDisplay("1200x600");
 
   auto* controller = StartImageRegionCapture();
@@ -3732,7 +3826,7 @@
             GetCaptureModeBarView()->GetBoundsInScreen().CenterPoint().x());
 }
 
-TEST_F(CaptureModeTest, ReenterOnSmallerDisplay) {
+TEST_P(CaptureModeTest, ReenterOnSmallerDisplay) {
   UpdateDisplay("1200x600,1201+0-700x600");
 
   // Start off with the primary display as the targeted display. Create a region
@@ -3753,7 +3847,7 @@
 }
 
 // Tests tabbing when in capture window mode.
-TEST_F(CaptureModeTest, KeyboardNavigationBasic) {
+TEST_P(CaptureModeTest, KeyboardNavigationBasic) {
   auto* controller =
       StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
 
@@ -3798,7 +3892,7 @@
 
 // Tests tabbing through windows on multiple displays when in capture window
 // mode.
-TEST_F(CaptureModeTest, KeyboardNavigationTabThroughWindowsOnMultipleDisplays) {
+TEST_P(CaptureModeTest, KeyboardNavigationTabThroughWindowsOnMultipleDisplays) {
   UpdateDisplay("800x700,801+0-800x700");
   std::vector<raw_ptr<aura::Window, VectorExperimental>> root_windows =
       Shell::GetAllRootWindows();
@@ -3909,7 +4003,7 @@
 }
 
 // Tests that a click will remove focus.
-TEST_F(CaptureModeTest, KeyboardNavigationClicksRemoveFocus) {
+TEST_P(CaptureModeTest, KeyboardNavigationClicksRemoveFocus) {
   auto* controller = StartImageRegionCapture();
   auto* event_generator = GetEventGenerator();
 
@@ -3922,7 +4016,14 @@
 }
 
 // Tests that pressing space on a focused button will activate it.
-TEST_F(CaptureModeTest, KeyboardNavigationSpaceToActivateButton) {
+TEST_P(CaptureModeTest, KeyboardNavigationSpaceToActivateButton) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled || scanner_enabled) {
+    // This test fails when either Sunfish or Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   auto* controller = StartImageRegionCapture();
   SelectRegion(gfx::Rect(200, 200));
 
@@ -3957,7 +4058,7 @@
 
 // Tests that functionality to create and adjust a region with keyboard
 // shortcuts works as intended.
-TEST_F(CaptureModeTest, KeyboardNavigationSelectRegion) {
+TEST_P(CaptureModeTest, KeyboardNavigationSelectRegion) {
   auto* controller = StartImageRegionCapture();
   auto* event_generator = GetEventGenerator();
   ASSERT_TRUE(controller->user_capture_region().IsEmpty());
@@ -4020,7 +4121,14 @@
 }
 
 // Tests behavior regarding the default region when using keyboard navigation.
-TEST_F(CaptureModeTest, KeyboardNavigationDefaultRegion) {
+TEST_P(CaptureModeTest, KeyboardNavigationDefaultRegion) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled || scanner_enabled) {
+    // This test fails when either Sunfish or Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   auto* controller = StartImageRegionCapture();
   auto* event_generator = GetEventGenerator();
   ASSERT_TRUE(controller->user_capture_region().IsEmpty());
@@ -4070,7 +4178,7 @@
   EXPECT_EQ(gfx::Rect(), controller->user_capture_region());
 }
 
-TEST_F(CaptureModeTest, A11yEnterWithNoFocus) {
+TEST_P(CaptureModeTest, A11yEnterWithNoFocus) {
   auto* controller = StartImageRegionCapture();
   SelectRegion(gfx::Rect(20, 20, 200, 200));
   CaptureModeSessionTestApi test_api(controller->capture_mode_session());
@@ -4083,7 +4191,7 @@
   EXPECT_FALSE(controller->IsActive());
 }
 
-TEST_F(CaptureModeTest, A11yEnterWithFocusOnRegionKnob) {
+TEST_P(CaptureModeTest, A11yEnterWithFocusOnRegionKnob) {
   auto* controller = StartImageRegionCapture();
   SelectRegion(gfx::Rect(20, 20, 200, 200));
   CaptureModeSessionTestApi test_api(controller->capture_mode_session());
@@ -4105,7 +4213,7 @@
   EXPECT_FALSE(controller->IsActive());
 }
 
-TEST_F(CaptureModeTest, A11yEnterWithFocusOnWindow) {
+TEST_P(CaptureModeTest, A11yEnterWithFocusOnWindow) {
   auto window = CreateTestWindow(gfx::Rect(200, 200));
   auto* controller =
       StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kImage);
@@ -4124,7 +4232,7 @@
   EXPECT_FALSE(controller->IsActive());
 }
 
-TEST_F(CaptureModeTest, A11yEnterWithFocusOnFullscreenButton) {
+TEST_P(CaptureModeTest, A11yEnterWithFocusOnFullscreenButton) {
   auto* controller = StartImageRegionCapture();
   EXPECT_EQ(controller->source(), CaptureModeSource::kRegion);
   CaptureModeSessionTestApi test_api(controller->capture_mode_session());
@@ -4160,7 +4268,19 @@
 
 // Tests that the UAF issue caused by `NotifyAccessibilityEvent` after the
 // button been destroyed has been handled without leading to a crash.
-TEST_F(CaptureModeTest, KeyboardNavigationButtonDestroyedAfterBeenActivated) {
+TEST_P(CaptureModeTest, KeyboardNavigationButtonDestroyedAfterBeenActivated) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled) {
+    // This test crashes if Sunfish is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+  if (scanner_enabled) {
+    // This test fails if Sunfish is disabled and Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   auto* controller = StartImageRegionCapture();
   SelectRegion(gfx::Rect(200, 300));
 
@@ -4191,7 +4311,14 @@
 
 // Tests that accessibility overrides are set as expected on capture mode
 // widgets.
-TEST_F(CaptureModeTest, AccessibilityFocusAnnotator) {
+TEST_P(CaptureModeTest, AccessibilityFocusAnnotator) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled || scanner_enabled) {
+    // This test fails when either Sunfish or Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   StartImageRegionCapture();
 
   // Helper that takes in a current widget and checks if the accessibility next
@@ -4230,7 +4357,7 @@
 }
 
 // Tests that a captured image is written to the clipboard.
-TEST_F(CaptureModeTest, ClipboardWrite) {
+TEST_P(CaptureModeTest, ClipboardWrite) {
   auto* clipboard = ui::Clipboard::GetForCurrentThread();
   ASSERT_NE(clipboard, nullptr);
 
@@ -4248,7 +4375,7 @@
 }
 
 // Tests the reverse tabbing behavior of the keyboard navigation.
-TEST_F(CaptureModeTest, ReverseTabbingTest) {
+TEST_P(CaptureModeTest, ReverseTabbingTest) {
   using FocusGroup = CaptureModeSessionFocusCycler::FocusGroup;
   auto* event_generator = GetEventGenerator();
   for (CaptureModeSource source :
@@ -4274,7 +4401,7 @@
 // calling `Close()` or `CloseNow()` on the widget, we get a UAF. This can
 // happen when all the windows in the window tree hierarchy gets deleted e.g.
 // when shutting down.
-TEST_F(CaptureModeTest, SettingsMenuWidgetDestruction) {
+TEST_P(CaptureModeTest, SettingsMenuWidgetDestruction) {
   CaptureModeTestApi().StartForFullscreen(true);
   ClickOnView(GetSettingsButton(), GetEventGenerator());
   auto* widget = GetCaptureModeSettingsWidget();
@@ -4292,8 +4419,18 @@
   ~CaptureModeMockTimeTest() override = default;
 };
 
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    CaptureModeMockTimeTest,
+    testing::Combine(testing::Bool(), testing::Bool()),
+    [](const testing::TestParamInfo<CaptureModeMockTimeTest::ParamType>& info) {
+      bool sunfish_enabled = std::get<0>(info.param);
+      bool scanner_enabled = std::get<1>(info.param);
+      return SunfishScannerTestName(sunfish_enabled, scanner_enabled);
+    });
+
 // Tests that the consecutive screenshots histogram is recorded properly.
-TEST_F(CaptureModeMockTimeTest, ConsecutiveScreenshotsHistograms) {
+TEST_P(CaptureModeMockTimeTest, ConsecutiveScreenshotsHistograms) {
   constexpr char kConsecutiveScreenshotsHistogram[] =
       "Ash.CaptureModeController.ConsecutiveScreenshots";
   base::HistogramTester histogram_tester;
@@ -4332,7 +4469,7 @@
 }
 
 // Tests that the user capture region will be cleared up after a period of time.
-TEST_F(CaptureModeMockTimeTest, ClearUserCaptureRegionBetweenSessions) {
+TEST_P(CaptureModeMockTimeTest, ClearUserCaptureRegionBetweenSessions) {
   UpdateDisplay("900x800");
   auto* controller = StartImageRegionCapture();
   EXPECT_EQ(gfx::Rect(), controller->user_capture_region());
@@ -4361,7 +4498,14 @@
 }
 
 // Tests that in Region mode, the capture bar hides and shows itself correctly.
-TEST_F(CaptureModeTest, CaptureBarOpacity) {
+TEST_P(CaptureModeTest, CaptureBarOpacity) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled || scanner_enabled) {
+    // This test fails when either Sunfish or Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   UpdateDisplay("800x700");
 
   auto* event_generator = GetEventGenerator();
@@ -4420,7 +4564,7 @@
   EXPECT_EQ(1.f, capture_bar_layer->GetTargetOpacity());
 }
 
-TEST_F(CaptureModeTest, CaptureBarOpacityOnHoveringOnCaptureLabel) {
+TEST_P(CaptureModeTest, CaptureBarOpacityOnHoveringOnCaptureLabel) {
   UpdateDisplay("800x700");
 
   auto* event_generator = GetEventGenerator();
@@ -4445,7 +4589,7 @@
 }
 
 // Tests that the quick action histogram is recorded properly.
-TEST_F(CaptureModeTest, QuickActionHistograms) {
+TEST_P(CaptureModeTest, QuickActionHistograms) {
   constexpr char kQuickActionHistogramName[] =
       "Ash.CaptureModeController.QuickAction";
   base::HistogramTester histogram_tester;
@@ -4497,7 +4641,7 @@
                                      CaptureQuickAction::kBacklight, 1);
 }
 
-TEST_F(CaptureModeTest, NotificationButtonOfVideoRecording) {
+TEST_P(CaptureModeTest, NotificationButtonOfVideoRecording) {
   auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                          CaptureModeType::kVideo);
   StartVideoRecordingImmediately();
@@ -4518,7 +4662,7 @@
   EXPECT_FALSE(GetPreviewNotification());
 }
 
-TEST_F(CaptureModeTest, CannotDoMultipleRecordings) {
+TEST_P(CaptureModeTest, CannotDoMultipleRecordings) {
   StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
 
   auto* controller = CaptureModeController::Get();
@@ -4558,7 +4702,7 @@
 }
 
 // Tests the basic settings menu functionality.
-TEST_F(CaptureModeTest, SettingsMenuVisibilityBasic) {
+TEST_P(CaptureModeTest, SettingsMenuVisibilityBasic) {
   auto* event_generator = GetEventGenerator();
   auto* controller = StartImageRegionCapture();
   EXPECT_TRUE(controller->IsActive());
@@ -4579,7 +4723,7 @@
 // Tests how interacting with the rest of the screen (i.e. clicking outside of
 // the bar/menu, on other buttons) affects whether the settings menu should
 // close or not.
-TEST_F(CaptureModeTest, SettingsMenuVisibilityClicking) {
+TEST_P(CaptureModeTest, SettingsMenuVisibilityClicking) {
   UpdateDisplay("800x700");
 
   auto* event_generator = GetEventGenerator();
@@ -4633,7 +4777,14 @@
 
 // Tests capture bar and settings menu visibility / opacity when capture region
 // is being or after drawn.
-TEST_F(CaptureModeTest, CaptureBarAndSettingsMenuVisibilityDrawingRegion) {
+TEST_P(CaptureModeTest, CaptureBarAndSettingsMenuVisibilityDrawingRegion) {
+  auto [sunfish_enabled, scanner_enabled] = GetParam();
+  if (sunfish_enabled || scanner_enabled) {
+    // This test crashes when either Sunfish or Scanner is enabled.
+    // TODO: b/381965299 - Fix these test failures.
+    GTEST_SKIP();
+  }
+
   UpdateDisplay("800x700");
 
   auto* event_generator = GetEventGenerator();
@@ -4734,7 +4885,7 @@
             capture_bar_layer->GetTargetOpacity());
 }
 
-TEST_F(CaptureModeTest, CaptureFolderSetting) {
+TEST_P(CaptureModeTest, CaptureFolderSetting) {
   auto* controller = CaptureModeController::Get();
   auto* test_delegate = controller->delegate_for_testing();
   const auto default_downloads_folder =
@@ -4752,7 +4903,7 @@
   EXPECT_FALSE(capture_folder.is_default_downloads_folder);
 }
 
-TEST_F(CaptureModeTest, CaptureFolderSetToDefaultDownloads) {
+TEST_P(CaptureModeTest, CaptureFolderSetToDefaultDownloads) {
   auto* controller = CaptureModeController::Get();
   auto* test_delegate = controller->delegate_for_testing();
 
@@ -4772,7 +4923,7 @@
   EXPECT_TRUE(capture_folder.is_default_downloads_folder);
 }
 
-TEST_F(CaptureModeTest, UsesDefaultFolderWithCustomFolderSet) {
+TEST_P(CaptureModeTest, UsesDefaultFolderWithCustomFolderSet) {
   auto* controller = CaptureModeController::Get();
   auto* test_delegate = controller->delegate_for_testing();
 
@@ -4800,7 +4951,7 @@
   EXPECT_FALSE(capture_folder.is_default_downloads_folder);
 }
 
-TEST_F(CaptureModeTest, CaptureFolderSetToEmptyPath) {
+TEST_P(CaptureModeTest, CaptureFolderSetToEmptyPath) {
   auto* controller = CaptureModeController::Get();
   auto* test_delegate = controller->delegate_for_testing();
 
@@ -4820,7 +4971,7 @@
   EXPECT_TRUE(capture_folder.is_default_downloads_folder);
 }
 
-TEST_F(CaptureModeTest, SimulateUserCancelingDlpWarningDialog) {
+TEST_P(CaptureModeTest, SimulateUserCancelingDlpWarningDialog) {
   auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                          CaptureModeType::kVideo);
   StartVideoRecordingImmediately();
@@ -4846,7 +4997,7 @@
 
 // Tests that `CaptureScreenshotOfGivenWindow` can take window screenshot
 // successfully and that the image size matches the window size.
-TEST_F(CaptureModeTest, InstantScreenshotForkWindow) {
+TEST_P(CaptureModeTest, InstantScreenshotForkWindow) {
   const gfx::Rect window_bounds(10, 20, 700, 500);
   std::unique_ptr<aura::Window> window(CreateTestWindow(window_bounds));
   CaptureModeController::Get()->CaptureScreenshotOfGivenWindow(window.get());
@@ -4857,7 +5008,7 @@
 
 // Tests the capture mode behavior in the default capture mode session and
 // during video recording.
-TEST_F(CaptureModeTest, CaptureModeDefaultBehavior) {
+TEST_P(CaptureModeTest, CaptureModeDefaultBehavior) {
   CaptureModeController* controller = StartCaptureSession(
       CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
   ASSERT_TRUE(controller->IsActive());
@@ -4909,7 +5060,7 @@
 // 'Ctrl + Shift + Overview' with `kImage` as the default type and `kRegion` as
 // the default source. And the screen recording can be ended with the keyboard
 // shortcut 'Search + Shift + X'.
-TEST_F(CaptureModeTest, KeyboardShortcutTest) {
+TEST_P(CaptureModeTest, KeyboardShortcutTest) {
   base::HistogramTester histogram_tester;
   histogram_tester.ExpectBucketCount(
       kEndRecordingReasonInClamshellHistogramName,
@@ -4936,7 +5087,7 @@
       EndRecordingReason::kKeyboardShortcut, 1);
 }
 
-TEST_F(CaptureModeTest, StopRecordingButtonTrayAccessibleName) {
+TEST_P(CaptureModeTest, StopRecordingButtonTrayAccessibleName) {
   ash::CaptureModeTestApi test_api;
   test_api.StartForFullscreen(/*for_video=*/true);
   test_api.PerformCapture();
@@ -5085,9 +5236,20 @@
   std::unique_ptr<TestVideoCaptureOverlay> fake_overlay_;
 };
 
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    CaptureModeCursorOverlayTest,
+    testing::Combine(testing::Bool(), testing::Bool()),
+    [](const testing::TestParamInfo<CaptureModeCursorOverlayTest::ParamType>&
+           info) {
+      bool sunfish_enabled = std::get<0>(info.param);
+      bool scanner_enabled = std::get<1>(info.param);
+      return SunfishScannerTestName(sunfish_enabled, scanner_enabled);
+    });
+
 }  // namespace
 
-TEST_F(CaptureModeCursorOverlayTest, TabletModeHidesCursorOverlay) {
+TEST_P(CaptureModeCursorOverlayTest, TabletModeHidesCursorOverlay) {
   StartRecordingAndSetupFakeOverlay(CaptureModeSource::kFullscreen);
   EXPECT_FALSE(fake_overlay()->IsHidden());
 
@@ -5104,7 +5266,7 @@
 
 // Tests that the cursor is hidden while taking a screenshot in tablet mode and
 // remains hidden afterward.
-TEST_F(CaptureModeCursorOverlayTest, TabletModeHidesCursor) {
+TEST_P(CaptureModeCursorOverlayTest, TabletModeHidesCursor) {
   // Enter tablet mode.
   SwitchToTabletMode();
 
@@ -5129,7 +5291,7 @@
 
 // Tests that a cursor is hidden while taking a fullscreen screenshot
 // (crbug.com/1186652).
-TEST_F(CaptureModeCursorOverlayTest, CursorInFullscreenScreenshot) {
+TEST_P(CaptureModeCursorOverlayTest, CursorInFullscreenScreenshot) {
   auto* cursor_manager = Shell::Get()->cursor_manager();
   EXPECT_FALSE(cursor_manager->IsCursorLocked());
   CaptureModeController* controller = StartCaptureSession(
@@ -5150,7 +5312,7 @@
 
 // Tests that a cursor is hidden while taking a region screenshot
 // (crbug.com/1186652).
-TEST_F(CaptureModeCursorOverlayTest, CursorInPartialRegionScreenshot) {
+TEST_P(CaptureModeCursorOverlayTest, CursorInPartialRegionScreenshot) {
   // Use a set display size as we will be choosing points in this test.
   UpdateDisplay("800x700");
 
@@ -5174,7 +5336,7 @@
   CaptureScreenshotAndCheckCursorVisibility(controller);
 }
 
-TEST_F(CaptureModeCursorOverlayTest, SoftwareCursorInitiallyEnabled) {
+TEST_P(CaptureModeCursorOverlayTest, SoftwareCursorInitiallyEnabled) {
   // The software cursor is enabled before recording starts.
   SetDockedMagnifierEnabled(true);
   EXPECT_TRUE(IsCursorCompositingEnabled());
@@ -5184,7 +5346,7 @@
   EXPECT_TRUE(fake_overlay()->IsHidden());
 }
 
-TEST_F(CaptureModeCursorOverlayTest, SoftwareCursorInFullscreenRecording) {
+TEST_P(CaptureModeCursorOverlayTest, SoftwareCursorInFullscreenRecording) {
   StartRecordingAndSetupFakeOverlay(CaptureModeSource::kFullscreen);
   EXPECT_FALSE(fake_overlay()->IsHidden());
 
@@ -5201,7 +5363,7 @@
   EXPECT_FALSE(fake_overlay()->IsHidden());
 }
 
-TEST_F(CaptureModeCursorOverlayTest, SoftwareCursorInPartialRegionRecording) {
+TEST_P(CaptureModeCursorOverlayTest, SoftwareCursorInPartialRegionRecording) {
   CaptureModeController::Get()->SetUserCaptureRegion(gfx::Rect(20, 20),
                                                      /*by_user=*/true);
   StartRecordingAndSetupFakeOverlay(CaptureModeSource::kRegion);
@@ -5214,7 +5376,7 @@
   EXPECT_TRUE(fake_overlay()->IsHidden());
 }
 
-TEST_F(CaptureModeCursorOverlayTest, SoftwareCursorInWindowRecording) {
+TEST_P(CaptureModeCursorOverlayTest, SoftwareCursorInWindowRecording) {
   StartRecordingAndSetupFakeOverlay(CaptureModeSource::kWindow);
   EXPECT_FALSE(fake_overlay()->IsHidden());
 
@@ -5228,7 +5390,7 @@
   EXPECT_FALSE(fake_overlay()->IsHidden());
 }
 
-TEST_F(CaptureModeCursorOverlayTest, OverlayHidesWhenOutOfBounds) {
+TEST_P(CaptureModeCursorOverlayTest, OverlayHidesWhenOutOfBounds) {
   StartRecordingAndSetupFakeOverlay(CaptureModeSource::kWindow);
   EXPECT_FALSE(fake_overlay()->IsHidden());
 
@@ -5261,7 +5423,7 @@
 
 }  // namespace
 
-TEST_F(CaptureModeCursorOverlayTest, OverlayWhenCursorIsHiddenOrFails) {
+TEST_P(CaptureModeCursorOverlayTest, OverlayWhenCursorIsHiddenOrFails) {
   StartRecordingAndSetupFakeOverlay(CaptureModeSource::kWindow);
   EXPECT_FALSE(fake_overlay()->IsHidden());
 
@@ -5314,7 +5476,7 @@
 
 // Verifies that the cursor overlay bounds calculation takes into account the
 // cursor image scale factor. https://crbug.com/1222494.
-TEST_F(CaptureModeCursorOverlayTest, OverlayBoundsAccountForCursorScaleFactor) {
+TEST_P(CaptureModeCursorOverlayTest, OverlayBoundsAccountForCursorScaleFactor) {
   UpdateDisplay("500x400");
   StartRecordingAndSetupFakeOverlay(CaptureModeSource::kFullscreen);
   EXPECT_FALSE(fake_overlay()->IsHidden());
@@ -5388,7 +5550,7 @@
 
 }  // namespace
 
-class ProjectorCaptureModeIntegrationTests : public CaptureModeTest {
+class ProjectorCaptureModeIntegrationTests : public CaptureModeTestBase {
  public:
   ProjectorCaptureModeIntegrationTests() = default;
   ~ProjectorCaptureModeIntegrationTests() override = default;
@@ -5400,9 +5562,9 @@
   }
   aura::Window* window() const { return window_.get(); }
 
-  // CaptureModeTest:
+  // CaptureModeTestBase:
   void SetUp() override {
-    CaptureModeTest::SetUp();
+    CaptureModeTestBase::SetUp();
     projector_helper_.SetUp();
     window_ = CreateTestWindow(gfx::Rect(20, 30, 200, 200));
     CaptureModeController::Get()->SetUserCaptureRegion(kUserRegion,
@@ -5411,7 +5573,7 @@
 
   void TearDown() override {
     window_.reset();
-    CaptureModeTest::TearDown();
+    CaptureModeTestBase::TearDown();
   }
 
   void StartProjectorModeSession() {
@@ -6183,9 +6345,7 @@
                                          CaptureModeSource::kRegion,
                                          CaptureModeSource::kWindow));
 
-class AnnotatorCaptureModeIntegrationTests
-    : public CaptureModeTest,
-      public ::testing::WithParamInterface<CaptureModeSource> {
+class AnnotatorCaptureModeIntegrationTests : public CaptureModeTestBase {
  public:
   AnnotatorCaptureModeIntegrationTests() = default;
   ~AnnotatorCaptureModeIntegrationTests() override = default;
@@ -6194,9 +6354,9 @@
 
   aura::Window* window() const { return window_.get(); }
 
-  // CaptureModeTest:
+  // CaptureModeTestBase:
   void SetUp() override {
-    CaptureModeTest::SetUp();
+    CaptureModeTestBase::SetUp();
     annotator_helper_.SetUp();
     window_ = CreateTestWindow(gfx::Rect(20, 30, 200, 200));
     CaptureModeController::Get()->SetUserCaptureRegion(kUserRegion,
@@ -6205,7 +6365,7 @@
 
   void TearDown() override {
     window_.reset();
-    CaptureModeTest::TearDown();
+    CaptureModeTestBase::TearDown();
   }
 
   void StartRecordingFromSource(CaptureModeSource source) {
@@ -6235,6 +6395,10 @@
   base::HistogramTester histogram_tester_;
 };
 
+class AnnotatorCaptureModeIntegrationTestsWithSource
+    : public AnnotatorCaptureModeIntegrationTests,
+      public ::testing::WithParamInterface<CaptureModeSource> {};
+
 TEST_F(AnnotatorCaptureModeIntegrationTests, AnnotationsOverlayWidget) {
   StartRecordingFromSource(CaptureModeSource::kFullscreen);
 
@@ -6443,7 +6607,8 @@
   }
 }
 
-TEST_P(AnnotatorCaptureModeIntegrationTests, AnnotationsOverlayWidgetBounds) {
+TEST_P(AnnotatorCaptureModeIntegrationTestsWithSource,
+       AnnotationsOverlayWidgetBounds) {
   const auto capture_source = GetParam();
   StartRecordingFromSource(capture_source);
   CaptureModeTestApi test_api;
@@ -6454,7 +6619,7 @@
   VerifyOverlayWindow(overlay_window, capture_source, kUserRegion);
 }
 
-TEST_P(AnnotatorCaptureModeIntegrationTests,
+TEST_P(AnnotatorCaptureModeIntegrationTestsWithSource,
        AnnotationsOverlayWidgetBoundsSecondDisplay) {
   UpdateDisplay("800x700,801+0-800x700");
   const gfx::Point point_in_second_display = gfx::Point(1000, 500);
@@ -6479,7 +6644,7 @@
 }
 
 INSTANTIATE_TEST_SUITE_P(All,
-                         AnnotatorCaptureModeIntegrationTests,
+                         AnnotatorCaptureModeIntegrationTestsWithSource,
                          testing::Values(CaptureModeSource::kFullscreen,
                                          CaptureModeSource::kRegion,
                                          CaptureModeSource::kWindow));
@@ -6488,20 +6653,20 @@
 // CaptureModeSettingsTest:
 
 // Test fixture for CaptureMode settings view.
-class CaptureModeSettingsTest : public CaptureModeTest {
+class CaptureModeSettingsTest : public CaptureModeTestBase {
  public:
   CaptureModeSettingsTest() = default;
   ~CaptureModeSettingsTest() override = default;
 
-  // CaptureModeTest:
+  // CaptureModeTestBase:
   void SetUp() override {
-    CaptureModeTest::SetUp();
+    CaptureModeTestBase::SetUp();
     FakeFolderSelectionDialogFactory::Start();
   }
 
   void TearDown() override {
     FakeFolderSelectionDialogFactory::Stop();
-    CaptureModeTest::TearDown();
+    CaptureModeTestBase::TearDown();
   }
 
   CaptureModeSettingsView* GetCaptureModeSettingsView() const {
diff --git a/ash/capture_mode/search_results_panel.cc b/ash/capture_mode/search_results_panel.cc
index 2e8b139..ae50964 100644
--- a/ash/capture_mode/search_results_panel.cc
+++ b/ash/capture_mode/search_results_panel.cc
@@ -126,7 +126,7 @@
     // Resize the image to fit in the searchbox, keeping the same aspect ratio.
     const int target_height = height();
     const int target_width = (image.width() * target_height) / image.height();
-    image_view_->SetImage(image);
+    image_view_->SetImage(ui::ImageModel::FromImageSkia(image));
     image_view_->SetImageSize(gfx::Size(target_width, target_height));
   }
 
diff --git a/ash/capture_mode/stop_recording_button_tray.cc b/ash/capture_mode/stop_recording_button_tray.cc
index 5386d87..b008135 100644
--- a/ash/capture_mode/stop_recording_button_tray.cc
+++ b/ash/capture_mode/stop_recording_button_tray.cc
@@ -41,17 +41,12 @@
   image_view_->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
   image_view_->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
   image_view_->SetPreferredSize(gfx::Size(kTrayItemSize, kTrayItemSize));
+  image_view_->SetImage(ui::ImageModel::FromVectorIcon(
+      kCaptureModeCircleStopIcon, kColorAshIconColorAlert));
 }
 
 StopRecordingButtonTray::~StopRecordingButtonTray() = default;
 
-void StopRecordingButtonTray::OnThemeChanged() {
-  TrayBackgroundView::OnThemeChanged();
-  image_view_->SetImage(gfx::CreateVectorIcon(
-      kCaptureModeCircleStopIcon,
-      GetColorProvider()->GetColor(kColorAshIconColorAlert)));
-}
-
 BEGIN_METADATA(StopRecordingButtonTray)
 END_METADATA
 
diff --git a/ash/capture_mode/stop_recording_button_tray.h b/ash/capture_mode/stop_recording_button_tray.h
index 0dc7d14b..bce04d96 100644
--- a/ash/capture_mode/stop_recording_button_tray.h
+++ b/ash/capture_mode/stop_recording_button_tray.h
@@ -41,7 +41,6 @@
   void UpdateTrayItemColor(bool is_active) override {}
   void HandleLocaleChange() override {}
   void HideBubbleWithView(const TrayBubbleView* bubble_view) override {}
-  void OnThemeChanged() override;
   void HideBubble(const TrayBubbleView* bubble_view) override {}
 
   // Image view of the stop recording icon.
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index d3369c30..7ae7a78 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -2218,12 +2218,6 @@
              "OsFeedbackDialog",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Whether the DNS dialog in should be deprecated in Security and Privacy
-// Settings page when the user toggles off the DNS button.
-BASE_FEATURE(kOsSettingsDeprecateDnsDialog,
-             "OsSettingsDeprecateDnsDialog",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables Jelly colors and components to appear in the Parent Access Widget
 // if jelly-colors is also enabled.
 BASE_FEATURE(kParentAccessJelly,
@@ -4165,10 +4159,6 @@
   return base::FeatureList::IsEnabled(kOsFeedbackDialog);
 }
 
-bool IsOsSettingsDeprecateDnsDialogEnabled() {
-  return base::FeatureList::IsEnabled(kOsSettingsDeprecateDnsDialog);
-}
-
 bool IsOsSyncConsentRevampEnabled() {
   return base::FeatureList::IsEnabled(kOsSyncConsentRevamp);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index baf594dd..9a96268 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -722,7 +722,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kOsFeedbackDialog);
 COMPONENT_EXPORT(ASH_CONSTANTS)
-BASE_DECLARE_FEATURE(kOsSettingsDeprecateDnsDialog);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOsSyncConsentRevamp);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kParentAccessJelly);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -1257,7 +1256,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeDisplaySizeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeSplitModifierKeyboardInfoEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeInputMethodsEnabled();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOsSettingsDeprecateDnsDialogEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOsSyncConsentRevampEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsParentAccessJellyEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPasswordlessGaiaEnabledForConsumers();
diff --git a/ash/display/display_alignment_indicator.cc b/ash/display/display_alignment_indicator.cc
index 66a5926..2532aab 100644
--- a/ash/display/display_alignment_indicator.cc
+++ b/ash/display/display_alignment_indicator.cc
@@ -242,7 +242,7 @@
 
     text_label_->SetText(text);
 
-    icon_->SetImage(arrow_image_);
+    icon_->SetImage(ui::ImageModel::FromImageSkia(arrow_image_));
   }
 
   IndicatorPillView(const IndicatorPillView&) = delete;
@@ -303,20 +303,23 @@
 
     switch (position) {
       case IndicatorPosition::kLeft:
-        icon_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
-            arrow_image_, SkBitmapOperations::ROTATION_180_CW));
+        icon_->SetImage(ui::ImageModel::FromImageSkia(
+            gfx::ImageSkiaOperations::CreateRotatedImage(
+                arrow_image_, SkBitmapOperations::ROTATION_180_CW)));
         return;
       case IndicatorPosition::kRight:
         // |arrow_image_| points to right by default; no rotation required.
-        icon_->SetImage(arrow_image_);
+        icon_->SetImage(ui::ImageModel::FromImageSkia(arrow_image_));
         return;
       case IndicatorPosition::kTop:
-        icon_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
-            arrow_image_, SkBitmapOperations::ROTATION_270_CW));
+        icon_->SetImage(ui::ImageModel::FromImageSkia(
+            gfx::ImageSkiaOperations::CreateRotatedImage(
+                arrow_image_, SkBitmapOperations::ROTATION_270_CW)));
         return;
       case IndicatorPosition::kBottom:
-        icon_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
-            arrow_image_, SkBitmapOperations::ROTATION_90_CW));
+        icon_->SetImage(ui::ImageModel::FromImageSkia(
+            gfx::ImageSkiaOperations::CreateRotatedImage(
+                arrow_image_, SkBitmapOperations::ROTATION_90_CW)));
         return;
     }
   }
diff --git a/ash/lobster/lobster_controller.cc b/ash/lobster/lobster_controller.cc
index a2e2b90..221ec7bc5 100644
--- a/ash/lobster/lobster_controller.cc
+++ b/ash/lobster/lobster_controller.cc
@@ -21,11 +21,13 @@
 
 LobsterController::Trigger::Trigger(std::unique_ptr<LobsterClient> client,
                                     LobsterEntryPoint entry_point,
-                                    LobsterMode mode)
+                                    LobsterMode mode,
+                                    const gfx::Rect& caret_bounds)
     : client_(std::move(client)),
       state_(State::kReady),
       entry_point_(entry_point),
-      mode_(mode) {}
+      mode_(mode),
+      caret_bounds_(caret_bounds) {}
 
 LobsterController::Trigger::~Trigger() = default;
 
@@ -47,7 +49,7 @@
   }
 
   controller->StartSession(std::move(client_), std::move(query), entry_point_,
-                           mode_);
+                           mode_, caret_bounds_);
 }
 
 LobsterController::LobsterController() = default;
@@ -60,7 +62,8 @@
 
 std::unique_ptr<LobsterController::Trigger> LobsterController::CreateTrigger(
     LobsterEntryPoint entry_point,
-    bool support_image_insertion) {
+    bool support_image_insertion,
+    const gfx::Rect& caret_bounds) {
   if (client_factory_ == nullptr) {
     return nullptr;
   }
@@ -74,14 +77,16 @@
              ? std::make_unique<Trigger>(std::move(client), entry_point,
                                          support_image_insertion
                                              ? LobsterMode::kInsert
-                                             : LobsterMode::kDownload)
+                                             : LobsterMode::kDownload,
+                                         caret_bounds)
              : nullptr;
 }
 
 void LobsterController::StartSession(std::unique_ptr<LobsterClient> client,
                                      std::optional<std::string> query,
                                      LobsterEntryPoint entry_point,
-                                     LobsterMode mode) {
+                                     LobsterMode mode,
+                                     const gfx::Rect& caret_bounds) {
   // Before creating a new session, we need to inform the lobster client and
   // lobster session to clear their pointer to the session that is about to be
   // destroyed. This is to prevent them from holding a dangling pointer to the
@@ -93,7 +98,7 @@
   active_session_ =
       std::make_unique<LobsterSessionImpl>(std::move(client), entry_point);
   lobster_client_ptr->SetActiveSession(active_session_.get());
-  active_session_->LoadUI(query, mode);
+  active_session_->LoadUI(query, mode, caret_bounds);
 }
 
 }  // namespace ash
diff --git a/ash/lobster/lobster_controller.h b/ash/lobster/lobster_controller.h
index e8dae62..62a8acd3 100644
--- a/ash/lobster/lobster_controller.h
+++ b/ash/lobster/lobster_controller.h
@@ -27,7 +27,8 @@
    public:
     explicit Trigger(std::unique_ptr<LobsterClient> client,
                      LobsterEntryPoint entry_point,
-                     LobsterMode mode);
+                     LobsterMode mode,
+                     const gfx::Rect& caret_bounds);
     ~Trigger();
 
     void Fire(std::optional<std::string> query);
@@ -46,6 +47,8 @@
     LobsterEntryPoint entry_point_;
 
     LobsterMode mode_;
+
+    gfx::Rect caret_bounds_;
   };
 
   LobsterController();
@@ -54,7 +57,8 @@
   void SetClientFactory(LobsterClientFactory* client_factory);
 
   std::unique_ptr<Trigger> CreateTrigger(LobsterEntryPoint entry_point,
-                                         bool support_image_insertion);
+                                         bool support_image_insertion,
+                                         const gfx::Rect& caret_bounds);
 
  private:
   friend class Trigger;
@@ -62,7 +66,8 @@
   void StartSession(std::unique_ptr<LobsterClient> client,
                     std::optional<std::string> query,
                     LobsterEntryPoint entry_point,
-                    LobsterMode mode);
+                    LobsterMode mode,
+                    const gfx::Rect& caret_bounds);
 
   // Not owned by this class.
   raw_ptr<LobsterClientFactory> client_factory_;
diff --git a/ash/lobster/lobster_session_impl.cc b/ash/lobster/lobster_session_impl.cc
index 74ec157..e21ea65 100644
--- a/ash/lobster/lobster_session_impl.cc
+++ b/ash/lobster/lobster_session_impl.cc
@@ -265,8 +265,9 @@
 }
 
 void LobsterSessionImpl::LoadUI(std::optional<std::string> query,
-                                LobsterMode mode) {
-  client_->LoadUI(query, mode);
+                                LobsterMode mode,
+                                const gfx::Rect& caret_bounds) {
+  client_->LoadUI(query, mode, caret_bounds);
 }
 
 void LobsterSessionImpl::ShowUI() {
diff --git a/ash/lobster/lobster_session_impl.h b/ash/lobster/lobster_session_impl.h
index c993fa3..507490f 100644
--- a/ash/lobster/lobster_session_impl.h
+++ b/ash/lobster/lobster_session_impl.h
@@ -54,7 +54,9 @@
                        LobsterPreviewFeedbackCallback) override;
   bool SubmitFeedback(int candidate_id,
                       const std::string& description) override;
-  void LoadUI(std::optional<std::string> query, LobsterMode mode) override;
+  void LoadUI(std::optional<std::string> query,
+              LobsterMode mode,
+              const gfx::Rect& caret_bounds) override;
   void ShowUI() override;
   void CloseUI() override;
   void RecordWebUIMetricEvent(ash::LobsterMetricState metric_event) override;
diff --git a/ash/lobster/lobster_session_impl_unittest.cc b/ash/lobster/lobster_session_impl_unittest.cc
index a0d978c..81f50d9 100644
--- a/ash/lobster/lobster_session_impl_unittest.cc
+++ b/ash/lobster/lobster_session_impl_unittest.cc
@@ -77,7 +77,9 @@
               (override));
   MOCK_METHOD(void,
               LoadUI,
-              (std::optional<std::string> query, LobsterMode mode),
+              (std::optional<std::string> query,
+               LobsterMode mode,
+               const gfx::Rect& caret_bounds),
               (override));
   MOCK_METHOD(void, ShowUI, (), (override));
   MOCK_METHOD(void, CloseUI, (), (override));
diff --git a/ash/public/cpp/lobster/lobster_client.h b/ash/public/cpp/lobster/lobster_client.h
index dbe568a6..95f89af5 100644
--- a/ash/public/cpp/lobster/lobster_client.h
+++ b/ash/public/cpp/lobster/lobster_client.h
@@ -35,7 +35,9 @@
                               const std::string& model_version,
                               const std::string& description,
                               const std::string& image_bytes) = 0;
-  virtual void LoadUI(std::optional<std::string> query, LobsterMode mode) = 0;
+  virtual void LoadUI(std::optional<std::string> query,
+                      LobsterMode mode,
+                      const gfx::Rect& caret_bounds) = 0;
   virtual void ShowUI() = 0;
   virtual void CloseUI() = 0;
 };
diff --git a/ash/public/cpp/lobster/lobster_session.h b/ash/public/cpp/lobster/lobster_session.h
index e7b22974..655000def 100644
--- a/ash/public/cpp/lobster/lobster_session.h
+++ b/ash/public/cpp/lobster/lobster_session.h
@@ -17,6 +17,10 @@
 #include "base/functional/callback.h"
 #include "url/gurl.h"
 
+namespace gfx {
+class Rect;
+}
+
 namespace ash {
 
 class ASH_PUBLIC_EXPORT LobsterSession {
@@ -42,7 +46,9 @@
   virtual bool SubmitFeedback(int candidate_id,
                               const std::string& description) = 0;
 
-  virtual void LoadUI(std::optional<std::string> query, LobsterMode mode) = 0;
+  virtual void LoadUI(std::optional<std::string> query,
+                      LobsterMode mode,
+                      const gfx::Rect& caret_bounds) = 0;
   virtual void ShowUI() = 0;
   virtual void CloseUI() = 0;
   virtual void RecordWebUIMetricEvent(LobsterMetricState metric_state) = 0;
diff --git a/ash/quick_insert/mock_quick_insert_client.h b/ash/quick_insert/mock_quick_insert_client.h
index 941adf15..af23c9c 100644
--- a/ash/quick_insert/mock_quick_insert_client.h
+++ b/ash/quick_insert/mock_quick_insert_client.h
@@ -39,7 +39,7 @@
   MOCK_METHOD(ShowEditorCallback, CacheEditorContext, (), (override));
   MOCK_METHOD(ShowLobsterCallback,
               CacheLobsterContext,
-              (bool support_image_insertion),
+              (bool support_image_insertion, const gfx::Rect& caret_bounds),
               (override));
   MOCK_METHOD(void,
               GetSuggestedEditorResults,
diff --git a/ash/quick_insert/quick_insert_client.h b/ash/quick_insert/quick_insert_client.h
index 40527190..d58893c 100644
--- a/ash/quick_insert/quick_insert_client.h
+++ b/ash/quick_insert/quick_insert_client.h
@@ -28,6 +28,7 @@
 }
 
 namespace gfx {
+class Rect;
 class Size;
 }
 
@@ -82,7 +83,8 @@
   virtual ShowEditorCallback CacheEditorContext() = 0;
 
   virtual ShowLobsterCallback CacheLobsterContext(
-      bool support_image_insertion) = 0;
+      bool support_image_insertion,
+      const gfx::Rect& caret_bounds) = 0;
 
   virtual void GetSuggestedEditorResults(
       SuggestedEditorResultsCallback callback) = 0;
diff --git a/ash/quick_insert/quick_insert_controller.cc b/ash/quick_insert/quick_insert_controller.cc
index 438a3034..340100e 100644
--- a/ash/quick_insert/quick_insert_controller.cc
+++ b/ash/quick_insert/quick_insert_controller.cc
@@ -623,7 +623,8 @@
   show_editor_callback_ = client_->CacheEditorContext();
   show_lobster_callback_ = client_->CacheLobsterContext(
       /*support_image_insertion=*/focused_text_input_client &&
-      focused_text_input_client->CanInsertImage());
+          focused_text_input_client->CanInsertImage(),
+      /*caret_bounds=*/GetCaretBounds());
   input_method::ImeKeyboard& keyboard = GetImeKeyboard();
 
   if (focused_text_input_client &&
diff --git a/ash/system/video_conference/bubble/title_view.cc b/ash/system/video_conference/bubble/title_view.cc
index 5a71b2f..21f25ec 100644
--- a/ash/system/video_conference/bubble/title_view.cc
+++ b/ash/system/video_conference/bubble/title_view.cc
@@ -108,8 +108,9 @@
   auto* background_layer = background_view_->layer();
   background_layer->SetRoundedCornerRadius(gfx::RoundedCornersF(16));
 
-  AddChildView(std::make_unique<MicTestButtonContainer>(base::BindRepeating(
-      &MicTestButton::OnMicTestButtonClicked, base::Unretained(this))));
+  button_container_ =
+      AddChildView(std::make_unique<MicTestButtonContainer>(base::BindRepeating(
+          &MicTestButton::OnMicTestButtonClicked, base::Unretained(this))));
 }
 
 void MicTestButton::OnThemeChanged() {
@@ -120,6 +121,7 @@
           ? cros_tokens::kCrosSysSystemPrimaryContainer
           : cros_tokens::kCrosSysSystemOnBase);
   background_view_->layer()->SetColor(color);
+  button_container_->OnThemeChanged();
 }
 
 void MicTestButton::OnMicTestButtonClicked(const ui::Event& event) {
@@ -220,6 +222,7 @@
                       : cros_tokens::kCrosSysOnSurface;
   sidetone_icon_->SetImage(
       ui::ImageModel::FromVectorIcon(kVideoConferenceSidetoneIcon, color_id));
+  mic_indicator_->OnThemeChanged();
 }
 
 MicTestButtonContainer::~MicTestButtonContainer() = default;
diff --git a/ash/system/video_conference/bubble/title_view.h b/ash/system/video_conference/bubble/title_view.h
index 8a77a75..ba9c351 100644
--- a/ash/system/video_conference/bubble/title_view.h
+++ b/ash/system/video_conference/bubble/title_view.h
@@ -62,10 +62,12 @@
   void OnMicTestButtonClicked(const ui::Event& event);
   void CloseSidetoneBubble();
   void ShowSidetoneBubble(const bool supported);
+
   // views::View
   void OnThemeChanged() override;
 
   raw_ptr<views::View> background_view_ = nullptr;
+  raw_ptr<MicTestButtonContainer> button_container_ = nullptr;
 };
 
 }  // namespace ash::video_conference
diff --git a/base/compiler_specific.h b/base/compiler_specific.h
index 2e2c21f..f484a2f 100644
--- a/base/compiler_specific.h
+++ b/base/compiler_specific.h
@@ -59,7 +59,9 @@
 //     // This body will not be inlined into callers.
 //   }
 // ```
-#if __has_cpp_attribute(gnu::noinline)
+#if __has_cpp_attribute(clang::noinline)
+#define NOINLINE [[clang::noinline]]
+#elif __has_cpp_attribute(gnu::noinline)
 #define NOINLINE [[gnu::noinline]]
 #elif __has_cpp_attribute(msvc::noinline)
 #define NOINLINE [[msvc::noinline]]
@@ -67,6 +69,24 @@
 #define NOINLINE
 #endif
 
+// Annotates a call site indicating that the callee should not be inlined.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#noinline
+//
+// Usage:
+// ```
+//   void Func() {
+//      // This specific call to `DoSomething` should not be inlined.
+//      NOINLINE_CALL DoSomething();
+//   }
+// ```
+#if __has_cpp_attribute(clang::noinline)
+#define NOINLINE_CALL [[clang::noinline]]
+#else
+#define NOINLINE_CALL
+#endif
+
 // Annotates a function indicating it should not be optimized.
 //
 // See also:
@@ -102,7 +122,9 @@
 // Since `ALWAYS_INLINE` is performance-oriented but can hamper debugging,
 // ignore it in debug mode.
 #if defined(NDEBUG)
-#if __has_cpp_attribute(gnu::always_inline)
+#if __has_cpp_attribute(clang::always_inline)
+#define ALWAYS_INLINE [[clang::always_inline]] inline
+#elif __has_cpp_attribute(gnu::always_inline)
 #define ALWAYS_INLINE [[gnu::always_inline]] inline
 #elif defined(COMPILER_MSVC)
 #define ALWAYS_INLINE __forceinline
@@ -112,6 +134,30 @@
 #define ALWAYS_INLINE inline
 #endif
 
+// Annotates a call site indicating the calee should always be inlined.
+//
+// See also:
+//   https://clang.llvm.org/docs/AttributeReference.html#always-inline-force-inline
+//
+// Usage:
+// ```
+//   void Func() {
+//     // This specific call will be inlined if possible.
+//     ALWAYS_INLINE_CALL DoSomething();
+//   }
+// ```
+//
+// Since `ALWAYS_INLINE_CALL` is performance-oriented but can hamper debugging,
+// ignore it in debug mode.
+#if defined(NDEBUG)
+#if __has_cpp_attribute(clang::always_inline)
+#define ALWAYS_INLINE_CALL [[clang::always_inline]]
+#endif
+#endif
+#if !defined(ALWAYS_INLINE_CALL)
+#define ALWAYS_INLINE_CALL
+#endif
+
 // Annotates a function indicating it should never be tail called. Useful to
 // make sure callers of the annotated function are never omitted from call
 // stacks. Often useful with `NOINLINE` to make sure the function itself is also
diff --git a/base/tracing/stdlib/chrome/chrome_scrolls.sql b/base/tracing/stdlib/chrome/chrome_scrolls.sql
index 58f1b11..2fd228c0 100644
--- a/base/tracing/stdlib/chrome/chrome_scrolls.sql
+++ b/base/tracing/stdlib/chrome/chrome_scrolls.sql
@@ -218,6 +218,16 @@
     -- Timestamps
     generation_ts,
     touch_move_received_ts,
+    -- TODO(b:385160424): this is a workaround for cases when
+    -- generation time is later than the input time.
+    MAX(
+      IIF(
+        is_inertial AND touch_move_received_ts IS NULL,
+        scroll_update_created_ts,
+        touch_move_received_ts
+      ),
+      generation_ts)
+    AS browser_main_received_ts,
     -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
     -- Ids
     scroll_update_created_slice_id,
@@ -254,16 +264,12 @@
   generation_ts,
   -- Flings don't have a touch move event so make GenerationToBrowserMain span
   -- all the way to the creation of the gesture scroll update.
-  IIF(
-    is_inertial AND touch_move_received_ts IS NULL,
-    scroll_update_created_ts,
-    touch_move_received_ts
-  ) - generation_ts AS generation_to_browser_main_dur,
+  browser_main_received_ts - generation_ts AS generation_to_browser_main_dur,
   -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
   browser_utid,
   touch_move_received_slice_id,
   touch_move_received_ts,
-  scroll_update_created_ts - touch_move_received_ts
+  scroll_update_created_ts - MAX(touch_move_received_ts, generation_ts)
     AS touch_move_processing_dur,
   -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
   -- On `browser_utid`.
@@ -275,8 +281,11 @@
   -- No applicable utid (duration between two threads).
   -- No applicable slice id (duration between two threads).
   scroll_update_created_end_ts,
-  -- TODO(b:380868337): This is sometimes negative; check/fix this.
-  compositor_dispatch_ts - scroll_update_created_end_ts
+  -- TODO(b:385161677): use the start
+  -- of the STEP_SEND_DISPATCH_EVENT_MOJO_MESSAGE step
+  -- instead of scroll_update_created_end_ts.
+  MAX(compositor_dispatch_ts, scroll_update_created_end_ts)
+    - scroll_update_created_end_ts
     AS browser_to_compositor_delay_dur,
   -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
   compositor_utid,
@@ -317,6 +326,13 @@
     AS compositor_resample_task_ts,
   compositor_resample_step.ts AS compositor_resample_ts,
   -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+  compositor_receive_begin_frame_step.id
+    AS compositor_receive_begin_frame_slice_id,
+  compositor_receive_begin_frame_step.task_start_time_ts
+    AS compositor_receive_begin_frame_task_ts,
+  compositor_receive_begin_frame_step.ts
+    AS compositor_receive_begin_frame_ts,
+  --
   compositor_generate_compositor_frame_step.id
     AS compositor_generate_compositor_frame_slice_id,
   compositor_generate_compositor_frame_step.task_start_time_ts
@@ -375,6 +391,15 @@
 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 LEFT JOIN
   chrome_graphics_pipeline_surface_frame_steps
+    compositor_receive_begin_frame_step
+  ON
+    compositor_receive_begin_frame_step.surface_frame_trace_id
+      = refs.surface_frame_id
+    AND compositor_receive_begin_frame_step.step
+      = 'STEP_RECEIVE_BEGIN_FRAME'
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+LEFT JOIN
+  chrome_graphics_pipeline_surface_frame_steps
     compositor_generate_compositor_frame_step
   ON
     compositor_generate_compositor_frame_step.surface_frame_trace_id
@@ -431,6 +456,9 @@
   compositor_resample_slice_id LONG,
   -- Timestamp for the `STEP_RESAMPLE_SCROLL_EVENTS` slice.
   compositor_resample_ts TIMESTAMP,
+  -- Timestamp for the `STEP_RECEIVE_BEGIN_FRAME` slice or the
+  -- containing task (if available).
+  compositor_receive_begin_frame_ts TIMESTAMP,
   -- Slice id for the `STEP_GENERATE_COMPOSITOR_FRAME` slice.
   compositor_generate_compositor_frame_slice_id LONG,
   -- Timestamp for the `STEP_GENERATE_COMPOSITOR_FRAME` slice or the
@@ -510,6 +538,14 @@
       compositor_resample_ts) AS compositor_resample_ts,
     -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
     -- Ids
+    compositor_receive_begin_frame_slice_id,
+    -- Timestamps
+    COALESCE(
+      compositor_receive_begin_frame_task_ts,
+      compositor_receive_begin_frame_ts)
+      AS compositor_receive_begin_frame_ts,
+    -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+    -- Ids
     compositor_generate_compositor_frame_slice_id,
     -- Timestamps
     COALESCE(
@@ -566,6 +602,9 @@
   compositor_resample_ts,
   -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
   -- On `compositor_utid`.
+  compositor_receive_begin_frame_ts,
+  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+  -- On `compositor_utid`.
   compositor_generate_compositor_frame_slice_id,
   -- TODO(b:380868337): This is sometimes unexpectedly null; check/fix this.
   compositor_generate_compositor_frame_ts,
@@ -887,6 +926,8 @@
   -- No applicable slice id (duration between two slices).
   input.compositor_dispatch_end_ts,
   -- TODO(b:380868337): This is sometimes negative; check/fix this.
+  -- TODO(b:381273884): use frame.compositor_receive_begin_frame_ts instead of
+  -- input.compositor_dispatch_end_ts.
   COALESCE(
     frame.compositor_resample_ts,
     input.compositor_coalesced_input_handled_ts
diff --git a/build/config/unsafe_buffers_paths.txt b/build/config/unsafe_buffers_paths.txt
index 208a10c..d8e1f94 100644
--- a/build/config/unsafe_buffers_paths.txt
+++ b/build/config/unsafe_buffers_paths.txt
@@ -57,7 +57,6 @@
 -chrome/chrome_elf/third_party_dlls/
 -chrome/elevation_service/internal/
 -chromecast/
--clank/
 -components/optimization_guide/internal/
 -ios/
 -ios_internal/
diff --git a/cc/paint/oop_pixeltest.cc b/cc/paint/oop_pixeltest.cc
index 1469003..70137d4 100644
--- a/cc/paint/oop_pixeltest.cc
+++ b/cc/paint/oop_pixeltest.cc
@@ -76,6 +76,11 @@
 
 namespace cc {
 namespace {
+
+SkV4 SkColorToSkV4(SkColor4f color) {
+  return SkV4{color.fR, color.fG, color.fB, color.fA};
+}
+
 scoped_refptr<DisplayItemList> MakeNoopDisplayItemList() {
   auto display_item_list = base::MakeRefCounted<DisplayItemList>();
   display_item_list->StartPaint();
@@ -2915,31 +2920,40 @@
 INSTANTIATE_TEST_SUITE_P(P, OopPathPixelTest, ::testing::Bool());
 
 TEST_F(OopPixelTest, SkSLCommandShader) {
+  // Draws a red square.
+  const std::string_view kDrawRedRect(R"(
+    uniform float u_border_alpha;
+    uniform float2 u_top_left;
+    uniform float2 u_btm_right;
+    uniform vec4 u_border_color;
+    uniform vec4 u_center_color;
+
+    vec4 main(float2 coord) {
+      if (all(greaterThanEqual(coord, u_top_left)) &&
+          all(lessThan(coord, u_btm_right))) {
+        return u_center_color;
+      } else {
+        return vec4(u_border_color.xyz, u_border_alpha);
+      }
+    }
+  )");
+  auto shader = PaintShader::MakeSkSLCommand(
+      kDrawRedRect,
+      /*float_uniforms=*/{{.name = SkString("u_border_alpha"), .value = 0.5f}},
+      /*float2_uniforms=*/
+      {{.name = SkString("u_top_left"), .value = SkV2{25.f, 25.f}},
+       {.name = SkString("u_btm_right"), .value = SkV2{75.f, 75.f}}},
+      /*float4_uniforms=*/
+      {{.name = SkString("u_border_color"),
+        .value = SkColorToSkV4(SkColors::kRed)},
+       {.name = SkString("u_center_color"),
+        .value = SkColorToSkV4(SkColors::kGreen)}});
+  ASSERT_TRUE(shader);
+
   const gfx::Size rect(100, 100);
   RasterOptions options(rect);
   options.preclear = true;
   options.preclear_color = SkColors::kWhite;
-
-  // Draws a red square.
-  const std::string_view kDrawRedRect(R"(
-    const half4 color = half4(1.0, 0.0, 0.0, 1.0);  // <R, G, B, A>
-    const vec2 top_left = vec2(25, 25);             // <X, Y>
-    const vec2 btm_right = vec2(75, 75);            // <X+W, Y+H>
-
-    half4 main(float2 coord) {
-      if (all(greaterThanEqual(coord, top_left)) &&
-          all(lessThan(coord, btm_right))) {
-        // Red if inside the rect.
-        return color;
-      } else {
-        // Transparent elsewhere.
-        return half4(0.0);
-      }
-    }
-  )");
-  auto shader = PaintShader::MakeSkSLCommand(kDrawRedRect);
-  ASSERT_TRUE(shader);
-
   PaintFlags flags;
   flags.setShader(std::move(shader));
   auto display_item_list = base::MakeRefCounted<DisplayItemList>();
@@ -2955,9 +2969,14 @@
   SkCanvas canvas(expected, SkSurfaceProps{});
   SkPaint red;
   red.setColor(SkColors::kRed);
-  canvas.drawRect(SkRect::MakeXYWH(25, 25, 50, 50), red);
+  red.setAlphaf(0.5f);
+  canvas.drawRect(SkRect::MakeXYWH(0, 0, 100, 100), red);
+  SkPaint green;
+  green.setColor(SkColors::kGreen);
+  canvas.drawRect(SkRect::MakeXYWH(25, 25, 50, 50), green);
 
-  EXPECT_EQ(GetPNGDataUrl(actual), GetPNGDataUrl(expected));
+  EXPECT_TRUE(MatchesBitmap(actual, expected,
+                            ManhattanDistancePixelComparator(/*tolerance=*/5)));
 }
 
 }  // namespace
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index fca1e94..20087d6 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -3545,7 +3545,15 @@
       "half4 main(float2 coord) { return half4(0.5); }";
 
   PaintFlags flags;
-  flags.setShader(PaintShader::MakeSkSLCommand(kCommand));
+  std::vector<PaintShader::FloatUniform> scalar_uniforms = {
+      {.name = SkString("u_scalar"), .value = 42.f}};
+  std::vector<PaintShader::Float2Uniform> float2_uniforms = {
+      {.name = SkString("u_vec2"), .value = SkV2{42.f, 21.f}}};
+  std::vector<PaintShader::Float4Uniform> float4_uniforms = {
+      {.name = SkString("u_vec4"), .value = SkV4{42.f, 21.f, 10.f, 5.f}}};
+  flags.setShader(PaintShader::MakeSkSLCommand(
+      kCommand, std::move(scalar_uniforms), std::move(float2_uniforms),
+      std::move(float4_uniforms)));
   PaintOpBuffer buffer;
   buffer.push<DrawRectOp>(SkRect::MakeXYWH(1, 2, 3, 4), flags);
 
diff --git a/cc/paint/paint_op_reader.cc b/cc/paint/paint_op_reader.cc
index 7a35d45..6d7f17d 100644
--- a/cc/paint/paint_op_reader.cc
+++ b/cc/paint/paint_op_reader.cc
@@ -61,6 +61,8 @@
 namespace cc {
 namespace {
 
+static_assert(std::is_same_v<unsigned char, uint8_t>);
+
 bool IsValidPaintShaderType(PaintShader::Type type) {
   return static_cast<uint8_t>(type) <
          static_cast<uint8_t>(PaintShader::Type::kShaderCount);
@@ -73,6 +75,34 @@
 
 }  // namespace
 
+// Being a friend to `PaintOpReader`, this cannot be in the anonymous namespace.
+template <typename ValueType>
+void ReadSimpleValueUniformsHelper(
+    PaintOpReader& reader,
+    std::vector<PaintShader::Uniform<ValueType>>* output_uniforms) {
+  size_t count = 0u;
+  reader.ReadSize(&count);
+  if (count == 0) {
+    return;
+  }
+  output_uniforms->reserve(count);
+  for (size_t i = 0; i < count; ++i) {
+    SkString name;
+    reader.Read(&name);
+    if (!reader.valid()) {
+      return;
+    }
+    CHECK(!name.isEmpty());
+    ValueType value;
+    reader.ReadSimple(&value);
+    if (!reader.valid()) {
+      return;
+    }
+    output_uniforms->push_back(
+        {.name = std::move(name), .value = std::move(value)});
+  }
+}
+
 PaintOpReader::PaintOpReader(const volatile void* memory,
                              size_t size,
                              const PaintOp::DeserializeOptions& options,
@@ -746,6 +776,9 @@
   ReadVectorContent(positions_size, ref.positions_);
 
   Read(&ref.sksl_command_);
+  Read(&ref.scalar_uniforms_);
+  Read(&ref.float2_uniforms_);
+  Read(&ref.float4_uniforms_);
 
   // We don't write the cached shader, so don't attempt to read it either.
 
@@ -919,7 +952,6 @@
 }
 
 void PaintOpReader::Read(SkString* sk_string) {
-  static_assert(std::is_same_v<unsigned char, uint8_t>);
   size_t size = 0;
   // We always serialize the empty string's size (0u).
   ReadSize(&size);
@@ -934,6 +966,18 @@
   DidRead(size);
 }
 
+void PaintOpReader::Read(std::vector<PaintShader::FloatUniform>* uniforms) {
+  ReadSimpleValueUniformsHelper<SkScalar>(*this, uniforms);
+}
+
+void PaintOpReader::Read(std::vector<PaintShader::Float2Uniform>* uniforms) {
+  ReadSimpleValueUniformsHelper<SkV2>(*this, uniforms);
+}
+
+void PaintOpReader::Read(std::vector<PaintShader::Float4Uniform>* uniforms) {
+  ReadSimpleValueUniformsHelper<SkV4>(*this, uniforms);
+}
+
 void PaintOpReader::AlignMemory(size_t alignment) {
   DCHECK_GE(alignment, PaintOpWriter::kDefaultAlignment);
   DCHECK_LE(alignment, BufferAlignment());
diff --git a/cc/paint/paint_op_reader.h b/cc/paint/paint_op_reader.h
index 9c299762..4c66ac1 100644
--- a/cc/paint/paint_op_reader.h
+++ b/cc/paint/paint_op_reader.h
@@ -109,6 +109,9 @@
   void Read(SkGradientShader::Interpolation* interpolation);
   void Read(scoped_refptr<SkottieWrapper>* skottie);
   void Read(SkString* sk_string);
+  void Read(std::vector<PaintShader::FloatUniform>* uniforms);
+  void Read(std::vector<PaintShader::Float2Uniform>* uniforms);
+  void Read(std::vector<PaintShader::Float4Uniform>* uniforms);
 
   void Read(SkClipOp* op) { ReadEnum<SkClipOp, SkClipOp::kMax_EnumValue>(op); }
   void Read(PaintCanvas::AnnotationType* type) {
@@ -182,6 +185,11 @@
   }
 
  private:
+  template <typename ValueType>
+  friend void ReadSimpleValueUniformsHelper(
+      PaintOpReader&,
+      std::vector<PaintShader::Uniform<ValueType>>*);
+
   enum class DeserializationError {
     // Enum values must remain synchronized with PaintOpDeserializationError
     // in tools/metrics/histograms/metadata/gpu/enums.xml.
diff --git a/cc/paint/paint_op_writer.cc b/cc/paint/paint_op_writer.cc
index 4263f1f..b9b7bc0 100644
--- a/cc/paint/paint_op_writer.cc
+++ b/cc/paint/paint_op_writer.cc
@@ -55,6 +55,8 @@
 namespace cc {
 namespace {
 
+static_assert(std::is_same_v<unsigned char, uint8_t>);
+
 SkIRect MakeSrcRect(const PaintImage& image) {
   if (!image) {
     return SkIRect::MakeEmpty();
@@ -67,8 +69,38 @@
   static_cast<uint32_t*>(memory)[0] = type | serialized_size << 8;
 }
 
+template <typename ValueType>
+size_t CountNonEmptyUniforms(
+    const std::vector<PaintShader::Uniform<ValueType>>& uniforms) {
+  return std::count_if(uniforms.begin(), uniforms.end(),
+                       [](const PaintShader::Uniform<ValueType>& u) {
+                         return !u.name.isEmpty();
+                       });
+}
+
 }  // namespace
 
+// Friend to `PaintOpWriter`. Can't be nested in the anonymous namespace.
+template <typename ValueType>
+size_t SerializeSizeSimpleValueUniforms(
+    const std::vector<PaintShader::Uniform<ValueType>>& uniforms) {
+  const size_t count = uniforms.size();
+  size_t name_sizes = 0u;
+  for (const auto& [name, value] : uniforms) {
+    if (name.isEmpty()) {
+      continue;
+    }
+    name_sizes += PaintOpWriter::SerializedSize(name);
+  }
+  // [ size_t [ [size_t data] data [size_t data] data ] ]
+  //     2u         key0      val0      key1     val1
+  return (PaintOpWriter::SerializedSize<size_t>() +
+          base::CheckedNumeric<size_t>(name_sizes) +
+          base::CheckedNumeric<size_t>(count) *
+              PaintOpWriter::SerializedSizeSimple<ValueType>())
+      .ValueOrDie();
+}
+
 // static
 size_t PaintOpWriter::SerializedSize(const PaintImage& image) {
   // Image Serialization type.
@@ -130,6 +162,24 @@
   return SerializedSizeOfBytes(sk_string.size());
 }
 
+// static:
+size_t PaintOpWriter::SerializedSize(
+    const std::vector<PaintShader::FloatUniform>& uniforms) {
+  return SerializeSizeSimpleValueUniforms<SkScalar>(uniforms);
+}
+
+// static:
+size_t PaintOpWriter::SerializedSize(
+    const std::vector<PaintShader::Float2Uniform>& uniforms) {
+  return SerializeSizeSimpleValueUniforms<SkV2>(uniforms);
+}
+
+// static:
+size_t PaintOpWriter::SerializedSize(
+    const std::vector<PaintShader::Float4Uniform>& uniforms) {
+  return SerializeSizeSimpleValueUniforms<SkV4>(uniforms);
+}
+
 // static
 size_t PaintOpWriter::SerializedSize(const ColorFilter* filter) {
   if (!filter) {
@@ -497,13 +547,51 @@
 }
 
 void PaintOpWriter::Write(const SkString& sk_string) {
-  static_assert(std::is_same_v<unsigned char, uint8_t>);
   size_t num_bytes = sk_string.size();
   WriteSize(num_bytes);
   WriteData(base::span<const uint8_t>(
       reinterpret_cast<const uint8_t*>(sk_string.data()), num_bytes));
 }
 
+void PaintOpWriter::Write(
+    const std::vector<PaintShader::FloatUniform>& uniforms) {
+  const size_t count = CountNonEmptyUniforms(uniforms);
+  WriteSize(count);
+  for (const auto& [name, value] : uniforms) {
+    if (name.isEmpty()) {
+      continue;
+    }
+    Write(name);
+    WriteSimple(value);
+  }
+}
+
+void PaintOpWriter::Write(
+    const std::vector<PaintShader::Float2Uniform>& uniforms) {
+  const size_t count = CountNonEmptyUniforms(uniforms);
+  WriteSize(count);
+  for (const auto& [name, value] : uniforms) {
+    if (name.isEmpty()) {
+      continue;
+    }
+    Write(name);
+    WriteSimpleMultiple(value.x, value.y);
+  }
+}
+
+void PaintOpWriter::Write(
+    const std::vector<PaintShader::Float4Uniform>& uniforms) {
+  const size_t count = CountNonEmptyUniforms(uniforms);
+  WriteSize(count);
+  for (const auto& [name, value] : uniforms) {
+    if (name.isEmpty()) {
+      continue;
+    }
+    Write(name);
+    WriteSimpleMultiple(value.x, value.y, value.z, value.w);
+  }
+}
+
 void PaintOpWriter::Write(const SkGainmapInfo& gainmap_info) {
   Write(gainmap_info.fGainmapRatioMin);
   Write(gainmap_info.fGainmapRatioMax);
@@ -684,6 +772,9 @@
   // using other fields.
 
   Write(shader->sksl_command_);
+  Write(shader->scalar_uniforms_);
+  Write(shader->float2_uniforms_);
+  Write(shader->float4_uniforms_);
 }
 
 void PaintOpWriter::Write(SkYUVColorSpace yuv_color_space) {
diff --git a/cc/paint/paint_op_writer.h b/cc/paint/paint_op_writer.h
index 269307f..fefec18a 100644
--- a/cc/paint/paint_op_writer.h
+++ b/cc/paint/paint_op_writer.h
@@ -140,6 +140,10 @@
   static constexpr size_t kDefaultAlignment = alignof(uint32_t);
 
  private:
+  template <typename ValueType>
+  friend size_t SerializeSizeSimpleValueUniforms(
+      const std::vector<PaintShader::Uniform<ValueType>>&);
+
   template <typename T>
   static constexpr size_t SerializedSizeSimple();
 
@@ -159,6 +163,12 @@
   static size_t SerializedSize(const PaintRecord& record);
   static size_t SerializedSize(const SkHighContrastConfig& config);
   static size_t SerializedSize(const SkString& sk_string);
+  static size_t SerializedSize(
+      const std::vector<PaintShader::FloatUniform>& scalar_map);
+  static size_t SerializedSize(
+      const std::vector<PaintShader::Float2Uniform>& float2_map);
+  static size_t SerializedSize(
+      const std::vector<PaintShader::Float4Uniform>& float4_map);
 
   // Serialization of raw/smart pointers is not supported by default.
   template <typename T>
@@ -277,6 +287,9 @@
   void Write(const PathEffect* effect);
   void Write(const gfx::HDRMetadata& hdr_metadata);
   void Write(const SkString& sk_string);
+  void Write(const std::vector<PaintShader::FloatUniform>& scalar_uniforms);
+  void Write(const std::vector<PaintShader::Float2Uniform>& float2_uniforms);
+  void Write(const std::vector<PaintShader::Float4Uniform>& float4_uniforms);
 
   void Write(SkClipOp op) { WriteEnum(op); }
   void Write(PaintCanvas::AnnotationType type) { WriteEnum(type); }
diff --git a/cc/paint/paint_op_writer_reader_unittest.cc b/cc/paint/paint_op_writer_reader_unittest.cc
index c1734fba6..16de65f 100644
--- a/cc/paint/paint_op_writer_reader_unittest.cc
+++ b/cc/paint/paint_op_writer_reader_unittest.cc
@@ -6,7 +6,6 @@
 
 #include "cc/paint/paint_op_reader.h"
 #include "cc/paint/paint_op_writer.h"
-
 #include "cc/test/test_options_provider.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -121,4 +120,97 @@
   EXPECT_EQ(deseralized, original);
 }
 
+namespace {
+struct UniformTestCase {
+  std::vector<PaintShader::FloatUniform> scalars;
+  std::vector<PaintShader::Float2Uniform> float2s;
+  std::vector<PaintShader::Float4Uniform> float4s;
+  size_t expected_size;
+};
+
+using PaintOpWriterReaderUniformTest = testing::TestWithParam<UniformTestCase>;
+}  // namespace
+
+TEST_P(PaintOpWriterReaderUniformTest, Uniforms) {
+  char buffer[128];
+  TestOptionsProvider options_provider;
+  memset(buffer, 0xa5, std::size(buffer));
+  PaintOpWriter writer(buffer, std::size(buffer),
+                       options_provider.serialize_options(),
+                       /*enable_security_constraints=*/true);
+  const auto& scalars = GetParam().scalars;
+  const auto& float2s = GetParam().float2s;
+  const auto& float4s = GetParam().float4s;
+  if (!scalars.empty()) {
+    writer.Write(scalars);
+  } else if (!float2s.empty()) {
+    writer.Write(float2s);
+  } else if (!float4s.empty()) {
+    writer.Write(float4s);
+  } else {
+    ASSERT_TRUE(false);
+  }
+
+  EXPECT_EQ(writer.size(), GetParam().expected_size);
+
+  PaintOpReader reader(buffer, writer.size(),
+                       options_provider.deserialize_options(),
+                       /*enable_security_constraints=*/true);
+
+  if (!scalars.empty()) {
+    std::vector<PaintShader::FloatUniform> deseralized;
+    reader.Read(&deseralized);
+    EXPECT_THAT(deseralized, ::testing::UnorderedElementsAreArray(scalars));
+  } else if (!float2s.empty()) {
+    std::vector<PaintShader::Float2Uniform> deseralized;
+    reader.Read(&deseralized);
+    EXPECT_THAT(deseralized, ::testing::UnorderedElementsAreArray(float2s));
+  } else if (!float4s.empty()) {
+    std::vector<PaintShader::Float4Uniform> deseralized;
+    reader.Read(&deseralized);
+    EXPECT_THAT(deseralized, ::testing::UnorderedElementsAreArray(float4s));
+  } else {
+    ASSERT_TRUE(false);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    /*no prefix*/,
+    PaintOpWriterReaderUniformTest,
+    testing::ValuesIn<UniformTestCase>(
+        {UniformTestCase{
+             .scalars = {{.name = SkString("var1"), .value = 1.f},
+                         {.name = SkString("variable2"), .value = 2.f}},
+             // count = 8,
+             // "var1" = 8+4, 1.f = 4,
+             // "variable2" = 8+12 (9 aligned up to 12), 2.f = 4
+             .expected_size = 48u},
+         UniformTestCase{
+             .float2s = {{.name = SkString("var1"), .value = SkV2{1.f, 2.f}},
+                         {.name = SkString("variable2"),
+                          .value = SkV2{3.f, 4.f}}},
+             // count = 8,
+             // "var1" = 8+4, value = 8,
+             // "variable2" = 8+12 (9 aligned up to 12), value = 8
+             .expected_size = 56u},
+         UniformTestCase{.float4s = {{.name = SkString("var1"),
+                                      .value = SkV4{1.f, 2.f, 3.f, 4.f}},
+                                     {.name = SkString("variable2"),
+                                      .value = SkV4{5.f, 6.f, 7.f, 8.f}}},
+                         // count = 8,
+                         // "var1" = 8+4, value = 16,
+                         // "variable2" = 8+12 (9 aligned up to 12), value = 16
+                         .expected_size = 72u}}),
+    [](const ::testing::TestParamInfo<UniformTestCase> info) {
+      if (!info.param.scalars.empty()) {
+        return "Scalar";
+      } else if (!info.param.float2s.empty()) {
+        return "SkV2";
+      } else if (!info.param.float4s.empty()) {
+        return "SkV4";
+      } else {
+        NOTREACHED();
+      }
+    });
+
 }  // namespace cc
diff --git a/cc/paint/paint_shader.cc b/cc/paint/paint_shader.cc
index fbef187..938aeed 100644
--- a/cc/paint/paint_shader.cc
+++ b/cc/paint/paint_shader.cc
@@ -228,7 +228,11 @@
 }
 
 // static:
-sk_sp<PaintShader> PaintShader::MakeSkSLCommand(std::string_view sksl) {
+sk_sp<PaintShader> PaintShader::MakeSkSLCommand(
+    std::string_view sksl,
+    std::vector<FloatUniform> float_uniforms,
+    std::vector<Float2Uniform> float2_uniforms,
+    std::vector<Float4Uniform> float4_uniforms) {
   SkString cmd(sksl);
   auto [effect, error] = SkRuntimeEffect::MakeForShader(cmd);
   if (!effect) {
@@ -236,7 +240,10 @@
     return nullptr;
   }
   sk_sp<PaintShader> shader(new PaintShader(Type::kSkSLCommand));
-  shader->sksl_command_ = cmd;
+  shader->sksl_command_ = std::move(cmd);
+  shader->scalar_uniforms_ = std::move(float_uniforms);
+  shader->float2_uniforms_ = std::move(float2_uniforms);
+  shader->float4_uniforms_ = std::move(float4_uniforms);
   return shader;
 }
 
@@ -270,7 +277,10 @@
                                                   shader->colors_.size()) +
           PaintOpWriter::SerializedSizeOfElements(shader->positions_.data(),
                                                   shader->positions_.size()) +
-          PaintOpWriter::SerializedSize(shader->sksl_command_))
+          PaintOpWriter::SerializedSize(shader->sksl_command_) +
+          PaintOpWriter::SerializedSize(shader->scalar_uniforms_) +
+          PaintOpWriter::SerializedSize(shader->float2_uniforms_) +
+          PaintOpWriter::SerializedSize(shader->float4_uniforms_))
       .ValueOrDie();
 }
 
@@ -528,7 +538,17 @@
         // Fallback the the color shader.
         break;
       }
-      return effect->makeShader(/*uniforms=*/nullptr, /*children=*/{});
+      SkRuntimeShaderBuilder builder(effect);
+      for (const auto& [name, value] : scalar_uniforms_) {
+        builder.uniform(name.c_str()) = value;
+      }
+      for (const auto& [name, value] : float2_uniforms_) {
+        builder.uniform(name.c_str()) = value;
+      }
+      for (const auto& [name, value] : float4_uniforms_) {
+        builder.uniform(name.c_str()) = value;
+      }
+      return builder.makeShader();
     }
     case Type::kShaderCount:
       NOTREACHED();
diff --git a/cc/paint/paint_shader.h b/cc/paint/paint_shader.h
index 272d26e..02ffcad 100644
--- a/cc/paint/paint_shader.h
+++ b/cc/paint/paint_shader.h
@@ -126,9 +126,27 @@
 
   // Returns null if the `sksl` command is invalid.
   //
-  // *NOTE*: This is only intended for trusted shader (e.g., shaders that are
-  // part of the Chromium binary).
-  static sk_sp<PaintShader> MakeSkSLCommand(std::string_view sksl);
+  // NOTE:
+  // - This is only intended for trusted shader (e.g., shaders that are part of
+  //   the Chromium binary).
+  // - Not using flat_map because SkString does not have built-in comparator.
+  template <typename ValueType>
+  struct Uniform {
+    SkString name;
+    ValueType value;
+
+    bool operator==(const Uniform& other) const {
+      return name == other.name && value == other.value;
+    }
+  };
+  using FloatUniform = Uniform<SkScalar>;
+  using Float2Uniform = Uniform<SkV2>;
+  using Float4Uniform = Uniform<SkV4>;
+  static sk_sp<PaintShader> MakeSkSLCommand(
+      std::string_view sksl,
+      std::vector<FloatUniform> float_uniforms,
+      std::vector<Float2Uniform> float2_uniforms,
+      std::vector<Float4Uniform> float4_uniforms);
 
   static size_t GetSerializedSize(const PaintShader* shader);
 
@@ -301,9 +319,13 @@
   //
   // TODO(https://crbug.com/384532231): Consider cashing the Skia shader for
   // performance.
-  //
-  // TODO(https://crbug.com/384075578): Add support to shader uniforms.
   SkString sksl_command_;
+
+  // Uniforms for `sksl_command_`. The keys of the map are the variable name of
+  // the uniform.
+  std::vector<FloatUniform> scalar_uniforms_;
+  std::vector<Float2Uniform> float2_uniforms_;
+  std::vector<Float4Uniform> float4_uniforms_;
 };
 
 }  // namespace cc
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
index 5683cda..dc5de93 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
@@ -965,7 +965,7 @@
                     /* didCloseCallback= */ null);
         } else if (menuId == R.id.delete_shared_group) {
             RecordUserAction.record("TabGridDialogMenu.DeleteShared");
-            TabUiUtils.deleteSharedTabGroup(
+            TabUiUtils.exitSharedTabGroupWithDialog(
                     mActivity,
                     mCurrentTabGroupModelFilterSupplier.get(),
                     mActionConfirmationManager,
@@ -973,7 +973,7 @@
                     tabId);
         } else if (menuId == R.id.leave_group) {
             RecordUserAction.record("TabGridDialogMenu.LeaveShared");
-            TabUiUtils.leaveTabGroup(
+            TabUiUtils.exitSharedTabGroupWithDialog(
                     mActivity,
                     mCurrentTabGroupModelFilterSupplier.get(),
                     mActionConfirmationManager,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediatorUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediatorUnitTest.java
index b767663..c1d42652 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupListMediatorUnitTest.java
@@ -653,8 +653,7 @@
                 .onResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
 
         verify(mDataSharingService)
-                .removeMember(
-                        eq(COLLABORATION_ID1), eq(EMAIL2), mActionOutcomeCallbackCaptor.capture());
+                .leaveGroup(eq(COLLABORATION_ID1), mActionOutcomeCallbackCaptor.capture());
         mActionOutcomeCallbackCaptor
                 .getValue()
                 .onResult(PeopleGroupActionOutcome.TRANSIENT_FAILURE);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediator.java
index a7690d6..9a9efa8 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupRowMediator.java
@@ -18,7 +18,6 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.supplier.LazyOneshotSupplier;
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.data_sharing.ui.shared_image_tiles.SharedImageTilesColor;
 import org.chromium.chrome.browser.data_sharing.ui.shared_image_tiles.SharedImageTilesCoordinator;
 import org.chromium.chrome.browser.data_sharing.ui.shared_image_tiles.SharedImageTilesType;
@@ -33,7 +32,6 @@
 import org.chromium.components.data_sharing.DataSharingService;
 import org.chromium.components.data_sharing.DataSharingService.GroupDataOrFailureOutcome;
 import org.chromium.components.data_sharing.GroupData;
-import org.chromium.components.data_sharing.PeopleGroupActionOutcome;
 import org.chromium.components.data_sharing.member_role.MemberRole;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.base.GaiaId;
@@ -42,7 +40,6 @@
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
 import org.chromium.components.tab_group_sync.TabGroupUiActionHandler;
 import org.chromium.ui.modaldialog.ModalDialogManager;
-import org.chromium.ui.modaldialog.ModalDialogUtils;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.url.GURL;
 
@@ -170,22 +167,18 @@
 
         GaiaId gaiaId = mCoreAccountInfoSupplier.get().getGaiaId();
         @MemberRole int memberRole = TabShareUtils.getSelfMemberRole(groupData, gaiaId);
+        String groupTitle = groupData.displayName;
+        String collaborationId = groupData.groupToken.collaborationId;
         if (memberRole == MemberRole.OWNER) {
             mPropertyModel.set(
-                    DELETE_RUNNABLE,
-                    () ->
-                            processDeleteSharedGroup(
-                                    groupData.displayName, groupData.groupToken.collaborationId));
+                    DELETE_RUNNABLE, () -> processDeleteSharedGroup(groupTitle, collaborationId));
             mPropertyModel.set(LEAVE_RUNNABLE, null);
         } else {
             // TODO(crbug.com/365852281): Leave action should look like a delete if there are no
             // other users.
             mPropertyModel.set(DELETE_RUNNABLE, null);
             mPropertyModel.set(
-                    LEAVE_RUNNABLE,
-                    () ->
-                            processLeaveGroup(
-                                    groupData.displayName, groupData.groupToken.collaborationId));
+                    LEAVE_RUNNABLE, () -> processLeaveGroup(groupTitle, collaborationId));
         }
 
         if (sharedState == GroupSharedState.COLLABORATION_ONLY) {
@@ -268,45 +261,40 @@
         }
     }
 
-    private void processDeleteSharedGroup(String groupTitle, String groupId) {
+    private void processDeleteSharedGroup(String groupTitle, String collaborationId) {
         // TODO(crbug.com/365852281): Confirmation should look like a non-shared delete if there are
         // no other users.
         mActionConfirmationManager.processDeleteSharedGroupAttempt(
                 groupTitle,
                 (@ActionConfirmationResult Integer result) -> {
                     if (result != ActionConfirmationResult.CONFIRMATION_NEGATIVE) {
-                        mDataSharingService.deleteGroup(groupId, this::onLeaveOrDeleteGroup);
+                        TabUiUtils.exitCollaborationWithoutWarning(
+                                mContext,
+                                mModalDialogManager,
+                                mDataSharingService,
+                                collaborationId,
+                                MemberRole.OWNER);
                     }
                 });
     }
 
-    private void processLeaveGroup(String groupTitle, String groupId) {
+    private void processLeaveGroup(String groupTitle, String collaborationId) {
         // TODO(crbug.com/365852281): Confirmation should look like a non-shared delete if there are
         // no other users.
         mActionConfirmationManager.processLeaveGroupAttempt(
                 groupTitle,
                 (@ActionConfirmationResult Integer result) -> {
                     if (result != ActionConfirmationResult.CONFIRMATION_NEGATIVE) {
-                        String memberEmail = mCoreAccountInfoSupplier.get().getEmail();
-                        mDataSharingService.removeMember(
-                                groupId, memberEmail, this::onLeaveOrDeleteGroup);
+                        TabUiUtils.exitCollaborationWithoutWarning(
+                                mContext,
+                                mModalDialogManager,
+                                mDataSharingService,
+                                collaborationId,
+                                MemberRole.MEMBER);
                     }
                 });
     }
 
-    private void onLeaveOrDeleteGroup(@PeopleGroupActionOutcome int outcome) {
-        if (outcome == PeopleGroupActionOutcome.SUCCESS) {
-            // TODO(crbug.com/345854578): Do we need to actively remove things from the UI?
-        } else {
-            ModalDialogUtils.showOneButtonConfirmation(
-                    mModalDialogManager,
-                    mContext.getResources(),
-                    R.string.data_sharing_generic_failure_title,
-                    R.string.data_sharing_generic_failure_description,
-                    R.string.data_sharing_invitation_failure_button);
-        }
-    }
-
     private void deleteGroup(boolean allowDialog) {
         @GroupWindowState int state = mFetchGroupState.get();
         if (state == GroupWindowState.IN_ANOTHER) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index 0960543..f4546806 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -2587,7 +2587,7 @@
             TabUiUtils.ungroupTabGroup(mCurrentTabGroupModelFilterSupplier.get(), tabId);
         } else if (menuId == R.id.delete_shared_group) {
             RecordUserAction.record("TabGroupItemMenu.DeleteShared");
-            TabUiUtils.deleteSharedTabGroup(
+            TabUiUtils.exitSharedTabGroupWithDialog(
                     mActivity,
                     mCurrentTabGroupModelFilterSupplier.get(),
                     mActionConfirmationManager,
@@ -2595,7 +2595,7 @@
                     tabId);
         } else if (menuId == R.id.leave_group) {
             RecordUserAction.record("TabGroupItemMenu.LeaveShared");
-            TabUiUtils.leaveTabGroup(
+            TabUiUtils.exitSharedTabGroupWithDialog(
                     mActivity,
                     mCurrentTabGroupModelFilterSupplier.get(),
                     mActionConfirmationManager,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java
index 40bb4c5..9be03f3 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java
@@ -16,6 +16,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory;
 import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory;
 import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
 import org.chromium.chrome.browser.data_sharing.ui.shared_image_tiles.SharedImageTilesCoordinator;
@@ -31,11 +32,13 @@
 import org.chromium.chrome.browser.tabmodel.TabList;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelActionListener;
-import org.chromium.chrome.browser.tabmodel.TabModelActionListener.DialogType;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.widget.ActionConfirmationResult;
+import org.chromium.components.collaboration.CollaborationService;
 import org.chromium.components.data_sharing.DataSharingService;
+import org.chromium.components.data_sharing.GroupData;
 import org.chromium.components.data_sharing.PeopleGroupActionOutcome;
+import org.chromium.components.data_sharing.member_role.MemberRole;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.identitymanager.ConsentLevel;
 import org.chromium.components.signin.identitymanager.IdentityManager;
@@ -159,46 +162,7 @@
     }
 
     /**
-     * Deletes a shared tab group, prompting to user to verify first.
-     *
-     * @param context Used to load resources.
-     * @param filter Used to pull dependencies from.
-     * @param actionConfirmationManager Used to show a confirmation dialog.
-     * @param modalDialogManager Used to show error dialogs.
-     * @param tabId The local id of the tab being deleted.
-     */
-    public static void deleteSharedTabGroup(
-            Context context,
-            TabGroupModelFilter filter,
-            ActionConfirmationManager actionConfirmationManager,
-            ModalDialogManager modalDialogManager,
-            int tabId) {
-        assert ChromeFeatureList.isEnabled(ChromeFeatureList.DATA_SHARING);
-        TabModel tabModel = filter.getTabModel();
-        Profile profile = tabModel.getProfile();
-        TabGroupSyncService tabGroupSyncService = TabGroupSyncServiceFactory.getForProfile(profile);
-        DataSharingService dataSharingService = DataSharingServiceFactory.getForProfile(profile);
-
-        @Nullable
-        SavedTabGroup savedTabGroup =
-                TabGroupSyncUtils.getSavedTabGroupFromTabId(tabId, tabModel, tabGroupSyncService);
-        if (savedTabGroup == null || TextUtils.isEmpty(savedTabGroup.collaborationId)) return;
-
-        assert actionConfirmationManager != null;
-
-        actionConfirmationManager.processDeleteSharedGroupAttempt(
-                savedTabGroup.title,
-                (@ActionConfirmationResult Integer result) -> {
-                    if (result != ActionConfirmationResult.CONFIRMATION_NEGATIVE) {
-                        dataSharingService.deleteGroup(
-                                savedTabGroup.collaborationId,
-                                bindOnLeaveOrDeleteGroup(context, modalDialogManager));
-                    }
-                });
-    }
-
-    /**
-     * Leaves a shared tab group, prompting to user to verify first.
+     * Leave or deletes a shared tab group, prompting to user to verify first.
      *
      * @param context Used to load resources.
      * @param filter Used to pull dependencies from.
@@ -206,40 +170,90 @@
      * @param modalDialogManager Used to show error dialogs.
      * @param tabId The local id of the tab being left.
      */
-    public static void leaveTabGroup(
+    public static void exitSharedTabGroupWithDialog(
             Context context,
             TabGroupModelFilter filter,
             ActionConfirmationManager actionConfirmationManager,
             ModalDialogManager modalDialogManager,
             int tabId) {
         assert ChromeFeatureList.isEnabled(ChromeFeatureList.DATA_SHARING);
+        assert actionConfirmationManager != null;
+
         TabModel tabModel = filter.getTabModel();
         Profile profile = tabModel.getProfile();
         TabGroupSyncService tabGroupSyncService = TabGroupSyncServiceFactory.getForProfile(profile);
         DataSharingService dataSharingService = DataSharingServiceFactory.getForProfile(profile);
         IdentityManager identityManager =
                 IdentityServicesProvider.get().getIdentityManager(profile);
+        CollaborationService collaborationService =
+                CollaborationServiceFactory.getForProfile(profile);
 
         @Nullable
         SavedTabGroup savedTabGroup =
                 TabGroupSyncUtils.getSavedTabGroupFromTabId(tabId, tabModel, tabGroupSyncService);
-        if (savedTabGroup == null || TextUtils.isEmpty(savedTabGroup.collaborationId)) return;
         @Nullable
         CoreAccountInfo account = identityManager.getPrimaryAccountInfo(ConsentLevel.SIGNIN);
-        if (account == null) return;
+        if (savedTabGroup == null
+                || TextUtils.isEmpty(savedTabGroup.collaborationId)
+                || account == null) {
+            showGenericErrorDialog(context, modalDialogManager);
+            return;
+        }
 
-        assert actionConfirmationManager != null;
+        String collaborationId = savedTabGroup.collaborationId;
+        @Nullable GroupData shareGroup = collaborationService.getGroupData(collaborationId);
+        if (shareGroup == null) {
+            showGenericErrorDialog(context, modalDialogManager);
+            return;
+        }
 
-        actionConfirmationManager.processLeaveGroupAttempt(
-                savedTabGroup.title,
+        @MemberRole
+        int memberRole = TabShareUtils.getSelfMemberRole(shareGroup, account.getGaiaId());
+        Callback<Integer> onActionConfirmation =
                 (@ActionConfirmationResult Integer result) -> {
                     if (result != ActionConfirmationResult.CONFIRMATION_NEGATIVE) {
-                        dataSharingService.removeMember(
-                                savedTabGroup.collaborationId,
-                                account.getEmail(),
-                                bindOnLeaveOrDeleteGroup(context, modalDialogManager));
+                        exitCollaborationWithoutWarning(
+                                context,
+                                modalDialogManager,
+                                dataSharingService,
+                                collaborationId,
+                                memberRole);
                     }
-                });
+                };
+        if (memberRole == MemberRole.OWNER) {
+            actionConfirmationManager.processDeleteSharedGroupAttempt(
+                    savedTabGroup.title, onActionConfirmation);
+        } else if (memberRole == MemberRole.MEMBER) {
+            actionConfirmationManager.processLeaveGroupAttempt(
+                    savedTabGroup.title, onActionConfirmation);
+        } else {
+            showGenericErrorDialog(context, modalDialogManager);
+        }
+    }
+
+    /**
+     * Leaves or deletes a given collaboration.
+     *
+     * @param context Used to load resources.
+     * @param modalDialogManager Used to show error dialogs.
+     * @param dataSharingService Called to do the actual leave or delete action.
+     * @param collaborationId Used to identify the collaboration.
+     * @param memberRole Used to decide which way to exit the group.
+     */
+    public static void exitCollaborationWithoutWarning(
+            Context context,
+            ModalDialogManager modalDialogManager,
+            DataSharingService dataSharingService,
+            String collaborationId,
+            @MemberRole int memberRole) {
+        Callback<Integer> callback = bindOnLeaveOrDeleteGroup(context, modalDialogManager);
+        if (memberRole == MemberRole.OWNER) {
+            dataSharingService.deleteGroup(collaborationId, callback);
+        } else if (memberRole == MemberRole.MEMBER) {
+            dataSharingService.leaveGroup(collaborationId, callback);
+        } else {
+            showGenericErrorDialog(context, modalDialogManager);
+        }
     }
 
     /**
@@ -293,20 +307,29 @@
     private static Callback<Integer> bindOnLeaveOrDeleteGroup(
             Context context, ModalDialogManager modalDialogManager) {
         return (@PeopleGroupActionOutcome Integer outcome) -> {
-            if (outcome == PeopleGroupActionOutcome.SUCCESS) {
-                // TODO(crbug.com/345854578): Do we need to actively remove things from the UI?
-            } else {
-                ModalDialogUtils.showOneButtonConfirmation(
-                        modalDialogManager,
-                        context.getResources(),
-                        R.string.data_sharing_generic_failure_title,
-                        R.string.data_sharing_generic_failure_description,
-                        R.string.data_sharing_invitation_failure_button);
+            if (outcome != PeopleGroupActionOutcome.SUCCESS) {
+                showGenericErrorDialog(context, modalDialogManager);
             }
         };
     }
 
     /**
+     * Shows a generic error when a data sharing action fails.
+     *
+     * @param context Used to load resources.
+     * @param modalDialogManager Used to show the dialog.
+     */
+    public static void showGenericErrorDialog(
+            Context context, ModalDialogManager modalDialogManager) {
+        ModalDialogUtils.showOneButtonConfirmation(
+                modalDialogManager,
+                context.getResources(),
+                R.string.data_sharing_generic_failure_title,
+                R.string.data_sharing_generic_failure_description,
+                R.string.data_sharing_invitation_failure_button);
+    }
+
+    /**
      * Mark the tab switcher view as sensitive if at least one of the tabs in {@param tabList} has
      * sensitive content. Note that if all sensitive tabs are removed from the tab switcher, the tab
      * switcher will have to be closed and opened again to become not sensitive.
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtilsUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtilsUnitTest.java
index 760f06d..dbb2b99 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtilsUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtilsUnitTest.java
@@ -12,6 +12,14 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static org.chromium.components.data_sharing.SharedGroupTestHelper.COLLABORATION_ID1;
+import static org.chromium.components.data_sharing.SharedGroupTestHelper.EMAIL1;
+import static org.chromium.components.data_sharing.SharedGroupTestHelper.EMAIL2;
+import static org.chromium.components.data_sharing.SharedGroupTestHelper.GAIA_ID1;
+import static org.chromium.components.data_sharing.SharedGroupTestHelper.GAIA_ID2;
+import static org.chromium.components.data_sharing.SharedGroupTestHelper.GROUP_MEMBER1;
+import static org.chromium.components.data_sharing.SharedGroupTestHelper.GROUP_MEMBER2;
+import static org.chromium.components.tab_group_sync.SyncedGroupTestHelper.SYNC_GROUP_ID1;
 import static org.chromium.ui.test.util.MockitoHelper.runWithValue;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -31,6 +39,7 @@
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.base.test.util.HistogramWatcher;
+import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory;
 import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -44,13 +53,18 @@
 import org.chromium.chrome.browser.tabmodel.TabModelActionListener.DialogType;
 import org.chromium.chrome.browser.tabmodel.TabRemover;
 import org.chromium.components.browser_ui.widget.ActionConfirmationResult;
+import org.chromium.components.collaboration.CollaborationService;
 import org.chromium.components.data_sharing.DataSharingService;
+import org.chromium.components.data_sharing.GroupData;
+import org.chromium.components.data_sharing.GroupMember;
 import org.chromium.components.data_sharing.PeopleGroupActionOutcome;
+import org.chromium.components.data_sharing.SharedGroupTestHelper;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.base.GaiaId;
 import org.chromium.components.signin.identitymanager.IdentityManager;
 import org.chromium.components.tab_group_sync.LocalTabGroupId;
 import org.chromium.components.tab_group_sync.SavedTabGroup;
+import org.chromium.components.tab_group_sync.SyncedGroupTestHelper;
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 
@@ -63,10 +77,7 @@
     private static final int TAB_ID = 123;
     private static final int ROOT_ID = TAB_ID;
     private static final String GROUP_TITLE = "My Group";
-    private static final String COLLABORATION_ID1 = "A";
-    private static final GaiaId GAIA_ID = new GaiaId("Z");
-    private static final String EMAIL = "fake@gmail.com";
-    private static final Token TAB_GROUP_TOKEN = Token.createRandom();
+    private static final Token TAB_GROUP_ID = new Token(1L, 2L);
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
@@ -81,6 +92,7 @@
     @Mock private IdentityManager mIdentityManager;
     @Mock private TabGroupSyncService mTabGroupSyncService;
     @Mock private DataSharingService mDataSharingService;
+    @Mock private CollaborationService mCollaborationService;
     @Mock private Callback<Boolean> mDidCloseTabsCallback;
     @Mock private Callback<Boolean> mContentSensitivitySetter;
 
@@ -88,10 +100,13 @@
     @Captor private ArgumentCaptor<Callback<Integer>> mOutcomeCaptor;
 
     private List<Tab> mTabsToClose;
+    private SyncedGroupTestHelper mSyncedGroupTestHelper;
 
     @Before
     public void setUp() {
         mTabsToClose = List.of(mTab);
+        mSyncedGroupTestHelper = new SyncedGroupTestHelper(mTabGroupSyncService);
+
         when(mTabModel.getTabRemover()).thenReturn(mTabRemover);
         when(mFilter.getTabModel()).thenReturn(mTabModel);
         when(mFilter.isIncognitoBranded()).thenReturn(false);
@@ -101,12 +116,13 @@
         when(mTabModel.getTabById(TAB_ID)).thenReturn(mTab);
         when(mTab.isClosing()).thenReturn(false);
         when(mTab.getId()).thenReturn(TAB_ID);
-        when(mTab.getTabGroupId()).thenReturn(TAB_GROUP_TOKEN);
+        when(mTab.getTabGroupId()).thenReturn(TAB_GROUP_ID);
         when(mTabModel.getProfile()).thenReturn(mProfile);
         IdentityServicesProvider.setInstanceForTests(mIdentityServicesProvider);
         when(mIdentityServicesProvider.getIdentityManager(any())).thenReturn(mIdentityManager);
         TabGroupSyncServiceFactory.setForTesting(mTabGroupSyncService);
         DataSharingServiceFactory.setForTesting(mDataSharingService);
+        CollaborationServiceFactory.setForTesting(mCollaborationService);
     }
 
     @Test
@@ -182,13 +198,11 @@
         runWithValue(1, ActionConfirmationResult.CONFIRMATION_POSITIVE)
                 .when(mActionConfirmationManager)
                 .processDeleteSharedGroupAttempt(any(), any());
+        mockIdentity(EMAIL1, GAIA_ID1);
+        createSyncGroup(COLLABORATION_ID1);
+        createSharedGroup(GROUP_MEMBER1, GROUP_MEMBER2);
 
-        SavedTabGroup savedTabGroup = new SavedTabGroup();
-        savedTabGroup.title = GROUP_TITLE;
-        savedTabGroup.collaborationId = COLLABORATION_ID1;
-        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
-
-        TabUiUtils.deleteSharedTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -206,13 +220,11 @@
         runWithValue(1, ActionConfirmationResult.CONFIRMATION_NEGATIVE)
                 .when(mActionConfirmationManager)
                 .processDeleteSharedGroupAttempt(any(), any());
+        mockIdentity(EMAIL1, GAIA_ID1);
+        createSyncGroup(COLLABORATION_ID1);
+        createSharedGroup(GROUP_MEMBER1, GROUP_MEMBER2);
 
-        SavedTabGroup savedTabGroup = new SavedTabGroup();
-        savedTabGroup.title = GROUP_TITLE;
-        savedTabGroup.collaborationId = COLLABORATION_ID1;
-        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
-
-        TabUiUtils.deleteSharedTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -229,12 +241,9 @@
                 .processDeleteSharedGroupAttempt(any(), any());
 
         when(mTabModel.getTabById(anyInt())).thenReturn(null);
-        SavedTabGroup savedTabGroup = new SavedTabGroup();
-        savedTabGroup.title = GROUP_TITLE;
-        savedTabGroup.collaborationId = COLLABORATION_ID1;
-        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
+        createSyncGroup(COLLABORATION_ID1);
 
-        TabUiUtils.deleteSharedTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -248,14 +257,10 @@
         runWithValue(1, ActionConfirmationResult.CONFIRMATION_POSITIVE)
                 .when(mActionConfirmationManager)
                 .processDeleteSharedGroupAttempt(any(), any());
-
         when(mTab.getTabGroupId()).thenReturn(null);
-        SavedTabGroup savedTabGroup = new SavedTabGroup();
-        savedTabGroup.title = GROUP_TITLE;
-        savedTabGroup.collaborationId = COLLABORATION_ID1;
-        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
+        createSyncGroup(COLLABORATION_ID1);
 
-        TabUiUtils.deleteSharedTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -270,7 +275,7 @@
                 .when(mActionConfirmationManager)
                 .processDeleteSharedGroupAttempt(any(), any());
 
-        TabUiUtils.deleteSharedTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -284,13 +289,9 @@
         runWithValue(1, ActionConfirmationResult.CONFIRMATION_POSITIVE)
                 .when(mActionConfirmationManager)
                 .processDeleteSharedGroupAttempt(any(), any());
+        createSyncGroup(/* collaborationId= */ null);
 
-        SavedTabGroup savedTabGroup = new SavedTabGroup();
-        savedTabGroup.title = GROUP_TITLE;
-        savedTabGroup.collaborationId = null;
-        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
-
-        TabUiUtils.deleteSharedTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -300,46 +301,37 @@
     }
 
     @Test
-    public void testLeaveTabGroup_Positive() {
+    public void testLeaveSharedTabGroup_Positive() {
         runWithValue(1, ActionConfirmationResult.CONFIRMATION_POSITIVE)
                 .when(mActionConfirmationManager)
                 .processLeaveGroupAttempt(any(), any());
+        mockIdentity(EMAIL2, GAIA_ID2);
+        createSyncGroup(COLLABORATION_ID1);
+        createSharedGroup(GROUP_MEMBER1, GROUP_MEMBER2);
 
-        SavedTabGroup savedTabGroup = new SavedTabGroup();
-        savedTabGroup.title = GROUP_TITLE;
-        savedTabGroup.collaborationId = COLLABORATION_ID1;
-        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
-        CoreAccountInfo coreAccountInfo = CoreAccountInfo.createFromEmailAndGaiaId(EMAIL, GAIA_ID);
-        when(mIdentityManager.getPrimaryAccountInfo(anyInt())).thenReturn(coreAccountInfo);
-
-        TabUiUtils.leaveTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
                 mModalDialogManager,
                 TAB_ID);
         verify(mActionConfirmationManager).processLeaveGroupAttempt(eq(GROUP_TITLE), any());
-        verify(mDataSharingService)
-                .removeMember(eq(COLLABORATION_ID1), eq(EMAIL), mOutcomeCaptor.capture());
+        verify(mDataSharingService).leaveGroup(eq(COLLABORATION_ID1), mOutcomeCaptor.capture());
 
         mOutcomeCaptor.getValue().onResult(PeopleGroupActionOutcome.TRANSIENT_FAILURE);
         verify(mModalDialogManager).showDialog(any(), anyInt());
     }
 
     @Test
-    public void testLeaveTabGroup_Negative() {
+    public void testLeaveSharedTabGroup_Negative() {
         runWithValue(1, ActionConfirmationResult.CONFIRMATION_NEGATIVE)
                 .when(mActionConfirmationManager)
                 .processLeaveGroupAttempt(any(), any());
+        mockIdentity(EMAIL2, GAIA_ID2);
+        createSyncGroup(COLLABORATION_ID1);
+        createSharedGroup(GROUP_MEMBER1, GROUP_MEMBER2);
 
-        SavedTabGroup savedTabGroup = new SavedTabGroup();
-        savedTabGroup.title = GROUP_TITLE;
-        savedTabGroup.collaborationId = COLLABORATION_ID1;
-        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
-        CoreAccountInfo coreAccountInfo = CoreAccountInfo.createFromEmailAndGaiaId(EMAIL, GAIA_ID);
-        when(mIdentityManager.getPrimaryAccountInfo(anyInt())).thenReturn(coreAccountInfo);
-
-        TabUiUtils.leaveTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -350,20 +342,15 @@
     }
 
     @Test
-    public void testLeaveTabGroup_NullTab() {
+    public void testLeaveSharedTabGroup_NullTab() {
         runWithValue(1, ActionConfirmationResult.CONFIRMATION_POSITIVE)
                 .when(mActionConfirmationManager)
                 .processLeaveGroupAttempt(any(), any());
-
         when(mTabModel.getTabById(anyInt())).thenReturn(null);
-        SavedTabGroup savedTabGroup = new SavedTabGroup();
-        savedTabGroup.title = GROUP_TITLE;
-        savedTabGroup.collaborationId = COLLABORATION_ID1;
-        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
-        CoreAccountInfo coreAccountInfo = CoreAccountInfo.createFromEmailAndGaiaId(EMAIL, GAIA_ID);
-        when(mIdentityManager.getPrimaryAccountInfo(anyInt())).thenReturn(coreAccountInfo);
+        mockIdentity(EMAIL1, GAIA_ID1);
+        createSyncGroup(COLLABORATION_ID1);
 
-        TabUiUtils.leaveTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -373,16 +360,14 @@
     }
 
     @Test
-    public void testLeaveTabGroup_NullSavedTabGroup() {
+    public void testLeaveSharedTabGroup_NullSavedTabGroup() {
         runWithValue(1, ActionConfirmationResult.CONFIRMATION_POSITIVE)
                 .when(mActionConfirmationManager)
                 .processLeaveGroupAttempt(any(), any());
-
+        mockIdentity(EMAIL1, GAIA_ID1);
         when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(null);
-        CoreAccountInfo coreAccountInfo = CoreAccountInfo.createFromEmailAndGaiaId(EMAIL, GAIA_ID);
-        when(mIdentityManager.getPrimaryAccountInfo(anyInt())).thenReturn(coreAccountInfo);
 
-        TabUiUtils.leaveTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -392,18 +377,14 @@
     }
 
     @Test
-    public void testLeaveTabGroup_NullCoreAccountInfo() {
+    public void testLeaveSharedTabGroup_NullCoreAccountInfo() {
         runWithValue(1, ActionConfirmationResult.CONFIRMATION_POSITIVE)
                 .when(mActionConfirmationManager)
                 .processLeaveGroupAttempt(any(), any());
-
-        SavedTabGroup savedTabGroup = new SavedTabGroup();
-        savedTabGroup.title = GROUP_TITLE;
-        savedTabGroup.collaborationId = COLLABORATION_ID1;
-        when(mTabGroupSyncService.getGroup(any(LocalTabGroupId.class))).thenReturn(savedTabGroup);
+        createSyncGroup(COLLABORATION_ID1);
         when(mIdentityManager.getPrimaryAccountInfo(anyInt())).thenReturn(null);
 
-        TabUiUtils.leaveTabGroup(
+        TabUiUtils.exitSharedTabGroupWithDialog(
                 ApplicationProvider.getApplicationContext(),
                 mFilter,
                 mActionConfirmationManager,
@@ -457,4 +438,22 @@
         verify(mContentSensitivitySetter).onResult(false);
         histogramWatcherForFalseBucket.assertExpected();
     }
+
+    private SavedTabGroup createSyncGroup(String collaborationId) {
+        SavedTabGroup syncGroup = mSyncedGroupTestHelper.newTabGroup(SYNC_GROUP_ID1, TAB_GROUP_ID);
+        syncGroup.title = GROUP_TITLE;
+        syncGroup.collaborationId = collaborationId;
+        return syncGroup;
+    }
+
+    private GroupData createSharedGroup(GroupMember... members) {
+        GroupData sharedGroup = SharedGroupTestHelper.newGroupData(COLLABORATION_ID1, members);
+        when(mCollaborationService.getGroupData(eq(COLLABORATION_ID1))).thenReturn(sharedGroup);
+        return sharedGroup;
+    }
+
+    private void mockIdentity(String email, GaiaId gaiaId) {
+        CoreAccountInfo coreAccountInfo = CoreAccountInfo.createFromEmailAndGaiaId(email, gaiaId);
+        when(mIdentityManager.getPrimaryAccountInfo(anyInt())).thenReturn(coreAccountInfo);
+    }
 }
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
index b5207894..0ef65cb 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
@@ -35,7 +35,9 @@
 import static org.chromium.chrome.browser.tasks.tab_management.MessageCardViewProperties.MESSAGE_SERVICE_ACTION_PROVIDER;
 import static org.chromium.chrome.browser.tasks.tab_management.MessageCardViewProperties.MESSAGE_SERVICE_DISMISS_ACTION_PROVIDER;
 import static org.chromium.components.data_sharing.SharedGroupTestHelper.COLLABORATION_ID1;
+import static org.chromium.components.data_sharing.SharedGroupTestHelper.EMAIL1;
 import static org.chromium.components.data_sharing.SharedGroupTestHelper.EMAIL2;
+import static org.chromium.components.data_sharing.SharedGroupTestHelper.GAIA_ID1;
 import static org.chromium.components.data_sharing.SharedGroupTestHelper.GAIA_ID2;
 import static org.chromium.components.data_sharing.SharedGroupTestHelper.GROUP_MEMBER1;
 import static org.chromium.components.data_sharing.SharedGroupTestHelper.GROUP_MEMBER2;
@@ -1452,6 +1454,10 @@
     public void testDialogToolbarMenu_DeleteSharedGroup() {
         resetForDataSharing(/* isShared= */ true, GROUP_MEMBER1);
 
+        CoreAccountInfo coreAccountInfo =
+                CoreAccountInfo.createFromEmailAndGaiaId(EMAIL1, GAIA_ID1);
+        when(mIdentityManager.getPrimaryAccountInfo(anyInt())).thenReturn(coreAccountInfo);
+
         mMediator.onToolbarMenuItemClick(R.id.delete_shared_group, TAB1_ID, COLLABORATION_ID1);
         verify(mActionConfirmationManager).processDeleteSharedGroupAttempt(eq(GROUP_TITLE), any());
         assertEquals(1, mActionTester.getActionCount("TabGridDialogMenu.DeleteShared"));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
index d554c7a..9f5ba96 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
@@ -174,28 +174,13 @@
 
     /**
      * Creates a {@link LayoutTab}.
-     * @param id              The id of the reference {@link Tab} in the {@link TabModel}.
-     * @param isIncognito     Whether the new tab is incognito.
-     * @return                The newly created {@link LayoutTab}.
+     *
+     * @param id The id of the reference {@link Tab} in the {@link TabModel}.
+     * @param isIncognito Whether the new tab is incognito.
+     * @return The newly created {@link LayoutTab}.
      */
     public LayoutTab createLayoutTab(int id, boolean isIncognito) {
-        return createLayoutTab(id, isIncognito, -1.f, -1.f);
-    }
-
-    /**
-     * Creates a {@link LayoutTab}.
-     * @param id               The id of the reference {@link Tab} in the {@link TabModel}.
-     * @param isIncognito      Whether the new tab is incognito.
-     * @param maxContentWidth  The max content width of the tab.  Negative numbers will use the
-     *                         original content width.
-     * @param maxContentHeight The max content height of the tab.  Negative numbers will use the
-     *                         original content height.
-     * @return                 The newly created {@link LayoutTab}.
-     */
-    public LayoutTab createLayoutTab(
-            int id, boolean isIncognito, float maxContentWidth, float maxContentHeight) {
-        LayoutTab layoutTab =
-                mUpdateHost.createLayoutTab(id, isIncognito, maxContentWidth, maxContentHeight);
+        LayoutTab layoutTab = mUpdateHost.createLayoutTab(id, isIncognito);
         initLayoutTabFromHost(layoutTab);
         return layoutTab;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
index 5155c39f..6b8cdb45 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
@@ -1007,8 +1007,7 @@
     }
 
     @Override
-    public LayoutTab createLayoutTab(
-            int id, boolean incognito, float maxContentWidth, float maxContentHeight) {
+    public LayoutTab createLayoutTab(int id, boolean incognito) {
         LayoutTab tab = mTabCache.get(id);
         if (tab == null) {
             tab = new LayoutTab(id, incognito, mHost.getWidth(), mHost.getHeight());
@@ -1016,9 +1015,6 @@
         } else {
             tab.init(mHost.getWidth(), mHost.getHeight());
         }
-        if (maxContentWidth > 0.f) tab.setMaxContentWidth(maxContentWidth);
-        if (maxContentHeight > 0.f) tab.setMaxContentHeight(maxContentHeight);
-
         return tab;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutUpdateHost.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutUpdateHost.java
index e77170ad..8a45f6d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutUpdateHost.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutUpdateHost.java
@@ -64,23 +64,18 @@
     /**
      * Creates or recycles a {@Link LayoutTab}.
      *
-     * @param id               The id of the reference tab in the
-     *                         {@link org.chromium.chrome.browser.tabmodel.TabModel}.
-     * @param incognito        Whether the new tab is incognito.
-     * @param maxContentWidth  The maximum layout width this tab can be.  Negative numbers will use
-     *                         the original content width.
-     * @param maxContentHeight The maximum layout height this tab can be.  Negative numbers will use
-     *                         the original content height.
-     * @return                 The created or recycled {@link LayoutTab}.
+     * @param id The id of the reference tab in the {@link
+     *     org.chromium.chrome.browser.tabmodel.TabModel}.
+     * @param incognito Whether the new tab is incognito.
+     * @return The created or recycled {@link LayoutTab}.
      */
-    LayoutTab createLayoutTab(
-            int id, boolean incognito, float maxContentWidth, float maxContentHeight);
+    LayoutTab createLayoutTab(int id, boolean incognito);
 
     /**
      * Notifies the host that the {@link LayoutTab} is no longer needed by the layout.
      *
-     * @param id The id of the reference tab in the
-     *           {@link org.chromium.chrome.browser.tabmodel.TabModel}.
+     * @param id The id of the reference tab in the {@link
+     *     org.chromium.chrome.browser.tabmodel.TabModel}.
      */
     void releaseTabLayout(int id);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/LayoutTab.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/LayoutTab.java
index 2ae8706..b57f2ba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/LayoutTab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/LayoutTab.java
@@ -318,13 +318,6 @@
     }
 
     /**
-     * @return The original unclamped width (not scaled) of the tab contents texture.
-     */
-    public float getUnclampedOriginalContentHeight() {
-        return get(ORIGINAL_CONTENT_HEIGHT_IN_DP);
-    }
-
-    /**
      * @return The width of the drawn content (clipped and scaled).
      */
     public float getFinalContentWidth() {
@@ -332,27 +325,6 @@
     }
 
     /**
-     * @return The maximum height the content can be.
-     */
-    public float getMaxContentHeight() {
-        return get(MAX_CONTENT_HEIGHT);
-    }
-
-    /**
-     * @param width The maximum width the content can be.
-     */
-    public void setMaxContentWidth(float width) {
-        set(MAX_CONTENT_WIDTH, width);
-    }
-
-    /**
-     * @param height The maximum height the content can be.
-     */
-    public void setMaxContentHeight(float height) {
-        set(MAX_CONTENT_HEIGHT, height);
-    }
-
-    /**
      * @return The id of the tab, same as the id from the Tab in TabModel.
      */
     public int getId() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayoutUnitTest.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayoutUnitTest.java
index 6025c7f..a096327 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayoutUnitTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/NewTabAnimationLayoutUnitTest.java
@@ -11,7 +11,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
@@ -128,8 +127,7 @@
         when(mNewTab.getId()).thenReturn(NEW_TAB_ID);
 
         when(mLayoutTab.isInitFromHostNeeded()).thenReturn(true);
-        when(mUpdateHost.createLayoutTab(anyInt(), anyBoolean(), anyFloat(), anyFloat()))
-                .thenReturn(mLayoutTab);
+        when(mUpdateHost.createLayoutTab(anyInt(), anyBoolean())).thenReturn(mLayoutTab);
 
         mActivityScenarioRule.getScenario().onActivity(this::onActivity);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabGroupContextMenuCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabGroupContextMenuCoordinator.java
index dbd51b5..43d214e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabGroupContextMenuCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabGroupContextMenuCoordinator.java
@@ -219,7 +219,7 @@
                 dataSharingTabManager.showRecentActivity(activity, collaborationId);
                 recordUserAction("RecentActivity");
             } else if (menuId == R.id.delete_shared_group) {
-                TabUiUtils.deleteSharedTabGroup(
+                TabUiUtils.exitSharedTabGroupWithDialog(
                         activity,
                         tabGroupModelFilter,
                         actionConfirmationManager,
@@ -227,7 +227,7 @@
                         tabId);
                 recordUserAction("DeleteSharedGroup");
             } else if (menuId == R.id.leave_group) {
-                TabUiUtils.leaveTabGroup(
+                TabUiUtils.exitSharedTabGroupWithDialog(
                         activity,
                         tabGroupModelFilter,
                         actionConfirmationManager,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
index 5a23955..a78ad860 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
@@ -49,6 +49,7 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.PageTransition;
+import org.chromium.ui.widget.Toast;
 import org.chromium.url.GURL;
 import org.chromium.url.Origin;
 
@@ -358,6 +359,11 @@
                 mActivity.startActivity(intent, startActivityOptions);
                 finish(FinishReason.OPEN_IN_BROWSER);
             } else {
+                Toast.makeText(
+                                mActivity,
+                                R.string.custom_tab_cant_perform_action_toast,
+                                Toast.LENGTH_LONG)
+                        .show();
                 // Silently crash to investigate https://crbug.com/384992232
                 boolean isPdf = tab.isNativePage() && tab.getNativePage().isPdf();
                 String logMessage =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index 950c259..a0322d9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -1557,7 +1557,7 @@
             if (mState == STATE_TITLE_ONLY || mCurrentlyShowingBranding) return;
 
             int securityIconResource = 0;
-            if (!shouldNestSecurityIcon() || !isSecureLevel()) {
+            if (!shouldNestSecurityIcon() || !isSecureOrNeutralLevel()) {
                 securityIconResource =
                         mLocationBarDataProvider.getSecurityIconResource(
                                 DeviceFormFactor.isNonMultiDisplayContextOnTablet(getContext()));
@@ -1579,10 +1579,11 @@
         }
 
         /** Returns whether the current security level is considered secure. */
-        private boolean isSecureLevel() {
+        private boolean isSecureOrNeutralLevel() {
             @ConnectionSecurityLevel
             int securityLevel = mLocationBarDataProvider.getSecurityLevel();
-            return securityLevel == ConnectionSecurityLevel.SECURE
+            return securityLevel == ConnectionSecurityLevel.NONE
+                    || securityLevel == ConnectionSecurityLevel.SECURE
                     || securityLevel == ConnectionSecurityLevel.SECURE_WITH_POLICY_INSTALLED_CERT;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/hub/HubLayoutUnitTest.java b/chrome/android/java/src/org/chromium/chrome/browser/hub/HubLayoutUnitTest.java
index 65353ff..a02cf8a3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/hub/HubLayoutUnitTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/hub/HubLayoutUnitTest.java
@@ -13,7 +13,6 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -255,14 +254,10 @@
         doAnswer(
                         invocation -> {
                             var args = invocation.getArguments();
-                            return new LayoutTab(
-                                    (Integer) args[0],
-                                    (Boolean) args[1],
-                                    ((Float) args[2]).intValue(),
-                                    ((Float) args[3]).intValue());
+                            return new LayoutTab((Integer) args[0], (Boolean) args[1], -1, -1);
                         })
                 .when(mUpdateHost)
-                .createLayoutTab(anyInt(), anyBoolean(), anyFloat(), anyFloat());
+                .createLayoutTab(anyInt(), anyBoolean());
         when(mTab.getId()).thenReturn(TAB_ID);
         when(mTab.isNativePage()).thenReturn(false);
         when(mTabModelSelector.getCurrentTab()).thenReturn(mTab);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/BottomAttachedUiObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/BottomAttachedUiObserver.java
index 438b497..ded0b645 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/BottomAttachedUiObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/BottomAttachedUiObserver.java
@@ -13,10 +13,12 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.browser.browser_controls.BottomControlsStacker;
+import org.chromium.chrome.browser.browser_controls.BottomControlsStacker.LayerType;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelStateProvider;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.keyboard_accessory.AccessorySheetVisualStateProvider;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsVisualState;
@@ -31,7 +33,7 @@
 
 /**
  * An observer class that listens for changes in UI components that are attached to the bottom of
- * the screen, bordering the OS navigation bar. This class then aggregates that information and
+ * the screen, bordering the navigation bar area. This class then aggregates that information and
  * notifies its own observers of properties of the UI currently bordering ("attached to") the
  * navigation bar.
  */
@@ -74,6 +76,7 @@
 
     private final BrowserControlsStateProvider mBrowserControlsStateProvider;
     private int mBottomControlsHeight;
+    private int mBottomControlsMinHeight;
     private @Nullable @ColorInt Integer mBottomControlsColor;
     private boolean mUseBottomControlsColor;
 
@@ -320,28 +323,58 @@
             boolean bottomControlsMinHeightChanged,
             boolean requestNewFrame,
             boolean isVisibilityForced) {
-        updateBrowserControlsVisibility(
+        boolean hasOtherVisibleBottomControls =
                 // MiniPlayerMediator#shrinkBottomControls() sets the height to 1 and minHeight to 0
                 // when hiding, instead of setting the height to 0.
                 // TODO(b/320750931): Clean up once the MiniPlayerMediator has been improved.
                 mBottomControlsHeight > 1
-                        && bottomOffset < mBottomControlsHeight
                         && mBottomControlsStacker.hasVisibleLayersOtherThan(
-                                BottomControlsStacker.LayerType.BOTTOM_CHIN));
+                                BottomControlsStacker.LayerType.BOTTOM_CHIN);
+
+        if (!hasOtherVisibleBottomControls) {
+            updateUseBottomControlsColor(false);
+            return;
+        }
+
+        boolean useBrowserControlsColor = bottomOffset < mBottomControlsHeight;
+
+        // When bottom chin constraint exists, the chin will have the same coloring mechanism as
+        // the OS navigation bar as if E2E is disabled.
+        if (EdgeToEdgeUtils.isEdgeToEdgeBottomChinEnabled()
+                && ChromeFeatureList.isEnabled(
+                        ChromeFeatureList.EDGE_TO_EDGE_SAFE_AREA_CONSTRAINT)) {
+            boolean hasScrollablePortion =
+                    bottomOffset < mBottomControlsHeight - mBottomControlsMinHeight;
+            boolean chinNotScrollable =
+                    mBottomControlsStacker.isLayerNonScrollable(LayerType.BOTTOM_CHIN);
+            boolean hasOtherNonScrollableLayer =
+                    mBottomControlsStacker.hasMultipleNonScrollableLayer();
+            boolean hasFixedBrowserControlsAttached =
+                    chinNotScrollable && hasOtherNonScrollableLayer;
+
+            useBrowserControlsColor = hasScrollablePortion || hasFixedBrowserControlsAttached;
+        }
+
+        updateUseBottomControlsColor(useBrowserControlsColor);
     }
 
     @Override
     public void onBottomControlsHeightChanged(
             int bottomControlsHeight, int bottomControlsMinHeight) {
         mBottomControlsHeight = bottomControlsHeight;
+        mBottomControlsMinHeight = bottomControlsMinHeight;
 
         // MiniPlayerMediator#shrinkBottomControls() sets the height to 1 and minHeight to 0 when
         // hiding, instead of setting the height to 0.
         // TODO(b/320750931): Clean up once the MiniPlayerMediator has been improved.
-        updateBrowserControlsVisibility(
+        updateUseBottomControlsColor(
                 mBottomControlsHeight > 1
                         && mBottomControlsStacker.hasVisibleLayersOtherThan(
                                 BottomControlsStacker.LayerType.BOTTOM_CHIN));
+
+        // BottomChin constraint does not impact this method, since when control's height changes,
+        // #hasVisibleLayersOtherThan(BOTTOM_CHIN) already covers whether bottom chin will have
+        // a colored layer attached.
     }
 
     @Override
@@ -350,7 +383,7 @@
         updateBottomAttachedColor();
     }
 
-    private void updateBrowserControlsVisibility(boolean useBottomControlsColor) {
+    private void updateUseBottomControlsColor(boolean useBottomControlsColor) {
         if (useBottomControlsColor == mUseBottomControlsColor) {
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelRemover.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelRemover.java
index 2a18140..9d9f2c0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelRemover.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelRemover.java
@@ -15,31 +15,25 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.Supplier;
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory;
 import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory;
 import org.chromium.chrome.browser.data_sharing.DataSharingTabGroupUtils;
 import org.chromium.chrome.browser.data_sharing.DataSharingTabGroupUtils.GroupsPendingDestroy;
 import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabSelectionType;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncServiceFactory;
 import org.chromium.chrome.browser.tasks.tab_management.ActionConfirmationManager;
 import org.chromium.chrome.browser.tasks.tab_management.TabShareUtils;
+import org.chromium.chrome.browser.tasks.tab_management.TabUiUtils;
 import org.chromium.components.browser_ui.widget.ActionConfirmationResult;
 import org.chromium.components.collaboration.CollaborationService;
 import org.chromium.components.data_sharing.DataSharingService;
-import org.chromium.components.data_sharing.PeopleGroupActionOutcome;
 import org.chromium.components.data_sharing.member_role.MemberRole;
-import org.chromium.components.signin.base.CoreAccountInfo;
-import org.chromium.components.signin.identitymanager.ConsentLevel;
-import org.chromium.components.signin.identitymanager.IdentityManager;
 import org.chromium.components.tab_group_sync.LocalTabGroupId;
 import org.chromium.components.tab_group_sync.SavedTabGroup;
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
 import org.chromium.ui.modaldialog.ModalDialogManager;
-import org.chromium.ui.modaldialog.ModalDialogUtils;
 
 import java.util.List;
 
@@ -220,54 +214,21 @@
 
     private void leaveOrDeleteCollaboration(@NonNull CollaborationInfo collaborationInfo) {
         assert collaborationInfo.isValid();
+
         // TODO(crbug.com/376907248): Remove DataSharingService from here once these operations
         // are supported by CollaborationService.
+
+        String collaborationId = collaborationInfo.collaborationId;
+        @MemberRole int memberRole = collaborationInfo.memberRole;
         @Nullable DataSharingService dataSharingService = getDataSharingService();
         if (dataSharingService == null) {
-            showGenericErrorDialog(mContext, mModalDialogManager);
-            return;
-        }
-        if (collaborationInfo.memberRole == MemberRole.OWNER) {
-            dataSharingService.deleteGroup(
-                    collaborationInfo.collaborationId,
-                    bindOnLeaveOrDeleteGroup(mContext, mModalDialogManager));
-        } else if (collaborationInfo.memberRole == MemberRole.MEMBER) {
-            IdentityManager identityManager =
-                    IdentityServicesProvider.get().getIdentityManager(getProfile());
-            @Nullable
-            CoreAccountInfo account = identityManager.getPrimaryAccountInfo(ConsentLevel.SIGNIN);
-            if (account == null) {
-                showGenericErrorDialog(mContext, mModalDialogManager);
-                return;
-            }
-            dataSharingService.removeMember(
-                    collaborationInfo.collaborationId,
-                    account.getEmail(),
-                    bindOnLeaveOrDeleteGroup(mContext, mModalDialogManager));
+            TabUiUtils.showGenericErrorDialog(mContext, mModalDialogManager);
         } else {
-            showGenericErrorDialog(mContext, mModalDialogManager);
+            TabUiUtils.exitCollaborationWithoutWarning(
+                    mContext, mModalDialogManager, dataSharingService, collaborationId, memberRole);
         }
     }
 
-    private static Callback<Integer> bindOnLeaveOrDeleteGroup(
-            Context context, ModalDialogManager modalDialogManager) {
-        return (@PeopleGroupActionOutcome Integer outcome) -> {
-            if (outcome != PeopleGroupActionOutcome.SUCCESS) {
-                showGenericErrorDialog(context, modalDialogManager);
-            }
-        };
-    }
-
-    private static void showGenericErrorDialog(
-            Context context, ModalDialogManager modalDialogManager) {
-        ModalDialogUtils.showOneButtonConfirmation(
-                modalDialogManager,
-                context.getResources(),
-                R.string.data_sharing_generic_failure_title,
-                R.string.data_sharing_generic_failure_description,
-                R.string.data_sharing_invitation_failure_button);
-    }
-
     /** Contains info about a collaboration. */
     private static class CollaborationInfo {
         public final @MemberRole int memberRole;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelRemoverUnitTest.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelRemoverUnitTest.java
index d104833..e7a9107 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelRemoverUnitTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelRemoverUnitTest.java
@@ -277,9 +277,7 @@
         mOnResultCaptor.getValue().onResult(ActionConfirmationResult.CONFIRMATION_NEGATIVE);
 
         verify(mTabModel).commitAllTabClosures();
-
-        verify(mDataSharingService)
-                .removeMember(eq(COLLABORATION_ID), eq(EMAIL), mOnResultCaptor.capture());
+        verify(mDataSharingService).leaveGroup(eq(COLLABORATION_ID), mOnResultCaptor.capture());
 
         mOnResultCaptor.getValue().onResult(PeopleGroupActionOutcome.PERSISTENT_FAILURE);
         verify(mModalDialogManager).showDialog(any(), anyInt());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/desktop_windowing/AppHeaderCoordinatorBrowserTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/desktop_windowing/AppHeaderCoordinatorBrowserTest.java
index 6573bd57..26667b6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/desktop_windowing/AppHeaderCoordinatorBrowserTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/desktop_windowing/AppHeaderCoordinatorBrowserTest.java
@@ -408,6 +408,29 @@
                 activity.getActivityTab().getWebContents(),
                 "document.querySelector('input').blur()");
 
+        // Verify that the root view bottom padding uses the nav bar bottom inset.
+        CriteriaHelper.pollUiThread(
+                () -> {
+                    var navBarBottomInset =
+                            insetObserver
+                                    .getLastRawWindowInsets()
+                                    .getInsets(WindowInsetsCompat.Type.navigationBars())
+                                    .bottom;
+                    Criteria.checkThat(rootView.getPaddingBottom(), Matchers.is(navBarBottomInset));
+                });
+
+        // Dispatch window insets to simulate no overlap of the app window with the nav bar.
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    insetObserver.onApplyWindowInsets(
+                            rootView,
+                            new WindowInsetsCompat.Builder()
+                                    .setInsets(
+                                            WindowInsetsCompat.Type.navigationBars(),
+                                            Insets.of(0, 0, 0, 0))
+                                    .build());
+                });
+
         // Verify that the root view bottom padding is reset.
         CriteriaHelper.pollUiThread(
                 () -> Criteria.checkThat(rootView.getPaddingBottom(), Matchers.is(0)));
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
index ce97d71..4ff9e94 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
@@ -606,12 +606,12 @@
     })
     public void testSecurityIconShown() {
         when(mLocationBarModel.getSecurityIconResource(anyBoolean()))
-                .thenReturn(R.drawable.omnibox_info);
-        when(mLocationBarModel.getSecurityLevel()).thenReturn(ConnectionSecurityLevel.NONE);
+                .thenReturn(R.drawable.omnibox_not_secure_warning);
+        when(mLocationBarModel.getSecurityLevel()).thenReturn(ConnectionSecurityLevel.WARNING);
 
         mLocationBar.onSecurityStateChanged();
 
-        verify(mAnimationDelegate).updateSecurityButton(R.drawable.omnibox_info);
+        verify(mAnimationDelegate).updateSecurityButton(R.drawable.omnibox_not_secure_warning);
     }
 
     private void assertUrlAndTitleVisible(boolean titleVisible, boolean urlVisible) {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/BottomAttachedUiObserverTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/BottomAttachedUiObserverTest.java
index d68bfb5..cfb9074 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/BottomAttachedUiObserverTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/BottomAttachedUiObserverTest.java
@@ -29,6 +29,8 @@
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features;
+import org.chromium.base.test.util.Features.DisableFeatures;
+import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.chrome.browser.browser_controls.BottomControlsStacker;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
@@ -49,6 +51,7 @@
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     private static final int BOTTOM_CONTROLS_HEIGHT = 100;
+    private static final int BOTTOM_CONTROLS_MIN_HEIGHT_MULTIPLE_LAYER = 80;
     private static final int BOTTOM_CHIN_HEIGHT = 60;
     private static final int BROWSER_CONTROLS_COLOR = Color.RED;
     private static final int SNACKBAR_COLOR = Color.GREEN;
@@ -141,6 +144,7 @@
     }
 
     @Test
+    @DisableFeatures(ChromeFeatureList.EDGE_TO_EDGE_SAFE_AREA_CONSTRAINT)
     public void testAdaptsColorToBrowserControls() {
         mColorChangeObserver.assertState(null, false, false);
         when(mBottomControlsStacker.hasVisibleLayersOtherThan(
@@ -185,6 +189,133 @@
     }
 
     @Test
+    @EnableFeatures({
+        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
+        ChromeFeatureList.EDGE_TO_EDGE_SAFE_AREA_CONSTRAINT
+    })
+    public void testAdaptsColorToBrowserControls_bottomChinConstraint_bottomChinNonScrollable() {
+        mColorChangeObserver.assertState(null, false, false);
+        when(mBottomControlsStacker.hasVisibleLayersOtherThan(
+                        eq(BottomControlsStacker.LayerType.BOTTOM_CHIN)))
+                .thenReturn(true);
+
+        when(mBottomControlsStacker.isLayerNonScrollable(
+                        eq(BottomControlsStacker.LayerType.BOTTOM_CHIN)))
+                .thenReturn(true);
+        when(mBottomControlsStacker.hasMultipleNonScrollableLayer()).thenReturn(false);
+
+        // Show bottom controls, bottom chin is non-scrollable.
+        mBottomAttachedUiObserver.onBottomControlsBackgroundColorChanged(BROWSER_CONTROLS_COLOR);
+        mBottomAttachedUiObserver.onBottomControlsHeightChanged(
+                BOTTOM_CONTROLS_HEIGHT, BOTTOM_CHIN_HEIGHT);
+        mColorChangeObserver.assertState(BROWSER_CONTROLS_COLOR, false, false);
+
+        // Scroll off bottom controls fully. Browser controls should no longer be used.
+        mBottomAttachedUiObserver.onControlsOffsetChanged(
+                0,
+                0,
+                false,
+                BOTTOM_CONTROLS_HEIGHT - BOTTOM_CHIN_HEIGHT,
+                BOTTOM_CHIN_HEIGHT,
+                false,
+                false,
+                false);
+        mColorChangeObserver.assertState(null, false, false);
+    }
+
+    @Test
+    @EnableFeatures({
+        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
+        ChromeFeatureList.EDGE_TO_EDGE_SAFE_AREA_CONSTRAINT
+    })
+    public void testAdaptsColorToBrowserControls_bottomChinConstraint_multipleNonScrollableLayer() {
+        mColorChangeObserver.assertState(null, false, false);
+        when(mBottomControlsStacker.hasVisibleLayersOtherThan(
+                        eq(BottomControlsStacker.LayerType.BOTTOM_CHIN)))
+                .thenReturn(true);
+
+        when(mBottomControlsStacker.isLayerNonScrollable(
+                        eq(BottomControlsStacker.LayerType.BOTTOM_CHIN)))
+                .thenReturn(true);
+        when(mBottomControlsStacker.hasMultipleNonScrollableLayer()).thenReturn(true);
+
+        // Show bottom controls, but only with the bottom chin. Color should be null.
+        mBottomAttachedUiObserver.onBottomControlsBackgroundColorChanged(BROWSER_CONTROLS_COLOR);
+        mBottomAttachedUiObserver.onBottomControlsHeightChanged(
+                BOTTOM_CONTROLS_HEIGHT, BOTTOM_CONTROLS_MIN_HEIGHT_MULTIPLE_LAYER);
+        mColorChangeObserver.assertState(BROWSER_CONTROLS_COLOR, false, false);
+
+        // Scroll off bottom controls fully. Browser controls should still be used.
+        mBottomAttachedUiObserver.onControlsOffsetChanged(
+                0,
+                0,
+                false,
+                BOTTOM_CONTROLS_HEIGHT - BOTTOM_CONTROLS_MIN_HEIGHT_MULTIPLE_LAYER,
+                BOTTOM_CONTROLS_MIN_HEIGHT_MULTIPLE_LAYER,
+                false,
+                false,
+                false);
+        mColorChangeObserver.assertState(BROWSER_CONTROLS_COLOR, false, false);
+    }
+
+    @Test
+    @EnableFeatures({
+        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
+        ChromeFeatureList.EDGE_TO_EDGE_SAFE_AREA_CONSTRAINT
+    })
+    public void testAdaptsColorToBrowserControls_bottomChinConstraint_bottomChinScrollable() {
+        mColorChangeObserver.assertState(null, false, false);
+        when(mBottomControlsStacker.hasVisibleLayersOtherThan(
+                        eq(BottomControlsStacker.LayerType.BOTTOM_CHIN)))
+                .thenReturn(true);
+
+        when(mBottomControlsStacker.isLayerNonScrollable(
+                        eq(BottomControlsStacker.LayerType.BOTTOM_CHIN)))
+                .thenReturn(false);
+        when(mBottomControlsStacker.hasMultipleNonScrollableLayer()).thenReturn(false);
+
+        // Show bottom controls. Color should be BROWSER_CONTROLS_COLOR.
+        mBottomAttachedUiObserver.onBottomControlsBackgroundColorChanged(BROWSER_CONTROLS_COLOR);
+        mBottomAttachedUiObserver.onBottomControlsHeightChanged(BOTTOM_CONTROLS_HEIGHT, 0);
+        mColorChangeObserver.assertState(BROWSER_CONTROLS_COLOR, false, false);
+
+        // Scroll off bottom controls fully. Browser controls should no longer be used.
+        mBottomAttachedUiObserver.onControlsOffsetChanged(
+                0, 0, false, BOTTOM_CONTROLS_HEIGHT, 0, false, false, false);
+        mColorChangeObserver.assertState(null, false, false);
+    }
+
+    @Test
+    @EnableFeatures({
+        ChromeFeatureList.EDGE_TO_EDGE_BOTTOM_CHIN,
+        ChromeFeatureList.EDGE_TO_EDGE_SAFE_AREA_CONSTRAINT
+    })
+    public void testAdaptsColorToBrowserControls_bottomChinConstraint_bottomChinOnly() {
+        mColorChangeObserver.assertState(null, false, false);
+        when(mBottomControlsStacker.isLayerNonScrollable(
+                        eq(BottomControlsStacker.LayerType.BOTTOM_CHIN)))
+                .thenReturn(true);
+        when(mBottomControlsStacker.hasMultipleNonScrollableLayer()).thenReturn(false);
+
+        // Assume some other browser controls were visible, but then is removed.
+        when(mBottomControlsStacker.hasVisibleLayersOtherThan(
+                        eq(BottomControlsStacker.LayerType.BOTTOM_CHIN)))
+                .thenReturn(true);
+        mBottomAttachedUiObserver.onBottomControlsBackgroundColorChanged(BROWSER_CONTROLS_COLOR);
+        mBottomAttachedUiObserver.onBottomControlsHeightChanged(
+                BOTTOM_CONTROLS_HEIGHT, BOTTOM_CHIN_HEIGHT);
+        mColorChangeObserver.assertState(BROWSER_CONTROLS_COLOR, false, false);
+
+        // Then the control is removed, the chin is set as the only layer
+        when(mBottomControlsStacker.hasVisibleLayersOtherThan(
+                        eq(BottomControlsStacker.LayerType.BOTTOM_CHIN)))
+                .thenReturn(false);
+        mBottomAttachedUiObserver.onBottomControlsHeightChanged(
+                BOTTOM_CHIN_HEIGHT, BOTTOM_CHIN_HEIGHT);
+        mColorChangeObserver.assertState(null, false, false);
+    }
+
+    @Test
     public void testAdaptsColorToSnackbars() {
         mColorChangeObserver.assertState(null, false, false);
 
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 165e346..6bf71a5 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -7104,18 +7104,6 @@
   <message name="IDS_OS_SETTINGS_SECURE_DNS_DESCRIPTION" desc="Description of secure DNS in Privacy options">
     Make it harder for people with access to your internet traffic to see which sites you visit. <ph name="PRODUCT_NAME">$1<ex>ChromeOS</ex></ph> uses a secure connection to look up a site's IP address in the DNS (Domain Name System).
   </message>
-  <message name="IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TITLE" desc="Text for secure DNS dialog title in Privacy options for ChromeOS">
-    Turn off URL encryption?
-  </message>
-  <message name="IDS_OS_SETTINGS_SECURE_DNS_DIALOG_BODY" desc="Text for secure DNS dialog body in Privacy options for ChromeOS">
-    Those with access to your internet traffic can see what websites you visit
-  </message>
-  <message name="IDS_OS_SETTINGS_SECURE_DNS_DIALOG_CANCEL" desc="Text for secure DNS dialog to cancel action in Privacy options for ChromeOS">
-    Cancel
-  </message>
-  <message name="IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TURN_OFF" desc="Text for secure DNS dialog to turn off DNS in Privacy options for ChromeOS">
-    Turn off
-  </message>
   <message name="IDS_OS_SETTINGS_SECURE_DNS_AUTOMATIC_MODE_DESCRIPTION" desc="Text of the select option that puts secure DNS in Network Default mode">
     Network default
   </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_BODY.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_BODY.png.sha1
deleted file mode 100644
index 51d7148..0000000
--- a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_BODY.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-8e19c797d6306ed565077f8699074c07b84f3161
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_CANCEL.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_CANCEL.png.sha1
deleted file mode 100644
index fed53dd..0000000
--- a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_CANCEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b6b011b608a63740d7f09c6c4080194de1c468af
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TITLE.png.sha1
deleted file mode 100644
index 3b1f7987..0000000
--- a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-857a0d191e88ff0fb09bb06c393b15a9c388dbf5
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TURN_OFF.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TURN_OFF.png.sha1
deleted file mode 100644
index 436db7e8..0000000
--- a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TURN_OFF.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e7a8e01efb3035dbac92bfd70c4349e59e7c4ebb
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index bdfa88d..3cf950f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1993,6 +1993,9 @@
     kNtpMostRelevantTabResumptionModuleRemoteTabData[] = {
         {ntp_features::kNtpMostRelevantTabResumptionModuleDataParam, "2"}};
 const FeatureEntry::FeatureParam
+    kNtpMostRelevantTabResumptionModuleRemoteVisitsData[] = {
+        {ntp_features::kNtpMostRelevantTabResumptionModuleDataParam, "2,4"}};
+const FeatureEntry::FeatureParam
     kNtpMostRelevantTabResumptionModuleAllHistoryRemoteTabData[] = {
         {ntp_features::kNtpMostRelevantTabResumptionModuleDataParam, "2,3,4"}};
 const FeatureEntry::FeatureParam
@@ -2020,6 +2023,9 @@
          std::size(kNtpMostRelevantTabResumptionModuleTabData), nullptr},
         {"- Remote Tabs Only", kNtpMostRelevantTabResumptionModuleRemoteTabData,
          std::size(kNtpMostRelevantTabResumptionModuleRemoteTabData), nullptr},
+        {"- Remote Visits", kNtpMostRelevantTabResumptionModuleRemoteVisitsData,
+         std::size(kNtpMostRelevantTabResumptionModuleRemoteVisitsData),
+         nullptr},
         {"- All History, Remote Tabs",
          kNtpMostRelevantTabResumptionModuleAllHistoryRemoteTabData,
          std::size(kNtpMostRelevantTabResumptionModuleAllHistoryRemoteTabData),
@@ -9557,6 +9563,11 @@
      flag_descriptions::kTabStripGroupCollapseAndroidDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kTabStripGroupCollapseAndroid)},
 
+    {"tab-strip-group-drag-drop-android",
+     flag_descriptions::kTabStripGroupDragDropAndroidName,
+     flag_descriptions::kTabStripGroupDragDropAndroidDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kTabStripGroupDragDropAndroid)},
+
     {"tab-strip-group-reorder-android",
      flag_descriptions::kTabStripGroupReorderAndroidName,
      flag_descriptions::kTabStripGroupReorderAndroidDescription, kOsAndroid,
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index b7fd754..ae2762e93 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -2139,8 +2139,6 @@
 }
 
 IN_PROC_BROWSER_TEST_P(WebViewVisibilityTest, Shim_TestHiddenBeforeNavigation) {
-  SKIP_FOR_MPARCH();  // TODO(crbug.com/40202416): Enable test for MPArch.
-
   TestHelper("testHiddenBeforeNavigation", "web_view/shim", NO_TEST_SERVER);
 }
 
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
index f5b089ab7..08518dd 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
@@ -85,82 +85,6 @@
 
 }  // namespace
 
-// -----------------------------------------------------------------------------
-// DisplayOverlayController::FocusCycler:
-
-class DisplayOverlayController::FocusCycler {
- public:
-  FocusCycler() = default;
-  ~FocusCycler() = default;
-
-  // Adds `widget` to `widget_list_` if `widget` is visible and not in
-  // `widget_list_`. Otherwise, removes `widget` from `widget_list_`.
-  void RefreshWidget(views::Widget* widget) {
-    if (widget->IsVisible()) {
-      AddWidget(widget);
-    } else {
-      RemoveWidget(widget);
-    }
-  }
-
-  void MaybeChangeFocusWidget(ui::KeyEvent& event) {
-    // Only tab pressed is checked because the focus change is triggered by the
-    // tab pressed event. No need to change widget focus again on tab key
-    // released event.
-    if (event.type() == ui::EventType::kKeyReleased ||
-        !views::FocusManager::IsTabTraversalKeyEvent(event)) {
-      return;
-    }
-
-    const bool reverse = event.IsShiftDown();
-    auto* target_widget = views::Widget::GetWidgetForNativeWindow(
-        static_cast<aura::Window*>(event.target()));
-    auto* focus_manager = target_widget->GetFocusManager();
-
-    // Once there is next focusable view (dont_loop==true), it means the current
-    // focus is not the first or the last focusable view, so it doesn't need to
-    // change focus to the next widget.
-    if (focus_manager->GetNextFocusableView(
-            /*starting_view=*/focus_manager->GetFocusedView(),
-            /*starting_widget=*/target_widget, /*reverse=*/reverse,
-            /*dont_loop=*/true)) {
-      return;
-    }
-
-    // Change focus to the next widget.
-    if (auto* next_widget =
-            GetNextWidgetToFocus(widget_list_, target_widget, reverse)) {
-      next_widget->GetFocusManager()->AdvanceFocus(reverse);
-      // Change the event target.
-      ui::Event::DispatcherApi(&event).set_target(
-          next_widget->GetNativeWindow());
-    }
-  }
-
- private:
-  void AddWidget(views::Widget* widget) {
-    if (auto it = std::find(widget_list_.begin(), widget_list_.end(), widget);
-        it == widget_list_.end()) {
-      widget_list_.emplace_back(widget);
-      UpdateAccessibilityTree(widget_list_);
-    }
-  }
-
-  void RemoveWidget(views::Widget* widget) {
-    if (auto it = std::find(widget_list_.begin(), widget_list_.end(), widget);
-        it != widget_list_.end()) {
-      widget_list_.erase(it);
-      UpdateAccessibilityTree(widget_list_);
-    }
-  }
-
-  // Only contains visible and unique widgets.
-  std::vector<views::Widget*> widget_list_;
-};
-
-// -----------------------------------------------------------------------------
-// DisplayOverlayController:
-
 DisplayOverlayController::DisplayOverlayController(
     TouchInjector* touch_injector)
     : touch_injector_(touch_injector) {
@@ -176,7 +100,6 @@
 DisplayOverlayController::~DisplayOverlayController() {
   touch_injector_->set_display_overlay_controller(nullptr);
 
-  widget_observations_.RemoveAllObservations();
   touch_injector_->window()->RemoveObserver(this);
   RemoveAllWidgets();
 
@@ -215,7 +138,6 @@
       RemoveActionHighlightWidget();
       RemoveDeleteEditShortcutWidget();
       RemoveEditingListWidget();
-      RemoveFocusCycler();
       if (GetActiveActionsSize() == 0u) {
         // If there is no active action in `kView` mode, it doesn't create
         // `input_mapping_widget_` to save resources. When switching from
@@ -247,16 +169,11 @@
         AddInputMappingWidget();
       }
 
-      AddFocusCycler();
-
       // No matter if the mapping hint is hidden, `input_mapping_widget_` needs
       // to show up in `kEdit` mode.
       SetInputMappingVisible(/*visible=*/true);
 
-      // Since `focus_cycler_` was added in `kEdit` mode after
-      // `input_mapping_widget_` in general. Refresh `input_mapping_widget_` to
-      // make sure it is added in `focus_cycler_`.
-      focus_cycler_->RefreshWidget(input_mapping_widget_.get());
+      UpdateAccessibilityTree(GetTraversableWidgets());
 
       if (auto* input_mapping = GetInputMapping()) {
         input_mapping->SetDisplayMode(mode);
@@ -409,7 +326,6 @@
       CreateTransientWidget(input_mapping_widget_->GetNativeWindow(),
                             /*widget_name=*/kButtonOptionsMenu,
                             /*accept_events=*/true);
-  widget_observations_.AddObservation(button_options_widget_.get());
   button_options_widget_->SetContentsView(
       std::make_unique<ButtonOptionsMenu>(this, action));
   UpdateButtonOptionsMenuWidgetBounds();
@@ -420,6 +336,7 @@
   SetEditingListVisibility(/*visible=*/false);
 
   button_options_widget_->Show();
+  UpdateAccessibilityTree(GetTraversableWidgets());
 }
 
 void DisplayOverlayController::RemoveButtonOptionsMenuWidget() {
@@ -435,6 +352,7 @@
 
   button_options_widget_->Close();
   button_options_widget_.reset();
+  UpdateAccessibilityTree(GetTraversableWidgets());
 }
 
 void DisplayOverlayController::SetButtonOptionsMenuWidgetVisibility(
@@ -451,6 +369,7 @@
   } else {
     button_options_widget_->Hide();
   }
+  UpdateAccessibilityTree(GetTraversableWidgets());
 }
 
 void DisplayOverlayController::AddDeleteEditShortcutWidget(
@@ -459,7 +378,6 @@
     delete_edit_shortcut_widget_ =
         base::WrapUnique(views::BubbleDialogDelegateView::CreateBubble(
             std::make_unique<DeleteEditShortcut>(this, anchor_view)));
-    widget_observations_.AddObservation(delete_edit_shortcut_widget_.get());
   }
 
   if (auto* shortcut = GetDeleteEditShortcut();
@@ -468,12 +386,14 @@
   }
 
   delete_edit_shortcut_widget_->Show();
+  UpdateAccessibilityTree(GetTraversableWidgets());
 }
 
 void DisplayOverlayController::RemoveDeleteEditShortcutWidget() {
   if (delete_edit_shortcut_widget_) {
     delete_edit_shortcut_widget_->Close();
     delete_edit_shortcut_widget_.reset();
+    UpdateAccessibilityTree(GetTraversableWidgets());
   }
 }
 
@@ -561,8 +481,8 @@
 }
 
 void DisplayOverlayController::OnKeyEvent(ui::KeyEvent* event) {
-  if (focus_cycler_) {
-    focus_cycler_->MaybeChangeFocusWidget(*event);
+  if (display_mode_ == DisplayMode::kEdit) {
+    MaybeChangeFocusWidget(*event);
   }
 }
 
@@ -643,17 +563,6 @@
   }
 }
 
-void DisplayOverlayController::OnWidgetVisibilityChanged(views::Widget* widget,
-                                                         bool visible) {
-  if (focus_cycler_) {
-    focus_cycler_->RefreshWidget(widget);
-  }
-}
-
-void DisplayOverlayController::OnWidgetDestroying(views::Widget* widget) {
-  widget_observations_.RemoveObservation(widget);
-}
-
 void DisplayOverlayController::SetInputMappingVisible(
     bool visible,
     bool store_visible_state) {
@@ -761,7 +670,6 @@
   input_mapping_widget_ = CreateTransientWidget(touch_injector_->window(),
                                                 /*widget_name=*/kInputMapping,
                                                 /*accept_events=*/false);
-  widget_observations_.AddObservation(input_mapping_widget_.get());
   input_mapping_widget_->SetContentsView(
       std::make_unique<InputMappingView>(this));
   UpdateInputMappingWidgetBounds();
@@ -805,7 +713,6 @@
   editing_list_widget_ = CreateTransientWidget(
       input_mapping_widget_->GetNativeWindow(), /*widget_name=*/kEditingList,
       /*accept_events=*/true);
-  widget_observations_.AddObservation(editing_list_widget_.get());
   editing_list_widget_->SetContentsView(std::make_unique<EditingList>(this));
 
   // Avoid active conflict with the game dashboard main menu.
@@ -813,13 +720,14 @@
   UpdateEditingListWidgetBounds();
   editing_list_widget_->widget_delegate()->SetAccessibleTitle(
       l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_EDITING_LIST_A11Y_LABEL));
+  UpdateAccessibilityTree(GetTraversableWidgets());
 }
 
 void DisplayOverlayController::RemoveEditingListWidget() {
   if (editing_list_widget_) {
     editing_list_widget_->Close();
     editing_list_widget_.reset();
-
+    UpdateAccessibilityTree(GetTraversableWidgets());
     UpdateFlagAndProperty(touch_injector_->window(),
                           ash::ArcGameControlsFlag::kEdit,
                           /*turn_on=*/false);
@@ -837,6 +745,7 @@
   } else {
     editing_list_widget_->Hide();
   }
+  UpdateAccessibilityTree(GetTraversableWidgets());
 }
 
 EditingList* DisplayOverlayController::GetEditingList() {
@@ -857,18 +766,6 @@
       button_options_widget_->GetContentsView());
 }
 
-void DisplayOverlayController::AddFocusCycler() {
-  if (!focus_cycler_) {
-    focus_cycler_ = std::make_unique<FocusCycler>();
-  }
-}
-
-void DisplayOverlayController::RemoveFocusCycler() {
-  if (focus_cycler_) {
-    focus_cycler_.reset();
-  }
-}
-
 void DisplayOverlayController::EnterButtonPlaceMode(ActionType action_type) {
   RemoveDeleteEditShortcutWidget();
   SetEditingListVisibility(/*visible=*/false);
@@ -1011,4 +908,56 @@
       !IsFlagSet(flags, ash::ArcGameControlsFlag::kEdit));
 }
 
+std::vector<views::Widget*> DisplayOverlayController::GetTraversableWidgets()
+    const {
+  std::vector<views::Widget*> widget_list;
+  if (input_mapping_widget_ && input_mapping_widget_->IsVisible()) {
+    widget_list.emplace_back(input_mapping_widget_.get());
+  }
+  if (editing_list_widget_ && editing_list_widget_->IsVisible()) {
+    widget_list.emplace_back(editing_list_widget_.get());
+  }
+  if (button_options_widget_ && button_options_widget_->IsVisible()) {
+    widget_list.emplace_back(button_options_widget_.get());
+  }
+  if (delete_edit_shortcut_widget_ &&
+      delete_edit_shortcut_widget_->IsVisible()) {
+    widget_list.emplace_back(delete_edit_shortcut_widget_.get());
+  }
+  return widget_list;
+}
+
+void DisplayOverlayController::MaybeChangeFocusWidget(ui::KeyEvent& event) {
+  // Only tab pressed is checked because the focus change is triggered by the
+  // tab pressed event. No need to change widget focus again on tab key
+  // released event.
+  if (event.type() == ui::EventType::kKeyReleased ||
+      !views::FocusManager::IsTabTraversalKeyEvent(event)) {
+    return;
+  }
+
+  const bool reverse = event.IsShiftDown();
+  auto* target_widget = views::Widget::GetWidgetForNativeWindow(
+      static_cast<aura::Window*>(event.target()));
+  auto* focus_manager = target_widget->GetFocusManager();
+
+  // Once there is next focusable view (dont_loop==true), it means the current
+  // focus is not the first or the last focusable view, so it doesn't need to
+  // change focus to the next widget.
+  if (focus_manager->GetNextFocusableView(
+          /*starting_view=*/focus_manager->GetFocusedView(),
+          /*starting_widget=*/target_widget, /*reverse=*/reverse,
+          /*dont_loop=*/true)) {
+    return;
+  }
+
+  // Change focus to the next widget.
+  if (auto* next_widget = GetNextWidgetToFocus(GetTraversableWidgets(),
+                                               target_widget, reverse)) {
+    next_widget->GetFocusManager()->AdvanceFocus(reverse);
+    // Change the event target.
+    ui::Event::DispatcherApi(&event).set_target(next_widget->GetNativeWindow());
+  }
+}
+
 }  // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
index 9c338d8f..4341e86 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
@@ -11,7 +11,6 @@
 #include "ash/public/cpp/arc_game_controls_flag.h"
 #include "ash/public/cpp/window_properties.h"
 #include "base/memory/raw_ptr.h"
-#include "base/scoped_multi_source_observation.h"
 #include "chrome/browser/ash/arc/input_overlay/actions/input_element.h"
 #include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h"
 #include "ui/aura/window_observer.h"
@@ -21,7 +20,6 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/widget/unique_widget_ptr.h"
-#include "ui/views/widget/widget_observer.h"
 
 namespace views {
 class View;
@@ -46,8 +44,7 @@
 // menu, and educational dialog. It also handles the visibility of the
 // `ActionEditMenu` and `MessageView` by listening to the `LocatedEvent`.
 class DisplayOverlayController : public ui::EventHandler,
-                                 public aura::WindowObserver,
-                                 public views::WidgetObserver {
+                                 public aura::WindowObserver {
  public:
   explicit DisplayOverlayController(TouchInjector* touch_injector);
   DisplayOverlayController(const DisplayOverlayController&) = delete;
@@ -138,10 +135,6 @@
                                const void* key,
                                intptr_t old) override;
 
-  // views::WidgetObserver:
-  void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override;
-  void OnWidgetDestroying(views::Widget* widget) override;
-
   const TouchInjector* touch_injector() const { return touch_injector_; }
 
  private:
@@ -158,8 +151,6 @@
   friend class OverlayViewTestBase;
   friend class RichNudgeTest;
 
-  class FocusCycler;
-
   // If `on_overlay` is true, set event target on overlay layer. Otherwise, set
   // event target on the layer underneath the overlay layer.
   void SetEventTarget(views::Widget* overlay_widget, bool on_overlay);
@@ -207,10 +198,6 @@
 
   ButtonOptionsMenu* GetButtonOptionsMenu();
 
-  // Focus cycler operations.
-  void AddFocusCycler();
-  void RemoveFocusCycler();
-
   // Shows or removes target view when in or out button place mode.
   void AddTargetWidget(ActionType action_type);
   void RemoveTargetWidget();
@@ -235,10 +222,14 @@
   // events.
   void UpdateEventRewriteCapability();
 
-  const raw_ptr<TouchInjector> touch_injector_;
+  // Returns a list of visible widgets that are available to be traversed in the
+  // edit mode.
+  std::vector<views::Widget*> GetTraversableWidgets() const;
 
-  base::ScopedMultiSourceObservation<views::Widget, views::WidgetObserver>
-      widget_observations_{this};
+  void MaybeChangeFocusWidget(ui::KeyEvent& event);
+
+  // Owned by `ArcInputOverlayManager`.
+  const raw_ptr<TouchInjector> touch_injector_;
 
   DisplayMode display_mode_ = DisplayMode::kNone;
 
@@ -250,8 +241,6 @@
   std::unique_ptr<views::Widget> action_highlight_widget_;
   views::UniqueWidgetPtr delete_edit_shortcut_widget_;
   views::UniqueWidgetPtr rich_nudge_widget_;
-
-  std::unique_ptr<FocusCycler> focus_cycler_;
 };
 
 }  // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/session/BUILD.gn b/chrome/browser/ash/arc/session/BUILD.gn
index 135fd95..7b72ba2 100644
--- a/chrome/browser/ash/arc/session/BUILD.gn
+++ b/chrome/browser/ash/arc/session/BUILD.gn
@@ -83,7 +83,6 @@
     "//chrome/browser/ash/arc/tracing",
     "//chrome/browser/ash/arc/tts",
     "//chrome/browser/ash/arc/user_session",
-    "//chrome/browser/ash/arc/video",
     "//chrome/browser/ash/arc/vmm",
     "//chrome/browser/ash/arc/wallpaper",
     "//chrome/browser/ash/crostini",
@@ -118,6 +117,7 @@
     "//chromeos/ash/experiences/arc/intent_helper",
     "//chromeos/ash/experiences/arc/media_session",
     "//chromeos/ash/experiences/arc/oemcrypto",
+    "//chromeos/ash/experiences/arc/video",
     "//chromeos/ash/services/cros_healthd/public/cpp",
     "//chromeos/ash/services/cros_healthd/public/mojom",
     "//chromeos/dbus/tpm_manager",
diff --git a/chrome/browser/ash/arc/session/arc_service_launcher.cc b/chrome/browser/ash/arc/session/arc_service_launcher.cc
index 22dff282..0943443 100644
--- a/chrome/browser/ash/arc/session/arc_service_launcher.cc
+++ b/chrome/browser/ash/arc/session/arc_service_launcher.cc
@@ -70,7 +70,6 @@
 #include "chrome/browser/ash/arc/tracing/arc_tracing_bridge.h"
 #include "chrome/browser/ash/arc/tts/arc_tts_service.h"
 #include "chrome/browser/ash/arc/user_session/arc_user_session_service.h"
-#include "chrome/browser/ash/arc/video/gpu_arc_video_service_host.h"
 #include "chrome/browser/ash/arc/vmm/arc_system_state_bridge.h"
 #include "chrome/browser/ash/arc/vmm/arc_vmm_manager.h"
 #include "chrome/browser/ash/arc/wallpaper/arc_wallpaper_service.h"
@@ -107,6 +106,7 @@
 #include "chromeos/ash/experiences/arc/session/arc_session.h"
 #include "chromeos/ash/experiences/arc/session/arc_session_runner.h"
 #include "chromeos/ash/experiences/arc/system_ui/arc_system_ui_bridge.h"
+#include "chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.h"
 #include "components/prefs/pref_member.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 
diff --git a/chrome/browser/ash/arc/video/DEPS b/chrome/browser/ash/arc/video/DEPS
deleted file mode 100644
index 8230f44..0000000
--- a/chrome/browser/ash/arc/video/DEPS
+++ /dev/null
@@ -1,20 +0,0 @@
-include_rules = [
-  # ChromeOS should not depend on //chrome. See //docs/chromeos/code.md for
-  # details.
-  "-chrome",
-
-  # This directory is in //chrome, which violates the rule above. Allow this
-  # directory to #include its own files.
-  "+chrome/browser/ash/arc/video",
-
-  # Existing dependencies within //chrome. There is an active effort to
-  # refactor //chrome/browser/ash to break these dependencies; see b/332804822.
-  # Whenever possible, avoid adding new //chrome dependencies to this list.
-  #
-  # Files residing in certain directories (e.g., //chrome/browser) are listed
-  # individually. Other dependencies within //chrome are listed on a per-
-  # directory basis. See //tools/chromeos/gen_deps.sh for details.
-
-  # Dependencies outside of //chrome:
-  "+mojo/core",
-]
diff --git a/chrome/browser/ash/dbus/BUILD.gn b/chrome/browser/ash/dbus/BUILD.gn
index 8268cd8..47e34d5d 100644
--- a/chrome/browser/ash/dbus/BUILD.gn
+++ b/chrome/browser/ash/dbus/BUILD.gn
@@ -79,7 +79,6 @@
     "//chrome/browser/ash/arc/fileapi",
     "//chrome/browser/ash/arc/session",
     "//chrome/browser/ash/arc/tracing",
-    "//chrome/browser/ash/arc/video",
     "//chrome/browser/ash/crostini",
     "//chrome/browser/ash/login/lock",
     "//chrome/browser/ash/net",
@@ -155,6 +154,7 @@
     "//chromeos/ash/components/language_packs",
     "//chromeos/ash/components/settings",
     "//chromeos/ash/experiences/arc/session",
+    "//chromeos/ash/experiences/arc/video",
     "//chromeos/ash/services/rollback_network_config/public/mojom",
     "//chromeos/components/mojo_bootstrap",
     "//chromeos/dbus/constants",
diff --git a/chrome/browser/ash/dbus/DEPS b/chrome/browser/ash/dbus/DEPS
index f310476..eb6d6087 100644
--- a/chrome/browser/ash/dbus/DEPS
+++ b/chrome/browser/ash/dbus/DEPS
@@ -19,7 +19,7 @@
   "+chrome/browser/ash/arc/session",
   "+chrome/browser/ash/arc/test",
   "+chrome/browser/ash/arc/tracing",
-  "+chrome/browser/ash/arc/video",
+  "+chromeos/ash/experiences/arc/video",
   "+chrome/browser/ash/borealis",
   "+chrome/browser/ash/crostini",
   "+chrome/browser/ash/exo",
diff --git a/chrome/browser/ash/dbus/libvda_service_provider.cc b/chrome/browser/ash/dbus/libvda_service_provider.cc
index aa3e588..8251ec9 100644
--- a/chrome/browser/ash/dbus/libvda_service_provider.cc
+++ b/chrome/browser/ash/dbus/libvda_service_provider.cc
@@ -8,7 +8,7 @@
 #include <utility>
 
 #include "base/functional/bind.h"
-#include "chrome/browser/ash/arc/video/gpu_arc_video_service_host.h"
+#include "chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "mojo/public/cpp/system/platform_handle.h"
diff --git a/chrome/browser/ash/floating_workspace/DEPS b/chrome/browser/ash/floating_workspace/DEPS
index 154fc24e..ad24539b 100644
--- a/chrome/browser/ash/floating_workspace/DEPS
+++ b/chrome/browser/ash/floating_workspace/DEPS
@@ -32,3 +32,9 @@
   "+chrome/grit",
   "+chrome/test/base",
 ]
+
+specific_include_rules = {
+  "floating_workspace_service_unittest\.cc": [
+    "+chrome/browser/ash/settings/scoped_cros_settings_test_helper.h",
+  ]
+}
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc b/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
index abf0ac2..8e64e93 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/ash/floating_workspace/floating_workspace_metrics_util.h"
 #include "chrome/browser/ash/floating_workspace/floating_workspace_service_factory.h"
 #include "chrome/browser/ash/floating_workspace/floating_workspace_util.h"
+#include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
@@ -405,6 +406,8 @@
 
   void SetUp() override {
     chromeos::PowerManagerClient::InitializeFake();
+    cros_settings_test_helper_ =
+        std::make_unique<ScopedCrosSettingsTestHelper>();
     ash::AshTestHelper::InitParams params;
     ash_test_helper_.SetUp(std::move(params));
     profile_manager_ = std::make_unique<TestingProfileManager>(
@@ -468,7 +471,9 @@
     profile_ = nullptr;
     profile_manager_ = nullptr;
     mock_desks_client_ = nullptr;
+    fake_user_manager_.Reset();
     ash_test_helper_.TearDown();
+    cros_settings_test_helper_.reset();
     chromeos::PowerManagerClient::Shutdown();
   }
 
@@ -484,6 +489,7 @@
   std::unique_ptr<NetworkHandlerTestHelper> network_handler_test_helper_;
   std::unique_ptr<apps::AppRegistryCache> cache_;
   AccountId account_id_;
+  std::unique_ptr<ScopedCrosSettingsTestHelper> cros_settings_test_helper_;
   AshTestHelper ash_test_helper_;
   std::unique_ptr<TestingProfileManager> profile_manager_;
   std::unique_ptr<MockDesksClient> mock_desks_client_;
diff --git a/chrome/browser/ash/input_method/BUILD.gn b/chrome/browser/ash/input_method/BUILD.gn
index 39d45f5b..08e85a4 100644
--- a/chrome/browser/ash/input_method/BUILD.gn
+++ b/chrome/browser/ash/input_method/BUILD.gn
@@ -195,7 +195,6 @@
     "//chrome/app:generated_resources",
     "//chrome/browser:browser_process",
     "//chrome/browser:resources",
-    "//chrome/browser/ash/crosapi:browser_util",
     "//chrome/browser/ash/file_manager",
     "//chrome/browser/ash/input_method/japanese",
     "//chrome/browser/ash/lobster",
@@ -346,7 +345,6 @@
     "//base/test:test_support",
     "//chrome/app:generated_resources",
     "//chrome/browser",
-    "//chrome/browser/ash/crosapi:browser_util",
     "//chrome/browser/ash/input_method:test_support",
     "//chrome/browser/ash/login/users:test_support",
     "//chrome/browser/ash/settings:test_support",
diff --git a/chrome/browser/ash/input_method/assistive_suggester_unittest.cc b/chrome/browser/ash/input_method/assistive_suggester_unittest.cc
index 441b29dc..ebe08267 100644
--- a/chrome/browser/ash/input_method/assistive_suggester_unittest.cc
+++ b/chrome/browser/ash/input_method/assistive_suggester_unittest.cc
@@ -16,7 +16,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
 #include "base/time/time.h"
-#include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/input_method/assistive_suggester_client_filter.h"
 #include "chrome/browser/ash/input_method/assistive_suggester_switch.h"
 #include "chrome/browser/ash/input_method/fake_suggestion_handler.h"
diff --git a/chrome/browser/ash/input_method/autocorrect_manager.cc b/chrome/browser/ash/input_method/autocorrect_manager.cc
index 0141014..bcdd275 100644
--- a/chrome/browser/ash/input_method/autocorrect_manager.cc
+++ b/chrome/browser/ash/input_method/autocorrect_manager.cc
@@ -15,7 +15,6 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
-#include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/input_method/assistive_prefs.h"
 #include "chrome/browser/ash/input_method/assistive_window_properties.h"
 #include "chrome/browser/ash/input_method/autocorrect_enums.h"
@@ -702,8 +701,7 @@
   auto autocorrect_pref =
       GetPhysicalKeyboardAutocorrectPref(*pref_service, engine_id);
 
-  if (!crosapi::browser_util::IsLacrosEnabled() &&
-      base::FeatureList::IsEnabled(features::kAutocorrectByDefault) &&
+  if (base::FeatureList::IsEnabled(features::kAutocorrectByDefault) &&
       autocorrect_pref == AutocorrectPreference::kDefault &&
       IsUsEnglishId(engine_id) &&
       // This class is instantiated with NativeInputMethodEngineObserver, which
@@ -859,10 +857,6 @@
 
   // If cursor is inside autocorrect range (inclusive), show undo window and
   // record relevant metrics.
-  // On Lacros, the async behaviors accidentally delay the update of autocorrect
-  // range after the onSurroundingTextChanged. When users delete a few
-  // characters of the suggested words, this code still uses the outdated range,
-  // hence allowing the undo window to show.
   // TODO(b/278616918): Consider remove the
   // IsAutocorrectSuggestionInSurroundingText logic once async behaviors are
   // corrected.
@@ -984,8 +978,7 @@
         autocorrect_range.end() - surrounding_text.selection_range.end();
 
     if (base::FeatureList::IsEnabled(
-            features::kAutocorrectUseReplaceSurroundingText) &&
-        !crosapi::browser_util::IsLacrosEnabled()) {
+            features::kAutocorrectUseReplaceSurroundingText)) {
       input_context->ReplaceSurroundingText(
           before, after, pending_autocorrect_->original_text);
     } else {
diff --git a/chrome/browser/ash/lobster/lobster_bubble_coordinator.cc b/chrome/browser/ash/lobster/lobster_bubble_coordinator.cc
index f75160bc..f0645070 100644
--- a/chrome/browser/ash/lobster/lobster_bubble_coordinator.cc
+++ b/chrome/browser/ash/lobster/lobster_bubble_coordinator.cc
@@ -26,7 +26,8 @@
 
 void LobsterBubbleCoordinator::LoadUI(Profile* profile,
                                       std::optional<std::string_view> query,
-                                      LobsterMode mode) {
+                                      LobsterMode mode,
+                                      const gfx::Rect& caret_bounds) {
   if (IsShowingUI()) {
     contents_wrapper_->CloseUI();
   }
@@ -53,7 +54,7 @@
       /*esc_closes_ui=*/false);
 
   std::unique_ptr<LobsterView> lobster_view =
-      std::make_unique<LobsterView>(contents_wrapper_.get(), gfx::Rect());
+      std::make_unique<LobsterView>(contents_wrapper_.get(), caret_bounds);
   auto bubble = lobster_view->GetWeakPtr();
   views::BubbleDialogDelegateView::CreateBubble(std::move(lobster_view));
 
diff --git a/chrome/browser/ash/lobster/lobster_bubble_coordinator.h b/chrome/browser/ash/lobster/lobster_bubble_coordinator.h
index 08fa417..3bf3ef8 100644
--- a/chrome/browser/ash/lobster/lobster_bubble_coordinator.h
+++ b/chrome/browser/ash/lobster/lobster_bubble_coordinator.h
@@ -28,7 +28,8 @@
 
   void LoadUI(Profile* profile,
               std::optional<std::string_view> query,
-              LobsterMode mode);
+              LobsterMode mode,
+              const gfx::Rect& caret_bounds);
   void ShowUI();
   void CloseUI();
 
diff --git a/chrome/browser/ash/lobster/lobster_client_impl.cc b/chrome/browser/ash/lobster/lobster_client_impl.cc
index 1301f8e..efd4a7db 100644
--- a/chrome/browser/ash/lobster/lobster_client_impl.cc
+++ b/chrome/browser/ash/lobster/lobster_client_impl.cc
@@ -47,8 +47,9 @@
 }
 
 void LobsterClientImpl::LoadUI(std::optional<std::string> query,
-                               ash::LobsterMode mode) {
-  service_->LoadUI(query, mode);
+                               ash::LobsterMode mode,
+                               const gfx::Rect& caret_bounds) {
+  service_->LoadUI(query, mode, caret_bounds);
 }
 
 void LobsterClientImpl::ShowUI() {
diff --git a/chrome/browser/ash/lobster/lobster_client_impl.h b/chrome/browser/ash/lobster/lobster_client_impl.h
index 544154f8..8ed5960 100644
--- a/chrome/browser/ash/lobster/lobster_client_impl.h
+++ b/chrome/browser/ash/lobster/lobster_client_impl.h
@@ -39,7 +39,9 @@
                       const std::string& image_bytes) override;
   void QueueInsertion(const std::string& image_bytes,
                       StatusCallback insert_status_callback) override;
-  void LoadUI(std::optional<std::string> query, ash::LobsterMode mode) override;
+  void LoadUI(std::optional<std::string> query,
+              ash::LobsterMode mode,
+              const gfx::Rect& caret_bounds) override;
   void ShowUI() override;
   void CloseUI() override;
 
diff --git a/chrome/browser/ash/lobster/lobster_service.cc b/chrome/browser/ash/lobster/lobster_service.cc
index 3029588a..35952a1 100644
--- a/chrome/browser/ash/lobster/lobster_service.cc
+++ b/chrome/browser/ash/lobster/lobster_service.cc
@@ -68,8 +68,9 @@
 }
 
 void LobsterService::LoadUI(std::optional<std::string> query,
-                            ash::LobsterMode mode) {
-  bubble_coordinator_.LoadUI(profile_, query, mode);
+                            ash::LobsterMode mode,
+                            const gfx::Rect& caret_bounds) {
+  bubble_coordinator_.LoadUI(profile_, query, mode, caret_bounds);
 }
 
 void LobsterService::ShowUI() {
diff --git a/chrome/browser/ash/lobster/lobster_service.h b/chrome/browser/ash/lobster/lobster_service.h
index 2ff4b0fb..9ca206f4 100644
--- a/chrome/browser/ash/lobster/lobster_service.h
+++ b/chrome/browser/ash/lobster/lobster_service.h
@@ -53,7 +53,9 @@
                       const std::string& description,
                       const std::string& image_bytes);
 
-  void LoadUI(std::optional<std::string> query, ash::LobsterMode mode);
+  void LoadUI(std::optional<std::string> query,
+              ash::LobsterMode mode,
+              const gfx::Rect& caret_bounds);
 
   void ShowUI();
 
diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java
index 2bd32089..50aba3c 100644
--- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java
+++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java
@@ -118,6 +118,11 @@
     // The heights of each layer at their fully shown positions.
     private final SparseIntArray mLayerRestingOffsets = new SparseIntArray();
 
+    // Whether layer is contributing to the minHeight. This is calculated during height calculation,
+    // and won't update when the layers are being repositioned during scroll.
+    private final SparseBooleanArray mLayerHasMinHeight = new SparseBooleanArray();
+    private boolean mHasMoreThanOneNonScrollableLayer;
+
     private final BrowserControlsSizer mBrowserControlsSizer;
 
     private int mTotalHeight = INVALID_HEIGHT;
@@ -184,6 +189,23 @@
         return mTotalMinHeight;
     }
 
+    /**
+     * Whether the layer with {@link type} is not scrollable. To other words, return true iff the
+     * layer is contributing to the bottom control's minHeight.
+     */
+    public boolean isLayerNonScrollable(int type) {
+        return mLayers.get(type) != null && mLayerHasMinHeight.get(type);
+    }
+
+    /**
+     * Whether there are more than one layer that returns true with {@link #isLayerNonScrollable}.
+     * To other words, returns true when more than one layer is contributing to browser control's
+     * minHeight.
+     */
+    public boolean hasMultipleNonScrollableLayer() {
+        return mHasMoreThanOneNonScrollableLayer;
+    }
+
     /** Returns if viz is able to move the browser controls now. */
     public boolean isMoveableByViz() {
         return ChromeFeatureList.sBcivBottomControls.isEnabled()
@@ -537,6 +559,10 @@
                     : "A scroll-off layer under a NEVER_SCROLL_OFF layer is not supported. Layer: "
                             + layer.getType();
 
+            // When min height exists before processing the current layer's height, it means more
+            // than one non-scrollable layer exists.
+            mHasMoreThanOneNonScrollableLayer = minHeight != 0;
+
             if (ChromeFeatureList.sBcivBottomControls.isEnabled()) {
                 if (shouldScrollOff) {
                     if (mOffsetTagsInfo != null) {
@@ -549,6 +575,7 @@
 
             height += layer.getHeight();
             minHeight += shouldScrollOff ? 0 : layer.getHeight();
+            mLayerHasMinHeight.put(type, !shouldScrollOff);
         }
 
         mTotalHeight = height;
diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStackerUnitTest.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStackerUnitTest.java
index 43c582c..a283861 100644
--- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStackerUnitTest.java
+++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStackerUnitTest.java
@@ -279,6 +279,8 @@
         mBottomControlsStacker.addLayer(layer);
         mBottomControlsStacker.requestLayerUpdate(false);
         verify(mBrowserControlsSizer).setBottomControlsHeight(100, 0);
+        assertLayerNonScrollable(TOP_LAYER, false);
+        assertHasMultipleNonScrollableLayer(false);
     }
 
     @Test
@@ -292,6 +294,8 @@
         mBottomControlsStacker.addLayer(layer);
         mBottomControlsStacker.requestLayerUpdate(false);
         verify(mBrowserControlsSizer).setBottomControlsHeight(100, 100);
+        assertLayerNonScrollable(TOP_LAYER, true);
+        assertHasMultipleNonScrollableLayer(false);
     }
 
     @Test
@@ -306,6 +310,8 @@
         mBottomControlsStacker.addLayer(layer);
         mBottomControlsStacker.requestLayerUpdate(false);
         verify(mBrowserControlsSizer).setBottomControlsHeight(0, 0);
+        assertLayerNonScrollable(TOP_LAYER, false);
+        assertHasMultipleNonScrollableLayer(false);
     }
 
     @Test
@@ -328,6 +334,10 @@
 
         verify(mBrowserControlsSizer).setBottomControlsHeight(110, 0);
         verify(mBrowserControlsSizer).setAnimateBrowserControlsHeightChanges(true);
+
+        assertLayerNonScrollable(TOP_LAYER, false);
+        assertLayerNonScrollable(BOTTOM_LAYER, false);
+        assertHasMultipleNonScrollableLayer(false);
     }
 
     @Test
@@ -350,6 +360,10 @@
 
         verify(mBrowserControlsSizer).setBottomControlsHeight(110, 110);
         verify(mBrowserControlsSizer).setAnimateBrowserControlsHeightChanges(true);
+
+        assertLayerNonScrollable(TOP_LAYER, true);
+        assertLayerNonScrollable(BOTTOM_LAYER, true);
+        assertHasMultipleNonScrollableLayer(true);
     }
 
     @Test
@@ -372,6 +386,10 @@
 
         verify(mBrowserControlsSizer).setBottomControlsHeight(110, 10);
         verify(mBrowserControlsSizer).setAnimateBrowserControlsHeightChanges(true);
+
+        assertLayerNonScrollable(TOP_LAYER, false);
+        assertLayerNonScrollable(BOTTOM_LAYER, true);
+        assertHasMultipleNonScrollableLayer(false);
     }
 
     @Test
@@ -401,6 +419,11 @@
 
         verify(mBrowserControlsSizer).setBottomControlsHeight(180, 80);
         verify(mBrowserControlsSizer).setAnimateBrowserControlsHeightChanges(true);
+
+        assertLayerNonScrollable(TOP_LAYER, false);
+        assertLayerNonScrollable(MID_LAYER, true);
+        assertLayerNonScrollable(BOTTOM_LAYER, true);
+        assertHasMultipleNonScrollableLayer(true);
     }
 
     @Test
@@ -423,6 +446,10 @@
 
         verify(mBrowserControlsSizer).setBottomControlsHeight(120, 0);
         verify(mBrowserControlsSizer).setAnimateBrowserControlsHeightChanges(true);
+
+        assertLayerNonScrollable(TOP_LAYER, false);
+        assertLayerNonScrollable(BOTTOM_LAYER, false);
+        assertHasMultipleNonScrollableLayer(false);
     }
 
     @Test(expected = AssertionError.class)
@@ -1582,6 +1609,20 @@
         assertEquals("Different yOffset observed.", expectedOffset, layer.mYOffset);
     }
 
+    private void assertLayerNonScrollable(@LayerType int type, boolean nonScrollable) {
+        assertEquals(
+                "isLayerNonScrollable(" + type + ") is unexpected.",
+                nonScrollable,
+                mBottomControlsStacker.isLayerNonScrollable(type));
+    }
+
+    private void assertHasMultipleNonScrollableLayer(boolean hasOtherLayers) {
+        assertEquals(
+                "hasMultipleNonScrollableLayer() is unexpected.",
+                hasOtherLayers,
+                mBottomControlsStacker.hasMultipleNonScrollableLayer());
+    }
+
     private static class TestLayer implements BottomControlsLayer {
         private final @LayerType int mType;
         private final @LayerScrollBehavior int mScrollBehavior;
diff --git a/chrome/browser/devtools/protocol/target_handler.cc b/chrome/browser/devtools/protocol/target_handler.cc
index 7ba4064..dfd7a6f7 100644
--- a/chrome/browser/devtools/protocol/target_handler.cc
+++ b/chrome/browser/devtools/protocol/target_handler.cc
@@ -4,10 +4,6 @@
 
 #include "chrome/browser/devtools/protocol/target_handler.h"
 
-#include <ranges>
-#include <string_view>
-
-#include "base/notreached.h"
 #include "chrome/browser/devtools/chrome_devtools_manager_delegate.h"
 #include "chrome/browser/devtools/devtools_browser_context_manager.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -15,8 +11,6 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
-#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
 #include "chrome/common/webui_url_constants.h"
 #include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/web_contents.h"
@@ -85,7 +79,6 @@
     std::optional<int> top,
     std::optional<int> width,
     std::optional<int> height,
-    std::optional<std::string> window_state,
     std::optional<std::string> browser_context_id,
     std::optional<bool> enable_begin_frame_control,
     std::optional<bool> new_window,
@@ -148,35 +141,13 @@
         "Target position can only be set for new windows");
   }
 
-  static std::string_view kActionableWindowStates[] = {
-      protocol::Target::WindowStateEnum::Minimized,
-      protocol::Target::WindowStateEnum::Maximized,
-      protocol::Target::WindowStateEnum::Fullscreen,
-  };
-
-  bool set_window_state = !!window_state;
-  if (set_window_state) {
-    if (!create_new_window) {
-      return protocol::Response::ServerError(
-          "Target window state can only be set for new windows");
-    }
-    if (*window_state == protocol::Target::WindowStateEnum::Normal) {
-      set_window_state = false;
-    } else if (std::ranges::find(kActionableWindowStates, *window_state) ==
-               std::end(kActionableWindowStates)) {
-      return protocol::Response::ServerError("Invalid target window state: " +
-                                             *window_state);
-    }
-  }
-
   NavigateParams params = CreateNavigateParams(
       profile, gurl, ui::PAGE_TRANSITION_AUTO_TOPLEVEL, create_new_window,
       create_in_background, target_browser);
 
   Navigate(&params);
-  if (!params.navigated_or_inserted_contents) {
+  if (!params.navigated_or_inserted_contents)
     return protocol::Response::ServerError("Failed to open a new tab");
-  }
 
   if (set_window_position) {
     BrowserWindow* browser_window = params.browser->window();
@@ -197,20 +168,6 @@
     browser_window->SetBounds(bounds);
   }
 
-  if (set_window_state) {
-    if (*window_state == protocol::Target::WindowStateEnum::Minimized) {
-      params.browser->window()->Minimize();
-    } else if (*window_state == protocol::Target::WindowStateEnum::Maximized) {
-      params.browser->window()->Maximize();
-    } else if (*window_state == protocol::Target::WindowStateEnum::Fullscreen) {
-      params.browser->exclusive_access_manager()
-          ->fullscreen_controller()
-          ->ToggleBrowserFullscreenMode(/*user_initiated=*/false);
-    } else {
-      NOTREACHED();
-    }
-  }
-
   if (!create_in_background) {
     params.navigated_or_inserted_contents->Focus();
   }
diff --git a/chrome/browser/devtools/protocol/target_handler.h b/chrome/browser/devtools/protocol/target_handler.h
index 67cddb8..05332ea2 100644
--- a/chrome/browser/devtools/protocol/target_handler.h
+++ b/chrome/browser/devtools/protocol/target_handler.h
@@ -35,7 +35,6 @@
       std::optional<int> top,
       std::optional<int> width,
       std::optional<int> height,
-      std::optional<std::string> window_state,
       std::optional<std::string> browser_context_id,
       std::optional<bool> enable_begin_frame_control,
       std::optional<bool> new_window,
diff --git a/chrome/browser/extensions/account_extension_tracker.cc b/chrome/browser/extensions/account_extension_tracker.cc
index 77ca943..b2ea29c 100644
--- a/chrome/browser/extensions/account_extension_tracker.cc
+++ b/chrome/browser/extensions/account_extension_tracker.cc
@@ -206,11 +206,7 @@
   // and thus don't need to be set here.
   AccountExtensionType type = GetAccountExtensionType(extension_id);
   if (type == AccountExtensionType::kLocal) {
-    SetAccountExtensionType(extension_id,
-                            AccountExtensionType::kAccountInstalledLocally);
-    for (auto& observer : observers_) {
-      observer.OnExtensionUploadabilityChanged(extension_id);
-    }
+    PromoteLocalToAccountExtension(extension_id);
   }
 }
 
@@ -271,6 +267,15 @@
          sync_util::ShouldSync(profile_, &extension);
 }
 
+void AccountExtensionTracker::OnAccountUploadInitiatedForExtension(
+    const ExtensionId& extension_id) {
+  PromoteLocalToAccountExtension(extension_id);
+
+  // The "local state" of the uploaded extension will be pushed to sync soon,
+  // so set NeedsSync to false.
+  ExtensionPrefs::Get(profile_)->SetNeedsSync(extension_id, false);
+}
+
 void AccountExtensionTracker::SetAccountExtensionTypeForTesting(
     const ExtensionId& extension_id,
     AccountExtensionType type) {
@@ -299,6 +304,25 @@
       [&extension_id](const ExtensionId& id) { return extension_id == id; });
 }
 
+void AccountExtensionTracker::PromoteLocalToAccountExtension(
+    const ExtensionId& extension_id) {
+  // Make sure we're actually promoting a local extension!
+  DCHECK_EQ(GetAccountExtensionType(extension_id),
+            AccountExtensionType::kLocal);
+
+  // The previous `AccountExtensionType::kLocal` state implies the extension is
+  // still associated with the current device hence the promotion to
+  // `AccountExtensionType::kAccountInstalledLocally`.
+  SetAccountExtensionType(extension_id,
+                          AccountExtensionType::kAccountInstalledLocally);
+
+  // The extension's uploadability may change when its AccountExtensionType
+  // changes.
+  for (auto& observer : observers_) {
+    observer.OnExtensionUploadabilityChanged(extension_id);
+  }
+}
+
 void AccountExtensionTracker::NotifyOnExtensionsUploadabilityChanged() {
   for (auto& observer : observers_) {
     observer.OnExtensionsUploadabilityChanged();
diff --git a/chrome/browser/extensions/account_extension_tracker.h b/chrome/browser/extensions/account_extension_tracker.h
index ce4c295..cc04b42 100644
--- a/chrome/browser/extensions/account_extension_tracker.h
+++ b/chrome/browser/extensions/account_extension_tracker.h
@@ -106,6 +106,10 @@
   // current signed in user.
   bool CanUploadAsAccountExtension(const Extension& extension) const;
 
+  // Called when the user initiates an upload for the given `extension_id` to
+  // their account.
+  void OnAccountUploadInitiatedForExtension(const ExtensionId& extension_id);
+
   void SetAccountExtensionTypeForTesting(const ExtensionId& extension_id,
                                          AccountExtensionType type);
 
@@ -122,6 +126,9 @@
   // Removes `extension_id` in `extensions_installed_with_signin_promo_`.
   void RemoveExpiredExtension(const ExtensionId& extension_id);
 
+  // Promotes `extension_id` from a local to an account extension.
+  void PromoteLocalToAccountExtension(const ExtensionId& extension_id);
+
   // Notifies observers that the eligibility of multiple extensions to be
   // uploaded to the user's account may have changed.
   void NotifyOnExtensionsUploadabilityChanged();
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.cc b/chrome/browser/extensions/api/developer_private/developer_private_api.cc
index dada213..f476b38 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api.cc
@@ -27,6 +27,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/thread_pool.h"
+#include "base/types/expected.h"
 #include "base/uuid.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/devtools/devtools_window.h"
@@ -39,6 +40,7 @@
 #include "chrome/browser/extensions/extension_commands_global_registry.h"
 #include "chrome/browser/extensions/extension_management.h"
 #include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_sync_service.h"
 #include "chrome/browser/extensions/extension_sync_util.h"
 #include "chrome/browser/extensions/extension_system_factory.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
@@ -2967,25 +2969,13 @@
 
   EXTENSION_FUNCTION_VALIDATE(params);
   extension_id_ = std::move(params->extension_id);
+  profile_ = Profile::FromBrowserContext(browser_context());
 
-  const Extension* extension =
-      ExtensionRegistry::Get(browser_context())
-          ->GetExtensionById(extension_id_, ExtensionRegistry::EVERYTHING);
-  if (!extension) {
-    return RespondNow(Error(
-        ErrorUtils::FormatErrorMessage(kNoExtensionError, extension_id_)));
+  auto result = VerifyExtensionAndSigninState();
+  if (!result.has_value()) {
+    return RespondNow(Error(result.error()));
   }
-
-  Profile* profile = Profile::FromBrowserContext(browser_context());
-
-  // Return an error if there is no signed in user.
-  signin::IdentityManager* identity_manager =
-      IdentityManagerFactory::GetForProfile(profile);
-  AccountInfo account_info = identity_manager->FindExtendedAccountInfo(
-      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin));
-  if (account_info.IsEmpty()) {
-    return RespondNow(Error(kUserNotSignedIn));
-  }
+  const Extension* extension = *result;
 
   // Return an error if the extension cannot be uploaded for reasons such as:
   // - syncing extensions in transport mode (signed in but not full sync) is
@@ -2993,7 +2983,7 @@
   // - the extension is already associated with the signed in user's account.
   // - the extension is not syncable (for example, if it's unpacked).
   if (!sync_util::IsExtensionsExplicitSigninEnabled() ||
-      !AccountExtensionTracker::Get(profile)->CanUploadAsAccountExtension(
+      !AccountExtensionTracker::Get(profile_)->CanUploadAsAccountExtension(
           *extension)) {
     return RespondNow(Error(ErrorUtils::FormatErrorMessage(
         kCannotUploadExtensionToAccount, extension_id_)));
@@ -3030,6 +3020,37 @@
   return RespondLater();
 }
 
+base::expected<const Extension*, std::string>
+DeveloperPrivateUploadExtensionToAccountFunction::
+    VerifyExtensionAndSigninState() {
+  const Extension* extension =
+      ExtensionRegistry::Get(browser_context())
+          ->GetExtensionById(extension_id_, ExtensionRegistry::EVERYTHING);
+  if (!extension) {
+    return base::unexpected(
+        ErrorUtils::FormatErrorMessage(kNoExtensionError, extension_id_));
+  }
+
+  // Return an error if there is no signed in user.
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(profile_);
+  AccountInfo account_info = identity_manager->FindExtendedAccountInfo(
+      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin));
+  if (account_info.IsEmpty()) {
+    return base::unexpected(kUserNotSignedIn);
+  }
+
+  return base::ok(extension);
+}
+
+void DeveloperPrivateUploadExtensionToAccountFunction::UploadExtensionToAccount(
+    const Extension& extension) {
+  AccountExtensionTracker::Get(browser_context())
+      ->OnAccountUploadInitiatedForExtension(extension.id());
+  ExtensionSyncService::Get(browser_context())
+      ->SyncExtensionChangeIfNeeded(extension);
+}
+
 void DeveloperPrivateUploadExtensionToAccountFunction::OnDialogAccepted() {
   // We cannot proceed if the `browser_context` is not valid as the relevant
   // classes needed to upload the extension will not exist.
@@ -3037,8 +3058,14 @@
     return;
   }
 
-  // TODO(crbug.com/381127648): Upload the associated `extension_id_` to the
-  // user's account once the dialog is accepted.
+  auto result = VerifyExtensionAndSigninState();
+  if (!result.has_value()) {
+    Respond(Error(result.error()));
+    return;
+  }
+  const Extension* extension = *result;
+
+  UploadExtensionToAccount(*extension);
   Respond(NoArguments());
 }
 
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.h b/chrome/browser/extensions/api/developer_private/developer_private_api.h
index 8211ded..2d199cdd 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api.h
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api.h
@@ -1128,6 +1128,13 @@
 
   ResponseAction Run() override;
 
+  // Verify that the extension to be uploaded exists and that there's a signed
+  // in user. Returns the extension if successful, otherwise returns an error.
+  base::expected<const Extension*, std::string> VerifyExtensionAndSigninState();
+
+  // Uploads the given `extension` to the user's account.
+  void UploadExtensionToAccount(const Extension& extension);
+
   // A callback function to run when the user accepts the action dialog.
   void OnDialogAccepted();
 
@@ -1137,6 +1144,8 @@
   // The ID of the extension to be uploaded.
   ExtensionId extension_id_;
 
+  raw_ptr<Profile> profile_;
+
   // If true, immediately accepts the keep dialog by running the callback.
   std::optional<bool> accept_bubble_for_testing_;
 };
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc b/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc
index 29047e1..fd5a44d 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc
@@ -3473,6 +3473,12 @@
     return identity_test_env_profile_adaptor_->identity_test_env();
   }
 
+  AccountExtensionTracker::AccountExtensionType GetAccountExtensionType(
+      const ExtensionId& extension_id) {
+    return AccountExtensionTracker::Get(profile())->GetAccountExtensionType(
+        extension_id);
+  }
+
   bool CanUploadToAccount(const Extension& extension) {
     return AccountExtensionTracker::Get(profile())->CanUploadAsAccountExtension(
         extension);
@@ -3586,7 +3592,8 @@
           unsyncable_extension->id()));
 }
 
-TEST_F(DeveloperPrivateApiTransportModeUnitTest, UploadExtensionToAccount) {
+TEST_F(DeveloperPrivateApiTransportModeUnitTest,
+       UploadExtensionToAccount_Cancelled) {
   // Add a syncable extension.
   auto syncable_extension = LoadSyncableExtension("ext");
 
@@ -3625,6 +3632,76 @@
           syncable_extension->id()));
 }
 
+TEST_F(DeveloperPrivateApiTransportModeUnitTest,
+       UploadExtensionToAccount_Accepted) {
+  // Add a syncable extension.
+  auto extension = LoadSyncableExtension("ext");
+  ItemStatePrefsChangedObserver test_observer =
+      StartListeningForEvent(extension->id());
+
+  // Sign the user in without full sync.
+  SimulateExplicitSignIn();
+
+  // Now simulate an initial sync with no extensions in the user's account. This
+  // is needed to spin up the sync service so uploaded extensions actually get
+  // synced.
+  SimulateInitialSync({});
+
+  // Wait for the associated prefs changed event from the initial sync so the
+  // event that gets emitted later from an extension upload can be properly
+  // picked up.
+  test_observer.WaitForEvent();
+
+  // The syncable extension can be uploaded and should be a local extension.
+  EXPECT_TRUE(CanUploadToAccount(*extension));
+  EXPECT_EQ(AccountExtensionTracker::AccountExtensionType::kLocal,
+            GetAccountExtensionType(extension->id()));
+
+  // On this machine, there should be no extensions syncing.
+  {
+    syncer::SyncDataList list =
+        ExtensionSyncService::Get(profile())->GetAllSyncDataForTesting(
+            syncer::EXTENSIONS);
+    EXPECT_TRUE(list.empty());
+  }
+
+  // Now upload the extension and accept the dialog to proceed with the upload.
+  base::Value::List args;
+  args.Append(extension->id());
+  auto upload_function = base::MakeRefCounted<
+      api::DeveloperPrivateUploadExtensionToAccountFunction>();
+  upload_function->set_source_context_type(mojom::ContextType::kWebUi);
+  upload_function->accept_bubble_for_testing(true);
+
+  test_observer.Reset();
+  EXPECT_TRUE(RunFunction(upload_function, args));
+
+  // Wait for the prefs changed update and verify that the extension is no
+  // longer uploadable after being uploaded.
+  test_observer.WaitForEvent();
+  auto info = test_observer.event_info();
+  EXPECT_FALSE(info.can_upload_as_account_extension);
+  EXPECT_FALSE(CanUploadToAccount(*extension));
+
+  // Double check that the extension is now an account extension.
+  EXPECT_EQ(
+      AccountExtensionTracker::AccountExtensionType::kAccountInstalledLocally,
+      GetAccountExtensionType(extension->id()));
+
+  // Verify that the extension is now syncing from the sync service.
+  {
+    syncer::SyncDataList list =
+        ExtensionSyncService::Get(profile())->GetAllSyncDataForTesting(
+            syncer::EXTENSIONS);
+    ASSERT_EQ(1u, list.size());
+    std::unique_ptr<ExtensionSyncData> data =
+        ExtensionSyncData::CreateFromSyncData(list[0]);
+    ASSERT_TRUE(data.get());
+    EXPECT_EQ(extension->id(), data->id());
+    EXPECT_TRUE(data->enabled());
+  }
+}
+
 // Test that an extension is uploadable when the user signs into transport mode
 // and the extension is not in the user's sync data.
 TEST_F(DeveloperPrivateApiTransportModeUnitTest, ExtensionUploadableOnSignIn) {
diff --git a/chrome/browser/extensions/api/developer_private/extension_info_generator.cc b/chrome/browser/extensions/api/developer_private/extension_info_generator.cc
index 6cb10f3..202e6ba 100644
--- a/chrome/browser/extensions/api/developer_private/extension_info_generator.cc
+++ b/chrome/browser/extensions/api/developer_private/extension_info_generator.cc
@@ -913,7 +913,7 @@
     ExtensionInfoList list = std::move(list_);
     list_.clear();
     std::move(callback_).Run(std::move(list));
-    // WARNING: |this| is possibly deleted after this line!
+    // WARNING: `this` is possibly deleted after this line!
   }
 }
 
diff --git a/chrome/browser/extensions/cross_origin_isolation_browsertest.cc b/chrome/browser/extensions/cross_origin_isolation_browsertest.cc
index 6fa7c54..bc1538b 100644
--- a/chrome/browser/extensions/cross_origin_isolation_browsertest.cc
+++ b/chrome/browser/extensions/cross_origin_isolation_browsertest.cc
@@ -3,11 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/files/file_path.h"
-#include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
@@ -26,6 +22,15 @@
 #include "net/dns/mock_host_resolver.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
+#if BUILDFLAG(IS_ANDROID)
+#include "chrome/browser/extensions/extension_platform_browsertest.h"
+#else
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/ui_test_utils.h"
+#endif
+
 namespace extensions {
 namespace {
 
@@ -37,7 +42,13 @@
   content::RenderProcessHost::SetMaxRendererProcessCount(1);
 }
 
-class CrossOriginIsolationTest : public ExtensionBrowserTest {
+#if BUILDFLAG(IS_ANDROID)
+using CrossOriginIsolationTestBase = ExtensionPlatformBrowserTest;
+#else
+using CrossOriginIsolationTestBase = ExtensionBrowserTest;
+#endif
+
+class CrossOriginIsolationTest : public CrossOriginIsolationTestBase {
  public:
   CrossOriginIsolationTest() = default;
   ~CrossOriginIsolationTest() override = default;
@@ -45,7 +56,7 @@
   CrossOriginIsolationTest& operator=(const CrossOriginIsolationTest&) = delete;
 
   void SetUpOnMainThread() override {
-    ExtensionBrowserTest::SetUpOnMainThread();
+    CrossOriginIsolationTestBase::SetUpOnMainThread();
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
   }
@@ -58,12 +69,16 @@
     const char* test_js = "";
     bool is_platform_app = false;
   };
+  using CrossOriginIsolationTestBase::LoadExtension;
   const Extension* LoadExtension(TestExtensionDir& dir,
                                  const Options& options) {
     CHECK(options.coep_value);
     CHECK(options.coop_value);
     CHECK(!options.is_platform_app || !options.use_service_worker)
         << "Platform apps cannot use 'service_worker' key.";
+#if BUILDFLAG(IS_ANDROID)
+    CHECK(!options.is_platform_app) << "Android does not support platform apps";
+#endif
 
     static constexpr char kManifestTemplate[] = R"(
       {
@@ -122,7 +137,7 @@
     dir.WriteFile(FILE_PATH_LITERAL("test.html"),
                   "<script src='test.js'></script>");
     dir.WriteFile(FILE_PATH_LITERAL("test.js"), options.test_js);
-    return ExtensionBrowserTest::LoadExtension(dir.UnpackedPath());
+    return LoadExtension(dir.UnpackedPath());
   }
 
   bool IsCrossOriginIsolated(content::RenderFrameHost* host) {
@@ -232,7 +247,9 @@
                             image_url_without_host_permissions));
 }
 
+#if !BUILDFLAG(IS_ANDROID)
 // Tests that platform apps can opt into cross origin isolation.
+// Not run on desktop Android because Android does not support platform apps.
 IN_PROC_BROWSER_TEST_F(CrossOriginIsolationTest,
                        CrossOriginIsolation_PlatformApps) {
   RestrictProcessCount();
@@ -267,6 +284,9 @@
 
 // Tests that a web accessible frame from a cross origin isolated extension is
 // not cross origin isolated.
+// TODO(https://crbug.com/388110291): Port to desktop Android. The URL of the
+// extension_iframe is "about:blank#blocked" for unclear reasons. Also, the
+// chrome.browserAction API is not yet supported.
 IN_PROC_BROWSER_TEST_F(CrossOriginIsolationTest, WebAccessibleFrame) {
   RestrictProcessCount();
 
@@ -281,16 +301,17 @@
   EXPECT_TRUE(IsCrossOriginIsolated(coi_background_render_frame_host));
 
   GURL extension_test_url = coi_extension->GetResourceURL("test.html");
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), extension_test_url));
-  content::WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(
+      content::NavigateToURL(GetActiveWebContents(), extension_test_url));
+  content::WebContents* web_contents = GetActiveWebContents();
   EXPECT_TRUE(IsCrossOriginIsolated(web_contents->GetPrimaryMainFrame()));
   EXPECT_EQ(web_contents->GetPrimaryMainFrame()->GetProcess(),
             coi_background_render_frame_host->GetProcess());
 
   // Load test.html as a web accessible resource inside a web frame.
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), embedded_test_server()->GetURL("/iframe_blank.html")));
+  ASSERT_TRUE(content::NavigateToURL(
+      GetActiveWebContents(),
+      embedded_test_server()->GetURL("/iframe_blank.html")));
   ASSERT_TRUE(
       content::NavigateIframeToURL(web_contents, "test", extension_test_url));
 
@@ -402,6 +423,7 @@
     }
   }
 }
+#endif  // !BUILDFLAG(IS_ANDROID)
 
 // Test that an extension service worker for a cross origin isolated extension
 // is not cross origin isolated. See crbug.com/1131404.
@@ -425,8 +447,10 @@
   EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
 
   GURL extension_test_url = coi_extension->GetResourceURL("test.html");
+  ASSERT_TRUE(
+      content::NavigateToURL(GetActiveWebContents(), extension_test_url));
   content::RenderFrameHost* extension_tab =
-      ui_test_utils::NavigateToURL(browser(), extension_test_url);
+      content::ConvertToRenderFrameHost(GetActiveWebContents());
   ASSERT_TRUE(extension_tab);
 
   // The service worker should be active since it's waiting for a response to
@@ -462,6 +486,7 @@
                 coi_extension, service_worker_process->GetDeprecatedID(), url));
 }
 
+#if !BUILDFLAG(IS_ANDROID)
 // Tests certain extension APIs which retrieve in-process extension windows.
 // Test these for a cross origin isolated extension with non-cross origin
 // isolated contexts.
@@ -544,6 +569,8 @@
 
 // Tests extension messaging between cross origin isolated and
 // non-cross-origin-isolated frames of an extension.
+// TODO(https://crbug.com/388110291): Port to desktop Android when
+// chrome.runtime.sendMessage() works there.
 IN_PROC_BROWSER_TEST_F(CrossOriginIsolationTest, ExtensionMessaging_Frames) {
   RestrictProcessCount();
 
@@ -633,6 +660,8 @@
 // Tests extension messaging between a cross origin isolated extension frame and
 // the extension service worker which is not cross origin isolated (and hence in
 // a different process).
+// TODO(https://crbug.com/388110291): Port to desktop Android when
+// chrome.runtime.sendMessage() works there.
 IN_PROC_BROWSER_TEST_F(CrossOriginIsolationTest,
                        ExtensionMessaging_ServiceWorker) {
   RestrictProcessCount();
@@ -710,6 +739,8 @@
 
 // Verify extension resource access if it's in an iframe. Regression test for
 // crbug.com/1343610.
+// TODO(https://crbug.com/388110291): Port to desktop Android when we have a
+// cross-platform replacement for NavigateParams.
 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, ExtensionResourceInIframe) {
   EXPECT_TRUE(embedded_test_server()->Start());
 
@@ -809,6 +840,7 @@
     EXPECT_EQ(target, iframe->GetLastCommittedURL());
   }
 }
+#endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_platform_browsertest.cc b/chrome/browser/extensions/extension_platform_browsertest.cc
index e395bc0e..c02b52e8 100644
--- a/chrome/browser/extensions/extension_platform_browsertest.cc
+++ b/chrome/browser/extensions/extension_platform_browsertest.cc
@@ -21,7 +21,12 @@
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/ui_test_utils.h"
 #else
+#include "base/check.h"
+#include "chrome/browser/android/tab_android.h"
 #include "chrome/browser/extensions/desktop_android/desktop_android_extension_system.h"
 #include "chrome/browser/extensions/platform_test_extension_loader.h"
 #include "chrome/browser/ui/android/tab_model/tab_model.h"
@@ -332,6 +337,37 @@
 #endif
 }
 
+content::RenderFrameHost* ExtensionPlatformBrowserTest::NavigateToURLInNewTab(
+    const GURL& url) {
+#if !BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
+  return ui_test_utils::NavigateToURLWithDisposition(
+      browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+#else
+  content::WebContents* active_web_contents = GetActiveWebContents();
+  TabModel* tab_model =
+      TabModelList::GetTabModelForWebContents(active_web_contents);
+  TabAndroid* parent = TabAndroid::FromWebContents(active_web_contents);
+  std::unique_ptr<content::WebContents> contents = content::WebContents::Create(
+      content::WebContents::CreateParams(profile()));
+  content::WebContents* new_web_contents = contents.release();
+  tab_model->CreateTab(parent, new_web_contents, /*select=*/true);
+  // Navigate and block until navigation finishes.
+  CHECK(content::NavigateToURL(new_web_contents, url));
+  return content::ConvertToRenderFrameHost(new_web_contents);
+#endif
+}
+
+int ExtensionPlatformBrowserTest::GetTabCount() {
+#if !BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
+  return browser()->tab_strip_model()->count();
+#else
+  TabModel* tab_model =
+      TabModelList::GetTabModelForWebContents(GetActiveWebContents());
+  return tab_model->GetTabCount();
+#endif
+}
+
 void ExtensionPlatformBrowserTest::SetUpTestProtocolHandler() {
   test_protocol_handler_ = base::BindRepeating(
       &ExtensionProtocolTestResourcesHandler, GetTestResourcesParentDir());
diff --git a/chrome/browser/extensions/extension_platform_browsertest.h b/chrome/browser/extensions/extension_platform_browsertest.h
index 030ae9b..cd85f1bb 100644
--- a/chrome/browser/extensions/extension_platform_browsertest.h
+++ b/chrome/browser/extensions/extension_platform_browsertest.h
@@ -16,6 +16,7 @@
 class Profile;
 
 namespace content {
+class RenderFrameHost;
 class WebContents;
 }
 
@@ -70,6 +71,12 @@
   // `profile`, blocking until the navigation finishes.
   void PlatformOpenURLOffTheRecord(Profile* profile, const GURL& url);
 
+  // Opens `url` in a new tab, blocking until the navigation finishes.
+  content::RenderFrameHost* NavigateToURLInNewTab(const GURL& url);
+
+  // Returns the number of tabs in the current window.
+  int GetTabCount();
+
   // Sets up `test_protocol_handler_` so that
   // chrome-extensions://<extension_id>/_test_resources/foo maps to
   // chrome/test/data/extensions/foo.
diff --git a/chrome/browser/extensions/extension_platform_browsertest_browsertest.cc b/chrome/browser/extensions/extension_platform_browsertest_browsertest.cc
new file mode 100644
index 0000000..b0da154
--- /dev/null
+++ b/chrome/browser/extensions/extension_platform_browsertest_browsertest.cc
@@ -0,0 +1,19 @@
+// Copyright 2025 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/extensions/extension_platform_browsertest.h"
+
+#include "content/public/test/browser_test.h"
+
+namespace extensions {
+namespace {
+
+IN_PROC_BROWSER_TEST_F(ExtensionPlatformBrowserTest, NavigateToURLInNewTab) {
+  ASSERT_EQ(GetTabCount(), 1);
+  EXPECT_TRUE(NavigateToURLInNewTab(GURL("about:blank")));
+  EXPECT_EQ(GetTabCount(), 2);
+}
+
+}  // namespace
+}  // namespace extensions
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 392218b2..007ed46 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -226,18 +226,14 @@
 
 void ExtensionService::BlocklistExtensionForTest(
     const std::string& extension_id) {
-  blocklist_prefs::SetSafeBrowsingExtensionBlocklistState(
-      extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
-      extension_prefs_);
-  OnBlocklistStateAdded(extension_id);
+  extension_registrar_.BlocklistExtensionForTest(extension_id);  // IN-TEST
 }
 
 void ExtensionService::GreylistExtensionForTest(
     const std::string& extension_id,
     const BitMapBlocklistState& state) {
-  blocklist_prefs::SetSafeBrowsingExtensionBlocklistState(extension_id, state,
-                                                          extension_prefs_);
-  OnGreylistStateAdded(extension_id, state);
+  extension_registrar_.GreylistExtensionForTest(extension_id,
+                                                state);  // IN-TEST
 }
 
 bool ExtensionService::OnExternalExtensionUpdateUrlFound(
@@ -958,86 +954,21 @@
 }
 
 void ExtensionService::OnGreylistStateRemoved(const std::string& extension_id) {
-  bool is_on_sb_list = (blocklist_prefs::GetSafeBrowsingExtensionBlocklistState(
-                            extension_id, extension_prefs_) !=
-                        BitMapBlocklistState::NOT_BLOCKLISTED);
-  bool is_on_omaha_list =
-      blocklist_prefs::HasAnyOmahaGreylistState(extension_id, extension_prefs_);
-  if (is_on_sb_list || is_on_omaha_list) {
-    return;
-  }
-  // Clear all acknowledged states so the extension will still get disabled if
-  // it is added to the greylist again.
-  blocklist_prefs::ClearAcknowledgedGreylistStates(extension_id,
-                                                   extension_prefs_);
-  RemoveDisableReasonAndMaybeEnable(extension_id,
-                                    disable_reason::DISABLE_GREYLIST);
+  extension_registrar_.OnGreylistStateRemoved(extension_id);
 }
 
 void ExtensionService::OnGreylistStateAdded(const std::string& extension_id,
                                             BitMapBlocklistState new_state) {
-#if DCHECK_IS_ON()
-  bool has_new_state_on_sb_list =
-      (blocklist_prefs::GetSafeBrowsingExtensionBlocklistState(
-           extension_id, extension_prefs_) == new_state);
-  bool has_new_state_on_omaha_list = blocklist_prefs::HasOmahaBlocklistState(
-      extension_id, new_state, extension_prefs_);
-  DCHECK(has_new_state_on_sb_list || has_new_state_on_omaha_list);
-#endif
-  if (blocklist_prefs::HasAcknowledgedBlocklistState(extension_id, new_state,
-                                                     extension_prefs_)) {
-    // If the extension is already acknowledged, don't disable it again
-    // because it can be already re-enabled by the user. This could happen if
-    // the extension is added to the SafeBrowsing blocklist, and then
-    // subsequently marked by Omaha. In this case, we don't want to disable the
-    // extension twice.
-    return;
-  }
-
-  // Set the current greylist states to acknowledge immediately because the
-  // extension is disabled silently. Clear the other acknowledged state because
-  // when the state changes to another greylist state in the future, we'd like
-  // to disable the extension again.
-  blocklist_prefs::UpdateCurrentGreylistStatesAsAcknowledged(extension_id,
-                                                             extension_prefs_);
-  DisableExtension(extension_id, disable_reason::DISABLE_GREYLIST);
+  extension_registrar_.OnGreylistStateAdded(extension_id, new_state);
 }
 
 void ExtensionService::OnBlocklistStateRemoved(
     const std::string& extension_id) {
-  if (blocklist_prefs::IsExtensionBlocklisted(extension_id, extension_prefs_)) {
-    return;
-  }
-
-  // Clear acknowledged state.
-  blocklist_prefs::RemoveAcknowledgedBlocklistState(
-      extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
-      extension_prefs_);
-
-  scoped_refptr<const Extension> extension =
-      registry_->blocklisted_extensions().GetByID(extension_id);
-  DCHECK(extension);
-  registry_->RemoveBlocklisted(extension_id);
-  AddExtension(extension.get());
+  extension_registrar_.OnBlocklistStateRemoved(extension_id);
 }
 
 void ExtensionService::OnBlocklistStateAdded(const std::string& extension_id) {
-  DCHECK(
-      blocklist_prefs::IsExtensionBlocklisted(extension_id, extension_prefs_));
-  // The extension was already acknowledged by the user, it should already be in
-  // the unloaded state.
-  if (blocklist_prefs::HasAcknowledgedBlocklistState(
-          extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
-          extension_prefs_)) {
-    DCHECK(base::Contains(registry_->blocklisted_extensions().GetIDs(),
-                          extension_id));
-    return;
-  }
-
-  scoped_refptr<const Extension> extension =
-      registry_->GetInstalledExtension(extension_id);
-  registry_->AddBlocklisted(extension);
-  UnloadExtension(extension_id, UnloadedExtensionReason::BLOCKLIST);
+  extension_registrar_.OnBlocklistStateAdded(extension_id);
 }
 
 void ExtensionService::RemoveDisableReasonAndMaybeEnable(
@@ -1054,7 +985,6 @@
 
 void ExtensionService::DisableExtension(const std::string& extension_id,
                                         int disable_reasons) {
-  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   extension_registrar_.DisableExtension(extension_id, disable_reasons);
 }
 
@@ -1062,7 +992,6 @@
     const Extension* source_extension,
     const std::string& extension_id,
     disable_reason::DisableReason disable_reasons) {
-  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   extension_registrar_.DisableExtensionWithSource(
       source_extension, extension_id, disable_reasons);
 }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index c3c46dd..394555c 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -8459,6 +8459,11 @@
     "expiry_milestone": 140
   },
   {
+    "name": "tab-strip-group-drag-drop-android",
+    "owners": [ "zheliooo@google.com", "nemco@google.com", "skavuluru@google.com", "clank-large-form-factors@google.com"],
+    "expiry_milestone": 145
+  },
+  {
     "name": "tab-strip-group-reorder-android",
     "owners": [ "skavuluru@google.com", "zheliooo@google.com", "nemco@google.com", "clank-large-form-factors@google.com" ],
     "expiry_milestone": 140
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index dc6c3d8..d2af346 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4813,6 +4813,14 @@
     "Collapsed groups can be clicked again to expand. Synced tab groups will "
     "immediately be collapsed.";
 
+const char kTabStripGroupDragDropAndroidName[] =
+    "Tab Strip Group Drag Drop Android";
+const char kTabStripGroupDragDropAndroidDescription[] =
+    "Enables long-pressing on tab strip tab group indicators to start "
+    "drag-and-drop. Users can drag the tab group off the tab strip and drop it "
+    "into another window in split-screen mode or create a new window by "
+    "dropping it on the edge of Chrome.";
+
 const char kTabStripGroupReorderAndroidName[] = "Tab Strip Group Reorder";
 const char kTabStripGroupReorderAndroidDescription[] =
     "Enables long-pressing on tab strip tab group indicators to enter reorder "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 401fd4d9..d2734b8 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2823,6 +2823,9 @@
 extern const char kTabStripGroupCollapseAndroidName[];
 extern const char kTabStripGroupCollapseAndroidDescription[];
 
+extern const char kTabStripGroupDragDropAndroidName[];
+extern const char kTabStripGroupDragDropAndroidDescription[];
+
 extern const char kTabStripGroupReorderAndroidName[];
 extern const char kTabStripGroupReorderAndroidDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 5d4724c2..4005b7b 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -1041,6 +1041,10 @@
              "TabStripGroupCollapseAndroid",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kTabStripGroupDragDropAndroid,
+             "TabStripGroupDragDropAndroid",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kTabStripGroupReorderAndroid,
              "TabStripGroupReorderAndroid",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 34aceabc..e69c711 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -177,6 +177,7 @@
 BASE_DECLARE_FEATURE(kTabGroupCreationDialogAndroid);
 BASE_DECLARE_FEATURE(kTabStateFlatBuffer);
 BASE_DECLARE_FEATURE(kTabStripGroupCollapseAndroid);
+BASE_DECLARE_FEATURE(kTabStripGroupDragDropAndroid);
 BASE_DECLARE_FEATURE(kTabStripGroupReorderAndroid);
 BASE_DECLARE_FEATURE(kTabStripIncognitoMigration);
 BASE_DECLARE_FEATURE(kTabStripLayoutOptimization);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 4e732d3..9ce634f4 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -530,6 +530,7 @@
             "TabGroupSyncAutoOpenKillSwitch";
     public static final String TAB_RESUMPTION_MODULE_ANDROID = "TabResumptionModuleAndroid";
     public static final String TAB_STRIP_GROUP_COLLAPSE = "TabStripGroupCollapseAndroid";
+    public static final String TAB_STRIP_GROUP_DRAG_DROP_ANDROID = "TabStripGroupDragDropAndroid";
     public static final String TAB_STRIP_GROUP_REORDER = "TabStripGroupReorderAndroid";
     public static final String TAB_STRIP_INCOGNITO_MIGRATION = "TabStripIncognitoMigration";
     public static final String TAB_STRIP_LAYOUT_OPTIMIZATION = "TabStripLayoutOptimization";
diff --git a/chrome/browser/glic/BUILD.gn b/chrome/browser/glic/BUILD.gn
index 8381ba1..5af8369fe 100644
--- a/chrome/browser/glic/BUILD.gn
+++ b/chrome/browser/glic/BUILD.gn
@@ -60,6 +60,8 @@
     "glic_ui.cc",
     "glic_view.cc",
     "glic_window_controller.cc",
+    "glic_window_resize_animation.cc",
+    "glic_window_resize_animation.h",
     "guest_util.cc",
   ]
   public_deps = [ "//chrome/browser:browser_public_dependencies" ]
@@ -141,7 +143,6 @@
   testonly = true
   defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
   sources = [
-    "border_view_browsertest.cc",
     "glic_browsertest.cc",
     "glic_policy_browsertest.cc",
     "guest_util_browsertest.cc",
@@ -167,7 +168,10 @@
 source_set("interactive_ui_tests") {
   testonly = true
   defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-  sources = [ "glic_window_controller_ui_test.cc" ]
+  sources = [
+    "border_view_browsertest.cc",
+    "glic_window_controller_ui_test.cc",
+  ]
   deps = [
     ":glic",
     "//chrome/browser",
diff --git a/chrome/browser/glic/border_view.cc b/chrome/browser/glic/border_view.cc
index 4784045f..e8c83c5 100644
--- a/chrome/browser/glic/border_view.cc
+++ b/chrome/browser/glic/border_view.cc
@@ -47,18 +47,11 @@
 }
 
 // static.
-void BorderView::CancelAllAnimationsForProfile(Profile* profile) {
-  std::vector<Browser*> browsers = chrome::FindAllBrowsersWithProfile(profile);
-  for (auto* browser : browsers) {
-    if (!browser || !browser->window()) {
-      // Unittests, or the View tree is torn down.
-      continue;
-    }
-    // Border is null if the feature is disabled for `profile`.
-    if (auto* border = browser->GetBrowserView().glic_border()) {
-      border->CancelAnimation();
-    }
-  }
+void BorderView::CancelAnimation(BrowserWindowInterface* browser_interface) {
+  BorderView* glic_border =
+      static_cast<Browser*>(browser_interface)->GetBrowserView().glic_border();
+  CHECK(glic_border);
+  glic_border->CancelAnimation();
 }
 
 BorderView::BorderView() = default;
@@ -136,6 +129,8 @@
 
   compositor_->RemoveAnimationObserver(this);
   compositor_ = nullptr;
+  progress_ = 0.f;
+  first_frame_time_ = base::TimeTicks();
 
   // `DestroyLayer()` schedules another paint to repaint the affected area by
   // the destroyed layer.
diff --git a/chrome/browser/glic/border_view.h b/chrome/browser/glic/border_view.h
index 4714478..32b12fe 100644
--- a/chrome/browser/glic/border_view.h
+++ b/chrome/browser/glic/border_view.h
@@ -10,7 +10,7 @@
 #include "ui/views/metadata/view_factory.h"
 #include "ui/views/view.h"
 
-class Profile;
+class BrowserWindowInterface;
 
 namespace content {
 class WebContents;
@@ -32,7 +32,7 @@
   static BorderView* FindBorderForWebContents(
       const content::WebContents* web_contents);
 
-  static void CancelAllAnimationsForProfile(Profile* profile);
+  static void CancelAnimation(BrowserWindowInterface* browser_interface);
 
   BorderView();
   BorderView(const BorderView&) = delete;
@@ -50,6 +50,8 @@
 
   void CancelAnimation();
 
+  ui::Compositor* compositor_for_testing() const { return compositor_; }
+
  private:
   raw_ptr<ui::Compositor> compositor_ = nullptr;
 
diff --git a/chrome/browser/glic/border_view_browsertest.cc b/chrome/browser/glic/border_view_browsertest.cc
index f727ca8b..748cebe 100644
--- a/chrome/browser/glic/border_view_browsertest.cc
+++ b/chrome/browser/glic/border_view_browsertest.cc
@@ -9,11 +9,15 @@
 #include <math.h>
 
 #include "cc/test/pixel_test_utils.h"
+#include "chrome/browser/glic/glic_keyed_service_factory.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
+#include "chrome/browser/ui/views/tabs/glic_button.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
-#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/interaction/interactive_browser_test.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -25,7 +29,7 @@
 namespace glic {
 
 namespace {
-class BorderViewBrowserTest : public InProcessBrowserTest {
+class BorderViewBrowserTest : public InteractiveBrowserTest {
  public:
   BorderViewBrowserTest() {
     scoped_feature_list_.InitWithFeatures(
@@ -67,6 +71,18 @@
     return tab_strip_model->GetTabAtIndex(0)->GetContents()->GetViewBounds();
   }
 
+  // Appends a new tab with `url` to the end of the tabstrip.
+  void AppendTab(Browser* browser, const GURL& url) {
+    chrome::AddTabAt(browser, url, /*index=*/-1, /*foreground=*/true);
+  }
+
+  GlicButton* GetGlicButton(Browser* browser) {
+    TabStripRegionView* tab_strip_view =
+        browser->GetBrowserView().tab_strip_region_view();
+    EXPECT_TRUE(tab_strip_view);
+    return tab_strip_view->GetGlicButton();
+  }
+
  protected:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
@@ -135,7 +151,7 @@
 
   base::TimeTicks timestamp = base::TimeTicks::Now();
 
-  // Note: the following is based on having the animation duration = 2 seconds.
+  // Note: the following is based on having animation duration = 2 seconds.
   // timestamp = 0 (now).
   {
     border->OnAnimationStep(timestamp);
@@ -228,6 +244,131 @@
   border->CancelAnimation();
 }
 
+// Ensures that the border animation state is reset after canceling the
+// animation.
+IN_PROC_BROWSER_TEST_F(BorderViewBrowserTest, AnimationStateReset) {
+  auto* border = browser()->window()->AsBrowserView()->glic_border();
+  ASSERT_TRUE(border);
+
+  border->StartAnimation();
+  border->OnAnimationStep(base::TimeTicks::Now());
+  border->CancelAnimation();
+
+  ASSERT_FALSE(border->compositor_for_testing());
+}
+
+// Ensures that the border animation is restarted when tab focus changes.
+IN_PROC_BROWSER_TEST_F(BorderViewBrowserTest, FocusedTabChange) {
+  auto* border = browser()->window()->AsBrowserView()->glic_border();
+  ASSERT_TRUE(border);
+  gfx::Rect capture_rect = GetContentsRectForWindow(browser());
+  SkColor background_color = SkColors::kBlack.toSkColor();
+  SkColor border_color =
+      browser()->GetBrowserView().GetColorProvider()->GetColor(
+          ui::kColorSysPrimary);
+
+  // Mimicking the user journey by clicking the button and having the WebApp set
+  // the context access indicator status.
+  auto* const glic_keyed_service =
+      glic::GlicKeyedServiceFactory::GetGlicKeyedService(
+          browser()->GetProfile());
+  GetGlicButton(browser())->LaunchUI();
+  glic_keyed_service->SetContextAccessIndicator(true);
+
+  base::TimeTicks timestamp = base::TimeTicks::Now();
+
+  // Note: the following is based on having animation duration = 2 seconds.
+  // timestamp = 0 (now).
+  {
+    border->OnAnimationStep(timestamp);
+    gfx::Canvas canvas(capture_rect.size(), /*image_scale=*/1.0f,
+                       /*is_opaque=*/true);
+    canvas.DrawColor(background_color);
+    border->OnPaint(&canvas);
+    SkBitmap actual_bitmap = canvas.GetBitmap();
+
+    SkBitmap expected_bitmap = ConstructExpectedBitmap(
+        capture_rect.size(),
+        /*border_color=*/border_color,
+        /*center_color=*/background_color, /*border_width=*/2, /*alpha=*/0);
+
+    EXPECT_TRUE(cc::MatchesBitmap(actual_bitmap, expected_bitmap,
+                                  cc::ExactPixelComparator()));
+  }
+
+  // timestamp = 0.5 seconds.
+  {
+    timestamp += base::Seconds(0.5);
+    border->OnAnimationStep(timestamp);
+    gfx::Canvas canvas(capture_rect.size(), /*image_scale=*/1.0f,
+                       /*is_opaque=*/true);
+    canvas.DrawColor(background_color);
+    border->OnPaint(&canvas);
+    SkBitmap actual_bitmap = canvas.GetBitmap();
+
+    float progress = sin(0.125 * M_PI);
+    float border_width = 2 + (8 * progress);
+    SkBitmap expected_bitmap = ConstructExpectedBitmap(
+        capture_rect.size(),
+        /*border_color=*/border_color,
+        /*center_color=*/background_color, /*border_width=*/border_width,
+        /*alpha=*/progress);
+
+    EXPECT_TRUE(cc::MatchesBitmap(actual_bitmap, expected_bitmap,
+                                  cc::ExactPixelComparator()));
+  }
+
+  // Changing the active tab.
+  AppendTab(browser(), GURL(chrome::kChromeUINewTabURL));
+  ASSERT_EQ(browser()->tab_strip_model()->active_index(), 1);
+
+  // Since the active tab has changed, the animation should start from the
+  // beginning.
+  // timestamp = 6 seconds; second 0 in the current animation.
+  {
+    timestamp += base::Seconds(5.5);
+    border->OnAnimationStep(timestamp);
+    gfx::Canvas canvas(capture_rect.size(), /*image_scale=*/1.0f,
+                       /*is_opaque=*/true);
+    canvas.DrawColor(background_color);
+    border->OnPaint(&canvas);
+    SkBitmap actual_bitmap = canvas.GetBitmap();
+
+    SkBitmap expected_bitmap = ConstructExpectedBitmap(
+        capture_rect.size(),
+        /*border_color=*/border_color,
+        /*center_color=*/background_color, /*border_width=*/2,
+        /*alpha=*/0);
+
+    EXPECT_TRUE(cc::MatchesBitmap(actual_bitmap, expected_bitmap,
+                                  cc::ExactPixelComparator()));
+  }
+
+  // timestamp = 6.5 seconds; second 0.5 in the current animation.
+  {
+    timestamp += base::Seconds(0.5);
+    border->OnAnimationStep(timestamp);
+    gfx::Canvas canvas(capture_rect.size(), /*image_scale=*/1.0f,
+                       /*is_opaque=*/true);
+    canvas.DrawColor(background_color);
+    border->OnPaint(&canvas);
+    SkBitmap actual_bitmap = canvas.GetBitmap();
+
+    float progress = sin(0.125 * M_PI);
+    float border_width = 2 + (8 * progress);
+    SkBitmap expected_bitmap = ConstructExpectedBitmap(
+        capture_rect.size(),
+        /*border_color=*/border_color,
+        /*center_color=*/background_color, /*border_width=*/border_width,
+        /*alpha=*/progress);
+
+    EXPECT_TRUE(cc::MatchesBitmap(actual_bitmap, expected_bitmap,
+                                  cc::ExactPixelComparator()));
+  }
+
+  border->CancelAnimation();
+}
+
 namespace {
 class BorderViewFeatureDisabledBrowserTest : public BorderViewBrowserTest {
  public:
diff --git a/chrome/browser/glic/glic_border_view_manager.cc b/chrome/browser/glic/glic_border_view_manager.cc
index f951e7482..3e16452e 100644
--- a/chrome/browser/glic/glic_border_view_manager.cc
+++ b/chrome/browser/glic/glic_border_view_manager.cc
@@ -42,8 +42,7 @@
 GlicBorderViewManager::~GlicBorderViewManager() = default;
 
 void GlicBorderViewManager::UpdateBorderView() {
-  BorderView::CancelAllAnimationsForProfile(
-      Profile::FromBrowserContext(browser_->GetProfile()));
+  BorderView::CancelAnimation(browser_.get());
   auto* const model = browser_->GetTabStripModel();
   CHECK(model);
   const int index = model->GetIndexOfWebContents(focused_tab_.get());
diff --git a/chrome/browser/glic/glic_window_controller.cc b/chrome/browser/glic/glic_window_controller.cc
index e056b21..779ac0f 100644
--- a/chrome/browser/glic/glic_window_controller.cc
+++ b/chrome/browser/glic/glic_window_controller.cc
@@ -7,6 +7,7 @@
 #include "base/check.h"
 #include "chrome/browser/glic/glic.mojom.h"
 #include "chrome/browser/glic/glic_view.h"
+#include "chrome/browser/glic/glic_window_resize_animation.h"
 #include "chrome/browser/media/audio_ducker.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
@@ -318,6 +319,10 @@
   Close();
 }
 
+void GlicWindowController::ResizeFinished() {
+  window_resize_animation_.reset();
+}
+
 void GlicWindowController::Attach() {
   // TODO (crbug.com/388917542) Determine which browser to attach to. Currently
   // attaches to the last focused glic-compatible browser.
@@ -376,8 +381,11 @@
     return false;
   }
 
-  glic_window_widget_->SetSize(size);
-  GetGlicView()->web_view()->SetSize(size);
+  window_resize_animation_.reset();
+  window_resize_animation_ = std::make_unique<GlicWindowResizeAnimation>(
+      glic_window_widget_.get(), GetGlicView(), size,
+      /*duration=*/base::Milliseconds(0),
+      base::BindOnce(&GlicWindowController::ResizeFinished, GetWeakPtr()));
   return true;
 }
 
@@ -403,8 +411,7 @@
   if (!glic_window_widget_) {
     return;
   }
-  glic_window_widget_->CloseWithReason(
-      views::Widget::ClosedReason::kCloseButtonClicked);
+  window_resize_animation_.reset();
   glic_widget_observer_.reset();
   window_event_observer_.reset();
   browser_close_subscription_.reset();
diff --git a/chrome/browser/glic/glic_window_controller.h b/chrome/browser/glic/glic_window_controller.h
index cf1c3a7..03f7b8e 100644
--- a/chrome/browser/glic/glic_window_controller.h
+++ b/chrome/browser/glic/glic_window_controller.h
@@ -31,6 +31,7 @@
 }  // namespace
 
 class GlicView;
+class GlicWindowResizeAnimation;
 
 // Class for Glic window controller. Owned by the Glic profile keyed-service.
 // This gets created when the Glic window needs to be shown and it owns the Glic
@@ -191,6 +192,9 @@
   // When the attached browser is closed, this is invoked so we can clean up.
   void AttachedBrowserDidClose(BrowserWindowInterface* browser);
 
+  // Called when the programmatic resize has finished.
+  void ResizeFinished();
+
   AttachedTargetWidgetObserver attached_target_widget_observer_{this};
   base::WeakPtr<Browser> attached_browser_;
 
@@ -212,6 +216,7 @@
   std::unique_ptr<ContentsAndProfileKeepAlive> contents_;
 
   std::unique_ptr<views::Widget> glic_window_widget_;
+  std::unique_ptr<GlicWindowResizeAnimation> window_resize_animation_;
   bool glic_window_widget_visible_ = false;
 
   // Indicates `Show()` has been called, but not `FinishShow()`.
diff --git a/chrome/browser/glic/glic_window_resize_animation.cc b/chrome/browser/glic/glic_window_resize_animation.cc
new file mode 100644
index 0000000..6056334
--- /dev/null
+++ b/chrome/browser/glic/glic_window_resize_animation.cc
@@ -0,0 +1,35 @@
+// Copyright 2025 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/glic/glic_window_resize_animation.h"
+
+#include "base/task/sequenced_task_runner.h"
+#include "chrome/browser/glic/glic_view.h"
+#include "ui/views/controls/webview/webview.h"
+#include "ui/views/widget/widget.h"
+
+namespace glic {
+
+GlicWindowResizeAnimation::GlicWindowResizeAnimation(
+    views::Widget* widget,
+    GlicView* view,
+    gfx::Size new_size,
+    base::TimeDelta duration,
+    FinishedCallback finished_callback)
+    : finished_callback_(std::move(finished_callback)) {
+  widget->SetSize(new_size);
+  view->web_view()->SetSize(new_size);
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(&GlicWindowResizeAnimation::Finished,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
+GlicWindowResizeAnimation::~GlicWindowResizeAnimation() = default;
+
+void GlicWindowResizeAnimation::Finished() {
+  // Destroys `this`.
+  std::move(finished_callback_).Run();
+}
+
+}  // namespace glic
diff --git a/chrome/browser/glic/glic_window_resize_animation.h b/chrome/browser/glic/glic_window_resize_animation.h
new file mode 100644
index 0000000..8a75fe9
--- /dev/null
+++ b/chrome/browser/glic/glic_window_resize_animation.h
@@ -0,0 +1,49 @@
+// Copyright 2025 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_GLIC_GLIC_WINDOW_RESIZE_ANIMATION_H_
+#define CHROME_BROWSER_GLIC_GLIC_WINDOW_RESIZE_ANIMATION_H_
+
+#include "base/callback_list.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "ui/gfx/geometry/rect_f.h"
+
+namespace views {
+class Widget;
+}  // namespace views
+
+namespace glic {
+
+class GlicView;
+
+// This class controls the animation of the glic window from one size to
+// another. It has the following constraints that the caller must enforce:
+// * The glic window and glic view must outlive instances of this class.
+// * There can be at most 1 animation at any point in time.
+// This class will generally override any other changes to window size.
+class GlicWindowResizeAnimation {
+ public:
+  // The caller is expected to destroy GlicWindowResizeAnimation upon receiving
+  // FinishedCallback. FinishedCallback is always invoked asynchronously.
+  using FinishedCallback = base::OnceClosure;
+  GlicWindowResizeAnimation(views::Widget* widget,
+                            GlicView* view,
+                            gfx::Size new_size,
+                            base::TimeDelta duration,
+                            FinishedCallback finished_callback);
+  ~GlicWindowResizeAnimation();
+
+ private:
+  // Ensures that `finished_callback_` is not called if the instance is
+  // destroyed after posting the task but before the task is run.
+  void Finished();
+
+  FinishedCallback finished_callback_;
+  base::WeakPtrFactory<GlicWindowResizeAnimation> weak_ptr_factory_{this};
+};
+
+}  // namespace glic
+
+#endif  // CHROME_BROWSER_GLIC_GLIC_WINDOW_RESIZE_ANIMATION_H_
diff --git a/chrome/browser/headless/headless_mode_protocol_browsertest.cc b/chrome/browser/headless/headless_mode_protocol_browsertest.cc
index 8468de2..310c77af 100644
--- a/chrome/browser/headless/headless_mode_protocol_browsertest.cc
+++ b/chrome/browser/headless/headless_mode_protocol_browsertest.cc
@@ -310,7 +310,4 @@
 HEADLESS_MODE_PROTOCOL_TEST(CreateTargetPosition,
                             "sanity/create-target-position.js")
 
-HEADLESS_MODE_PROTOCOL_TEST(CreateTargetWindowState,
-                            "sanity/create-target-window-state.js")
-
 }  // namespace headless
diff --git a/chrome/browser/headless/test/data/protocol/sanity/create-target-window-state-expected.txt b/chrome/browser/headless/test/data/protocol/sanity/create-target-window-state-expected.txt
deleted file mode 100644
index f096d356..0000000
--- a/chrome/browser/headless/test/data/protocol/sanity/create-target-window-state-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-Tests Target.createTarget() window state handling.
-Expected: normal, actual: normal
-Expected: maximized, actual: maximized
-Expected: minimized, actual: minimized
-Expected: fullscreen, actual: fullscreen
\ No newline at end of file
diff --git a/chrome/browser/headless/test/data/protocol/sanity/create-target-window-state.js b/chrome/browser/headless/test/data/protocol/sanity/create-target-window-state.js
deleted file mode 100644
index 18babd46..0000000
--- a/chrome/browser/headless/test/data/protocol/sanity/create-target-window-state.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-(async function(testRunner) {
-  const {session, dp} = await testRunner.startBlank(
-      `Tests Target.createTarget() window state handling.`);
-
-  async function tryCreateTarget(windowState) {
-    const {targetId} =
-        (await session.protocol.Target.createTarget(
-             {'url': 'about:blank', windowState, 'newWindow': true}))
-            .result;
-
-    const {bounds} = (await dp.Browser.getWindowForTarget({targetId})).result;
-    testRunner.log(`Expected: ${windowState}, actual: ${bounds.windowState}`);
-  }
-
-  await tryCreateTarget('normal');
-  await tryCreateTarget('maximized');
-  await tryCreateTarget('minimized');
-  await tryCreateTarget('fullscreen');
-
-  testRunner.completeTest();
-})
diff --git a/chrome/browser/resources/ash/settings/BUILD.gn b/chrome/browser/resources/ash/settings/BUILD.gn
index 56866b9..e9f2dd5e 100644
--- a/chrome/browser/resources/ash/settings/BUILD.gn
+++ b/chrome/browser/resources/ash/settings/BUILD.gn
@@ -330,7 +330,6 @@
     "os_privacy_page/privacy_hub_subpage.ts",
     "os_privacy_page/privacy_hub_system_service_row.ts",
     "os_privacy_page/secure_dns.ts",
-    "os_privacy_page/secure_dns_dialog.ts",
     "os_privacy_page/secure_dns_input.ts",
     "os_privacy_page/smart_privacy_subpage.ts",
     "os_reset_page/os_powerwash_dialog.ts",
diff --git a/chrome/browser/resources/ash/settings/lazy_load.ts b/chrome/browser/resources/ash/settings/lazy_load.ts
index 7aeb8b2..d9fa1c0 100644
--- a/chrome/browser/resources/ash/settings/lazy_load.ts
+++ b/chrome/browser/resources/ash/settings/lazy_load.ts
@@ -327,7 +327,6 @@
 export {SettingsPrivacyHubMicrophoneSubpage} from './os_privacy_page/privacy_hub_microphone_subpage.js';
 export {SettingsPrivacyHubSubpage} from './os_privacy_page/privacy_hub_subpage.js';
 export {SecureDnsResolverType, SettingsSecureDnsElement} from './os_privacy_page/secure_dns.js';
-export {SettingsSecureDnsDialogElement} from './os_privacy_page/secure_dns_dialog.js';
 export {SecureDnsInputElement} from './os_privacy_page/secure_dns_input.js';
 export {SettingsSmartPrivacySubpage} from './os_privacy_page/smart_privacy_subpage.js';
 export {OsSettingsPowerwashDialogElement} from './os_reset_page/os_powerwash_dialog.js';
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns.html b/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns.html
index 992d4d7..7a04e2b8 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns.html
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns.html
@@ -27,30 +27,15 @@
       }
     </style>
 
-    <template is="dom-if" if="[[isDeprecateDnsDialogEnabled_]]">
-      <settings-toggle-button
-          id="secureDnsToggle"
-          class="hr"
-          icon="os-settings:privacy-secure-dns"
-          pref="{{secureDnsToggle_}}"
-          label="$i18n{secureDnsOsSettingsTitle}"
-          sub-label="[[secureDnsDescription_]]"
-          on-change="onToggleChanged_">
-      </settings-toggle-button>
-    </template>
-
-    <template is="dom-if" if="[[!isDeprecateDnsDialogEnabled_]]">
-      <settings-toggle-button
-          id="secureDnsToggle"
-          class="hr"
-          icon="os-settings:privacy-secure-dns"
-          pref="[[secureDnsToggle_]]"
-          label="$i18n{secureDnsOsSettingsTitle}"
-          sub-label="[[secureDnsDescription_]]"
-          on-settings-boolean-control-change="onDnsToggleClick_"
-          no-set-pref>
-      </settings-toggle-button>
-    </template>
+    <settings-toggle-button
+        id="secureDnsToggle"
+        class="hr"
+        icon="os-settings:privacy-secure-dns"
+        pref="{{secureDnsToggle_}}"
+        label="$i18n{secureDnsOsSettingsTitle}"
+        sub-label="[[secureDnsDescription_]]"
+        on-change="onToggleChanged_">
+    </settings-toggle-button>
 
     <div id="resolverOptions" hidden="[[!showSecureDnsOptions_]]">
       <div class="cr-row continuation">
@@ -98,9 +83,3 @@
         </secure-dns-input>
       </div>
     </div>
-
-    <template is="dom-if" if="[[showDisableDnsDialog_]]" restamp>
-      <settings-secure-dns-dialog id="warningDialog"
-          on-close="onDisableDnsDialogClosed_" prefs="{{prefs}}">
-      </settings-secure-dns-dialog>
-    </template>
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns.ts b/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns.ts
index 2cff92b..2c09276 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns.ts
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns.ts
@@ -21,7 +21,6 @@
 import 'chrome://resources/ash/common/cr_elements/md_select.css.js';
 import '../controls/settings_toggle_button.js';
 import './secure_dns_input.js';
-import './secure_dns_dialog.js';
 
 import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js';
 import type {PrivacyPageBrowserProxy, ResolverOption, SecureDnsSetting} from '/shared/settings/privacy_page/privacy_page_browser_proxy.js';
@@ -32,8 +31,6 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import type {SettingsToggleButtonElement} from '../controls/settings_toggle_button.js';
-
 import {getTemplate} from './secure_dns.html.js';
 import type {SecureDnsInputElement} from './secure_dns_input.js';
 
@@ -118,19 +115,6 @@
        */
       secureDnsInputValue_: String,
 
-      showDisableDnsDialog_: {
-        type: Boolean,
-        value: false,
-      },
-
-      isDeprecateDnsDialogEnabled_: {
-        type: Boolean,
-        value() {
-          return loadTimeData.getBoolean('isDeprecateDnsDialogEnabled');
-        },
-        readOnly: true,
-      },
-
       /**
        * Boolean to make network default description visible if user selects
        * Automatic option in DNS dropdown.
@@ -164,8 +148,6 @@
   private secureDnsInputValue_: string;
   private browserProxy_: PrivacyPageBrowserProxy =
       PrivacyPageBrowserProxyImpl.getInstance();
-  private showDisableDnsDialog_: boolean;
-  private readonly isDeprecateDnsDialogEnabled_: boolean;
   private showNetworkDefaultDescription_: boolean;
   private showPrivacyPolicyDescription_: boolean;
   private networkDefaultAriaDescribedBy_: string|null;
@@ -189,15 +171,6 @@
           'secure-dns-setting-changed',
           (setting: SecureDnsSetting) =>
               this.onSecureDnsPrefsChanged_(setting));
-
-      // This event will only get dispatched from the DNS dialog. If the flag
-      // kOsSettingsDeprecateDnsDialog is enabled, we don't have to listen for
-      // the event.
-      if (!this.isDeprecateDnsDialogEnabled_) {
-        this.addEventListener(
-            'dns-settings-invalid-custom-to-off-mode',
-            () => this.onSecureDnsPrefChangedToFalse_());
-      }
     });
   }
 
@@ -503,34 +476,6 @@
     }
     return undefined;
   }
-
-  private onDnsToggleClick_(): void {
-    const secureDnsToggle =
-        this.shadowRoot!.querySelector<SettingsToggleButtonElement>(
-            '#secureDnsToggle');
-    assert(secureDnsToggle);
-    if (secureDnsToggle.checked) {
-      // Always allow turning on the toggle.
-      this.turnOnDnsToggle_();
-      return;
-    }
-
-    // Do not update the underlying pref value to false. Instead if the user is
-    // attempting to turn off the toggle, present the warning dialog.
-    this.showDisableDnsDialog_ = true;
-    return;
-  }
-
-  private onDisableDnsDialogClosed_(): void {
-    // Sync the toggle's value to its pref value.
-    const secureDnsToggle =
-        this.shadowRoot!.querySelector<SettingsToggleButtonElement>(
-            '#secureDnsToggle');
-    assert(secureDnsToggle);
-    secureDnsToggle.resetToPrefValue();
-
-    this.showDisableDnsDialog_ = false;
-  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns_dialog.html b/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns_dialog.html
deleted file mode 100644
index b7b97aa..0000000
--- a/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns_dialog.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<style include="settings-shared">
-  #secureDnsDialogDescription {
-    padding-top: 10px;
-  }
-
-  cr-dialog::part(dialog) {
-    width: 370px;
-  }
-</style>
-
-<cr-dialog id="dialog" show-on-attach>
-  <div slot="title">
-    $i18n{secureDnsDialogTitle}
-  </div>
-  <div slot="body">
-    <div id="secureDnsDialogDescription">
-      $i18n{secureDnsDialogBody}
-    </div>
-  </div>
-  <div slot="button-container">
-    <cr-button id="cancelButton" class="cancel-button"
-        on-click="onCancelButtonClicked_">
-      $i18n{secureDnsDialogCancel}
-    </cr-button>
-    <cr-button id="disableButton" class="action-button"
-        on-click="onDisableClicked_">
-      $i18n{secureDnsDialogTurnOff}
-    </cr-button>
-  </div>
-</cr-dialog>
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns_dialog.ts b/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns_dialog.ts
deleted file mode 100644
index 7d25c97..0000000
--- a/chrome/browser/resources/ash/settings/os_privacy_page/secure_dns_dialog.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview This dialog explains and warns users of the expected outcome
- * when turning off secure DNS
- */
-
-import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
-import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
-
-import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js';
-import {SecureDnsMode} from '/shared/settings/privacy_page/privacy_page_browser_proxy.js';
-import type {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
-import type {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
-import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {getTemplate} from './secure_dns_dialog.html.js';
-
-export interface SettingsSecureDnsDialogElement {
-  $: {
-    dialog: CrDialogElement,
-    cancelButton: CrButtonElement,
-    disableButton: CrButtonElement,
-  };
-}
-
-const SettingsSecureDnsDialogElementBase = PrefsMixin(PolymerElement);
-
-export class SettingsSecureDnsDialogElement extends
-    SettingsSecureDnsDialogElementBase {
-  static get is() {
-    return 'settings-secure-dns-dialog' as const;
-  }
-
-  static get template() {
-    return getTemplate();
-  }
-
-  /**
-   * Sets the pref's mode to false which will turn off the toggle, and closes
-   * the dialog.
-   */
-  private onDisableClicked_(): void {
-    // If the user tries to use their own Secure Custom DNS but enters an
-    // invalid DNS configuration, the DNS value will not be saved. So in the
-    // scenario where the user switches from Secure Custom with invalid config
-    // -> OFF -> Secure Custom with invalid config, the underlying pref value
-    // will remain OFF. If the user wants to turn DNS to OFF again, the
-    // secure-dns-setting-changed WebUI event does not get fired if the mode is
-    // OFF -> OFF, so we have to manually sync the toggle state through a new
-    // event. the underlying pref's value remains OFF until the DNS config is
-    // valid.
-    if (this.getPref('dns_over_https.mode').value === SecureDnsMode.OFF) {
-      this.dispatchEvent(
-          new CustomEvent('dns-settings-invalid-custom-to-off-mode', {
-            bubbles: true,
-            composed: true,
-          }));
-    } else {
-      this.setPrefValue('dns_over_https.mode', SecureDnsMode.OFF);
-    }
-
-    this.$.dialog.close();
-  }
-
-  private onCancelButtonClicked_(): void {
-    this.$.dialog.close();
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    [SettingsSecureDnsDialogElement.is]: SettingsSecureDnsDialogElement;
-  }
-}
-
-customElements.define(
-    SettingsSecureDnsDialogElement.is, SettingsSecureDnsDialogElement);
diff --git a/chrome/browser/resources/extensions/BUILD.gn b/chrome/browser/resources/extensions/BUILD.gn
index 6483acd2..ef812d81 100644
--- a/chrome/browser/resources/extensions/BUILD.gn
+++ b/chrome/browser/resources/extensions/BUILD.gn
@@ -72,9 +72,6 @@
     "runtime_hosts_dialog.html.ts",
     "runtime_hosts_dialog.ts",
     "service.ts",
-    "shortcut_input.html.ts",
-    "shortcut_input.ts",
-    "shortcut_util.ts",
     "sidebar.html.ts",
     "sidebar.ts",
     "site_permissions/site_permissions.html.ts",
@@ -126,7 +123,6 @@
     "runtime_host_permissions.css",
     "shared_style.css",
     "shared_vars.css",
-    "shortcut_input.css",
     "sidebar.css",
     "site_permissions/site_permissions.css",
     "site_permissions/site_permissions_by_site.css",
@@ -152,6 +148,7 @@
 
   ts_deps = [
     "//third_party/lit/v3_0:build_ts",
+    "//ui/webui/resources/cr_components/cr_shortcut_input:build_ts",
     "//ui/webui/resources/cr_components/managed_footnote:build_ts",
     "//ui/webui/resources/cr_elements:build_ts",
     "//ui/webui/resources/js:build_ts",
diff --git a/chrome/browser/resources/extensions/extensions.ts b/chrome/browser/resources/extensions/extensions.ts
index ea5661d..937e905c 100644
--- a/chrome/browser/resources/extensions/extensions.ts
+++ b/chrome/browser/resources/extensions/extensions.ts
@@ -36,8 +36,6 @@
 export {ExtensionsRuntimeHostPermissionsElement} from './runtime_host_permissions.js';
 export {ExtensionsRuntimeHostsDialogElement, getMatchingUserSpecifiedSites, getPatternFromSite} from './runtime_hosts_dialog.js';
 export {Service, ServiceInterface} from './service.js';
-export {ExtensionsShortcutInputElement} from './shortcut_input.js';
-export {isValidKeyCode, Key, keystrokeToString} from './shortcut_util.js';
 export {ExtensionsSidebarElement} from './sidebar.js';
 export {ExtensionsSitePermissionsElement} from './site_permissions/site_permissions.js';
 export {ExtensionsSitePermissionsBySiteElement} from './site_permissions/site_permissions_by_site.js';
diff --git a/chrome/browser/resources/extensions/keyboard_shortcuts.html.ts b/chrome/browser/resources/extensions/keyboard_shortcuts.html.ts
index 3e9d479..39fded7 100644
--- a/chrome/browser/resources/extensions/keyboard_shortcuts.html.ts
+++ b/chrome/browser/resources/extensions/keyboard_shortcuts.html.ts
@@ -19,10 +19,15 @@
         ${item.commands.map(command => html`
           <div class="command-entry">
             <span class="command-name">${command.description}</span>
-            <extensions-shortcut-input .delegate="${this.delegate}"
-                .item="${item}" .shortcut="${command.keybinding}"
-                .command="${command}">
-            </extensions-shortcut-input>
+            <cr-shortcut-input .shortcut="${command.keybinding}"
+                input-aria-label="${this.i18n('editShortcutInputLabel',
+                    command.description, item.name)}"
+                edit-button-aria-label="${this.i18n('editShortcutButtonLabel',
+                    command.description, item.name)}"
+                @input-capture-change="${this.onInputCaptureChange_}"
+                @shortcut-updated="${this.onShortcutUpdated_.bind(this, item.id,
+                    command.name)}">
+            </cr-shortcut-input>
             <select class="md-select" @change="${this.onScopeChanged_}"
                 data-extension-id="${item.id}"
                 data-command-name="${command.name}"
diff --git a/chrome/browser/resources/extensions/keyboard_shortcuts.ts b/chrome/browser/resources/extensions/keyboard_shortcuts.ts
index 5fe2ec7..1c42c6d 100644
--- a/chrome/browser/resources/extensions/keyboard_shortcuts.ts
+++ b/chrome/browser/resources/extensions/keyboard_shortcuts.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import './shortcut_input.js';
+import 'chrome://resources/cr_components/cr_shortcut_input/cr_shortcut_input.js';
 
 import {I18nMixinLit} from 'chrome://resources/cr_elements/i18n_mixin_lit.js';
 import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
@@ -43,6 +43,16 @@
     this.addEventListener('view-enter-start', this.onViewEnter_);
   }
 
+  protected onInputCaptureChange_(event: CustomEvent<boolean>) {
+    this.delegate.setShortcutHandlingSuspended(event.detail);
+  }
+
+  protected onShortcutUpdated_(
+      itemId: string, commandName: string, event: CustomEvent<string>) {
+    this.delegate.updateExtensionCommandKeybinding(
+        itemId, commandName, event.detail);
+  }
+
   private onViewEnter_() {
     chrome.metricsPrivate.recordUserAction('Options_ExtensionCommands');
   }
diff --git a/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinator.java b/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinator.java
index f9331a0..8657473 100644
--- a/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinator.java
+++ b/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinator.java
@@ -79,6 +79,7 @@
             DesktopWindowHeuristicResult.UNKNOWN;
     private @WindowingMode int mWindowingMode = WindowingMode.UNKNOWN;
     private int mKeyboardInset;
+    private int mNavBarInset;
 
     /**
      * Instantiate the coordinator to handle drawing the tab strip into the captionBar area.
@@ -110,7 +111,7 @@
         mRootView = rootView;
         mBrowserControlsVisibilityDelegate = browserControlsVisibilityDelegate;
         mInsetObserver = insetObserver;
-        mInsetObserver.addInsetsConsumer(this, InsetConsumerSource.APP_HEADER_COORDINATOR_IME);
+        mInsetObserver.addInsetsConsumer(this, InsetConsumerSource.APP_HEADER_COORDINATOR_BOTTOM);
         mInsetsController = mRootView.getWindowInsetsController();
         mActivityLifecycleDispatcher = activityLifecycleDispatcher;
         mActivityLifecycleDispatcher.register(this);
@@ -313,7 +314,10 @@
     private boolean maybeUpdateRootViewBottomPadding() {
         int rootViewBottomPadding = mRootView.getPaddingBottom();
         // Pad the root view with IME bottom insets only if E2E is active.
-        int bottomInset = FALSE.equals(mEdgeToEdgeStateProvider.get()) ? 0 : mKeyboardInset;
+        int bottomInset =
+                FALSE.equals(mEdgeToEdgeStateProvider.get())
+                        ? 0
+                        : Math.max(mKeyboardInset, mNavBarInset);
 
         // If the root view is padded as needed already, return early.
         if (rootViewBottomPadding == bottomInset) return bottomInset != 0;
@@ -358,12 +362,15 @@
     public WindowInsetsCompat onApplyWindowInsets(
             @NonNull View view, @NonNull WindowInsetsCompat windowInsetsCompat) {
         mKeyboardInset = windowInsetsCompat.getInsets(WindowInsetsCompat.Type.ime()).bottom;
+        mNavBarInset =
+                windowInsetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
         boolean resizedRootView = maybeUpdateRootViewBottomPadding();
         if (!resizedRootView) return windowInsetsCompat;
 
         // Consume IME insets if the root view has been adjusted.
         return new WindowInsetsCompat.Builder(windowInsetsCompat)
                 .setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE)
+                .setInsets(WindowInsetsCompat.Type.navigationBars(), Insets.NONE)
                 .build();
     }
 }
diff --git a/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinatorUnitTest.java b/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinatorUnitTest.java
index 15a86dc..abe720c7 100644
--- a/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinatorUnitTest.java
+++ b/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinatorUnitTest.java
@@ -72,6 +72,8 @@
     private static final Rect WIDEST_UNOCCLUDED_RECT =
             new Rect(LEFT_BLOCK, 0, WINDOW_WIDTH - RIGHT_BLOCK, HEADER_HEIGHT);
     private static final int KEYBOARD_INSET = 736;
+    private static final int NAV_BAR_INSET = 128;
+    private static final int UNSPECIFIED_INSET = -1;
     private static final int APPEARANCE_LIGHT_CAPTION_BARS = 1 << 8;
 
     @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
@@ -345,7 +347,7 @@
     }
 
     @Test
-    public void noImeInsets() {
+    public void noImeOrNavBarInsets() {
         // Simulate switching to desktop windowing mode, without any bottom insets.
         setupWithLeftAndRightBoundingRect();
         notifyInsetsRectObserver();
@@ -358,7 +360,7 @@
                 /* error= */ "DesktopWindowing should exit when no insets is supplied.");
 
         // Simulate overlapping keyboard.
-        var insets = applyWindowInsets(KEYBOARD_INSET);
+        var insets = applyWindowInsets(KEYBOARD_INSET, UNSPECIFIED_INSET);
         assertNotEquals(
                 "Ime insets should not be consumed when root view is not adjusted.",
                 Insets.NONE,
@@ -368,7 +370,7 @@
         // Simulate switching to desktop windowing mode.
         setupWithLeftAndRightBoundingRect();
         notifyInsetsRectObserver();
-        insets = applyWindowInsets(KEYBOARD_INSET);
+        insets = applyWindowInsets(KEYBOARD_INSET, UNSPECIFIED_INSET);
         assertEquals(
                 "Ime insets should be consumed when root view is bottom-padded.",
                 Insets.NONE,
@@ -382,7 +384,7 @@
         // Simulate switching out of desktop windowing mode.
         setupWithNoCaptionInsets();
         notifyInsetsRectObserver();
-        insets = applyWindowInsets(KEYBOARD_INSET);
+        insets = applyWindowInsets(KEYBOARD_INSET, UNSPECIFIED_INSET);
         assertNotEquals(
                 "Ime insets should not be consumed when root view is not adjusted.",
                 Insets.NONE,
@@ -398,7 +400,7 @@
         notifyInsetsRectObserver();
 
         // Simulate overlapping keyboard.
-        var insets = applyWindowInsets(KEYBOARD_INSET);
+        var insets = applyWindowInsets(KEYBOARD_INSET, UNSPECIFIED_INSET);
         assertEquals(
                 "Ime insets should be consumed when root view is adjusted.",
                 Insets.NONE,
@@ -409,7 +411,7 @@
                 mSpyRootView.getPaddingBottom());
 
         // Simulate moving a desktop window that causes the keyboard inset to be updated.
-        insets = applyWindowInsets(KEYBOARD_INSET + 100);
+        insets = applyWindowInsets(KEYBOARD_INSET + 100, UNSPECIFIED_INSET);
         assertEquals(
                 "Ime insets should be consumed when root view is adjusted.",
                 Insets.NONE,
@@ -421,6 +423,84 @@
     }
 
     @Test
+    public void overlappingNavBar_SwitchToAndFromDesktopWindowingMode() {
+        verifyDesktopWindowingDisabled(
+                /* error= */ "Desktop windowing mode should be disabled initially.");
+
+        // Simulate overlapping nav bar bottom inset.
+        var insets = applyWindowInsets(UNSPECIFIED_INSET, NAV_BAR_INSET);
+        assertNotEquals(
+                "Nav bar insets should not be consumed when root view is not adjusted.",
+                Insets.NONE,
+                insets.getInsets(WindowInsetsCompat.Type.navigationBars()));
+        assertEquals("Root view bottom should not be padded.", 0, mSpyRootView.getPaddingBottom());
+
+        // Simulate switching to desktop windowing mode.
+        setupWithLeftAndRightBoundingRect();
+        notifyInsetsRectObserver();
+        insets = applyWindowInsets(UNSPECIFIED_INSET, NAV_BAR_INSET);
+        assertEquals(
+                "Nav bar insets should be consumed when root view is bottom-padded.",
+                Insets.NONE,
+                insets.getInsets(WindowInsetsCompat.Type.ime()));
+        verifyDesktopWindowingEnabled();
+        assertEquals(
+                "Root view bottom padding should be updated.",
+                NAV_BAR_INSET,
+                mSpyRootView.getPaddingBottom());
+
+        // Simulate switching out of desktop windowing mode.
+        setupWithNoCaptionInsets();
+        notifyInsetsRectObserver();
+        insets = applyWindowInsets(UNSPECIFIED_INSET, NAV_BAR_INSET);
+        assertNotEquals(
+                "Nav bar insets should not be consumed when root view is not adjusted.",
+                Insets.NONE,
+                insets.getInsets(WindowInsetsCompat.Type.navigationBars()));
+        assertEquals(
+                "Root view bottom padding should be reset.", 0, mSpyRootView.getPaddingBottom());
+    }
+
+    @Test
+    public void overlappingNavBar_MoveDesktopWindow() {
+        // Simulate switching to desktop windowing mode.
+        setupWithLeftAndRightBoundingRect();
+        notifyInsetsRectObserver();
+
+        // Simulate overlapping nav bar bottom inset.
+        var insets = applyWindowInsets(UNSPECIFIED_INSET, NAV_BAR_INSET);
+        assertEquals(
+                "Nav bar insets should be consumed when root view is adjusted.",
+                Insets.NONE,
+                insets.getInsets(WindowInsetsCompat.Type.navigationBars()));
+
+        // Simulate moving a desktop window that causes the nav bar inset to be updated.
+        insets = applyWindowInsets(UNSPECIFIED_INSET, NAV_BAR_INSET - 10);
+        assertEquals(
+                "Nav bar insets should be consumed when root view is adjusted.",
+                Insets.NONE,
+                insets.getInsets(WindowInsetsCompat.Type.navigationBars()));
+        assertEquals(
+                "Root view bottom padding should be updated.",
+                NAV_BAR_INSET - 10,
+                mSpyRootView.getPaddingBottom());
+    }
+
+    @Test
+    public void overlappingKeyboardAndNavBar() {
+        // Simulate switching to desktop windowing mode.
+        setupWithLeftAndRightBoundingRect();
+        notifyInsetsRectObserver();
+
+        // Simulate overlapping keyboard and nav bar bottom insets.
+        var insets = applyWindowInsets(KEYBOARD_INSET, NAV_BAR_INSET);
+        assertEquals(
+                "Root view bottom padding should be updated.",
+                KEYBOARD_INSET,
+                mSpyRootView.getPaddingBottom());
+    }
+
+    @Test
     public void windowingModeHistogram_EnterFullScreen() {
         // Simulate starting in desktop windowing mode for an initial state.
         setupWithLeftAndRightBoundingRect();
@@ -594,10 +674,16 @@
         assertFalse("Edge to edge should not be active.", mEdgeToEdgeStateProvider.get());
     }
 
-    private WindowInsetsCompat applyWindowInsets(int keyboardInset) {
+    private WindowInsetsCompat applyWindowInsets(int keyboardInset, int navBarInset) {
         var windowInsetsBuilder = new WindowInsetsCompat.Builder();
-        windowInsetsBuilder.setInsets(
-                WindowInsetsCompat.Type.ime(), Insets.of(0, 0, 0, keyboardInset));
+        if (keyboardInset != UNSPECIFIED_INSET) {
+            windowInsetsBuilder.setInsets(
+                    WindowInsetsCompat.Type.ime(), Insets.of(0, 0, 0, keyboardInset));
+        }
+        if (navBarInset != UNSPECIFIED_INSET) {
+            windowInsetsBuilder.setInsets(
+                    WindowInsetsCompat.Type.navigationBars(), Insets.of(0, 0, 0, navBarInset));
+        }
         return mAppHeaderCoordinator.onApplyWindowInsets(mSpyRootView, windowInsetsBuilder.build());
     }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/DeferredIMEWindowInsetApplicationCallback.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/DeferredIMEWindowInsetApplicationCallback.java
index b67779cec..9ed4f4fe 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/DeferredIMEWindowInsetApplicationCallback.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/DeferredIMEWindowInsetApplicationCallback.java
@@ -135,10 +135,18 @@
         }
 
         // Zero out (consume) the ime insets; we're applying them ourselves so no one else needs
-        // to consume them.
-        return new WindowInsetsCompat.Builder(windowInsetsCompat)
-                .setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE)
-                .build();
+        // to consume them. Additionally, we will also consume nav bar insets because we have at
+        // least one other inset consumer that might otherwise use the nav bar inset incorrectly
+        // when the ime is visible and only the ime insets are consumed here. This is based on the
+        // assumption that both the ime and nav bar are present at the bottom of the app window.
+        // TODO (crbug.com/388037271): Remove nav bar inset consumption.
+        var builder =
+                new WindowInsetsCompat.Builder(windowInsetsCompat)
+                        .setInsets(WindowInsetsCompat.Type.ime(), Insets.NONE);
+        if (imeInsets.bottom > 0) {
+            builder.setInsets(WindowInsetsCompat.Type.navigationBars(), Insets.NONE);
+        }
+        return builder.build();
     }
 
     private void commitKeyboardHeight(int newKeyboardHeight) {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/DeferredIMEWindowInsetApplicationCallbackTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/DeferredIMEWindowInsetApplicationCallbackTest.java
index 23255cc..2dc422c8 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/DeferredIMEWindowInsetApplicationCallbackTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/DeferredIMEWindowInsetApplicationCallbackTest.java
@@ -79,6 +79,8 @@
         WindowInsetsCompat modifiedInsets = mCallback.onApplyWindowInsets(mView, windowInsets);
 
         assertEquals(Insets.NONE, modifiedInsets.getInsets(WindowInsetsCompat.Type.ime()));
+        assertEquals(
+                Insets.NONE, modifiedInsets.getInsets(WindowInsetsCompat.Type.navigationBars()));
         verify(mUpdateRunnable, never()).run();
 
         mCallback.onEnd(mAnimation);
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index de8c25c..e4f83df5 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -6183,6 +6183,9 @@
       <message name="IDS_CUSTOM_TABS_SIGNED_OUT_MESSAGE_TITLE" desc="This string is the title of a message promotion about signing in to Chrome with the user's Google Account.">
         Sign in to Chrome
       </message>
+      <message name="IDS_CUSTOM_TAB_CANT_PERFORM_ACTION_TOAST" desc="Toast displayed when the URL currently being displayed in the Custom Tab cannot be opened in the regular browser.">
+        Something went wrong. Try again.
+      </message>
 
       <message name="IDS_ACCOUNT_SELECTION_CONTINUE" desc="Title of the button that continues filling with the only available set of credentials.">
         Continue as <ph name="NAME">%1$s<ex>Albus (or Albus Dumbledore)</ex></ph>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CUSTOM_TAB_CANT_PERFORM_ACTION_TOAST.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CUSTOM_TAB_CANT_PERFORM_ACTION_TOAST.png.sha1
new file mode 100644
index 0000000..f5750a2
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CUSTOM_TAB_CANT_PERFORM_ACTION_TOAST.png.sha1
@@ -0,0 +1 @@
+bf54d3b35d3f41e90359d1ea1433c72eeaea6126
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/editor_menu/editor_menu_controller_impl.cc b/chrome/browser/ui/ash/editor_menu/editor_menu_controller_impl.cc
index 0bd370a..fe69e5f8 100644
--- a/chrome/browser/ui/ash/editor_menu/editor_menu_controller_impl.cc
+++ b/chrome/browser/ui/ash/editor_menu/editor_menu_controller_impl.cc
@@ -35,6 +35,7 @@
 #include "chromeos/components/magic_boost/public/cpp/magic_boost_state.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "content/public/browser/browser_context.h"
+#include "ui/base/ime/ash/ime_bridge.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/view_utils.h"
 #include "ui/views/widget/widget.h"
@@ -47,6 +48,15 @@
 
 namespace {
 
+gfx::Rect CalculateCaretBounds() {
+  const ui::InputMethod* input_method =
+      ash::IMEBridge::Get()->GetInputContextHandler()->GetInputMethod();
+  if (input_method && input_method->GetTextInputClient()) {
+    return input_method->GetTextInputClient()->GetCaretBounds();
+  }
+  return gfx::Rect();
+}
+
 std::unique_ptr<LobsterManager> CreateLobsterManager() {
   ash::LobsterController* lobster_controller =
       ash::Shell::Get()->lobster_controller();
@@ -57,7 +67,7 @@
 
   std::unique_ptr<ash::LobsterController::Trigger> lobster_trigger =
       lobster_controller->CreateTrigger(ash::LobsterEntryPoint::kRightClickMenu,
-                                        true);
+                                        true, CalculateCaretBounds());
 
   if (!lobster_trigger) {
     return nullptr;
diff --git a/chrome/browser/ui/ash/quick_insert/quick_insert_client_impl.cc b/chrome/browser/ui/ash/quick_insert/quick_insert_client_impl.cc
index cb761dc..122120b 100644
--- a/chrome/browser/ui/ash/quick_insert/quick_insert_client_impl.cc
+++ b/chrome/browser/ui/ash/quick_insert/quick_insert_client_impl.cc
@@ -390,7 +390,8 @@
 }
 
 QuickInsertClientImpl::ShowLobsterCallback
-QuickInsertClientImpl::CacheLobsterContext(bool support_image_insertion) {
+QuickInsertClientImpl::CacheLobsterContext(bool support_image_insertion,
+                                           const gfx::Rect& caret_bounds) {
   if (!ash::features::IsLobsterEnabled()) {
     return base::NullCallback();
   }
@@ -404,8 +405,9 @@
     return base::NullCallback();
   }
 
-  lobster_trigger_ = lobster_controller->CreateTrigger(
-      ash::LobsterEntryPoint::kQuickInsert, support_image_insertion);
+  lobster_trigger_ =
+      lobster_controller->CreateTrigger(ash::LobsterEntryPoint::kQuickInsert,
+                                        support_image_insertion, caret_bounds);
 
   if (!lobster_trigger_) {
     return base::NullCallback();
diff --git a/chrome/browser/ui/ash/quick_insert/quick_insert_client_impl.h b/chrome/browser/ui/ash/quick_insert/quick_insert_client_impl.h
index c9bdb9c..e22dd63c 100644
--- a/chrome/browser/ui/ash/quick_insert/quick_insert_client_impl.h
+++ b/chrome/browser/ui/ash/quick_insert/quick_insert_client_impl.h
@@ -66,7 +66,8 @@
   bool IsEligibleForEditor() override;
   ShowEditorCallback CacheEditorContext() override;
   ShowLobsterCallback CacheLobsterContext(
-      bool support_image_insertion) override;
+      bool support_image_insertion,
+      const gfx::Rect& caret_bounds) override;
   void GetSuggestedEditorResults(
       SuggestedEditorResultsCallback callback) override;
   void GetRecentLocalFileResults(size_t max_files,
diff --git a/chrome/browser/ui/lens/lens_overlay_entry_point_controller.cc b/chrome/browser/ui/lens/lens_overlay_entry_point_controller.cc
index 62d7c6f..d078cb49 100644
--- a/chrome/browser/ui/lens/lens_overlay_entry_point_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_entry_point_controller.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h"
 #include "chrome/browser/ui/webui/new_tab_page_third_party/new_tab_page_third_party_ui.h"
 #include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
+#include "chrome/grit/branded_strings.h"
 #include "components/lens/lens_features.h"
 #include "components/lens/lens_overlay_permission_utils.h"
 #include "components/omnibox/browser/omnibox_prefs.h"
@@ -251,7 +252,8 @@
   }
 
   if (!features::IsOmniboxEntrypointAlwaysVisible() &&
-      !location_bar_->HasFocus()) {
+      !location_bar_->Contains(
+          location_bar_->GetFocusManager()->GetFocusedView())) {
     page_action_controller->Hide(page_action_id);
     return;
   }
@@ -264,6 +266,11 @@
     return;
   }
 
+  // No-ops if the overriding string is the same.
+  page_action_controller->OverrideText(
+      page_action_id,
+      l10n_util::GetStringUTF16(IDS_CONTENT_LENS_OVERLAY_ENTRYPOINT_LABEL));
+
   // TODO(crbug.com/376283383): We should always use the chip state once that's
   // implemented.
   page_action_controller->Show(page_action_id);
diff --git a/chrome/browser/ui/tabs/tab_strip_model_observer.cc b/chrome/browser/ui/tabs/tab_strip_model_observer.cc
index 706e087f..4fdc3f2 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_observer.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_observer.cc
@@ -48,45 +48,40 @@
 TabStripModelChange::TabStripModelChange() = default;
 
 TabStripModelChange::TabStripModelChange(Insert delta)
-    : TabStripModelChange(Type::kInserted,
-                          std::make_unique<Insert>(std::move(delta))) {}
+    : TabStripModelChange(Type::kInserted, std::move(delta)) {}
 
 TabStripModelChange::TabStripModelChange(Remove delta)
-    : TabStripModelChange(Type::kRemoved,
-                          std::make_unique<Remove>(std::move(delta))) {}
+    : TabStripModelChange(Type::kRemoved, std::move(delta)) {}
 
 TabStripModelChange::TabStripModelChange(Move delta)
-    : TabStripModelChange(Type::kMoved,
-                          std::make_unique<Move>(std::move(delta))) {}
+    : TabStripModelChange(Type::kMoved, std::move(delta)) {}
 
 TabStripModelChange::TabStripModelChange(Replace delta)
-    : TabStripModelChange(Type::kReplaced,
-                          std::make_unique<Replace>(std::move(delta))) {}
+    : TabStripModelChange(Type::kReplaced, std::move(delta)) {}
 
 TabStripModelChange::~TabStripModelChange() = default;
 
 const TabStripModelChange::Insert* TabStripModelChange::GetInsert() const {
-  DCHECK_EQ(type_, Type::kInserted);
-  return static_cast<const Insert*>(delta_.get());
+  CHECK_EQ(type_, Type::kInserted);
+  return &std::get<Insert>(delta_);
 }
 
 const TabStripModelChange::Remove* TabStripModelChange::GetRemove() const {
-  DCHECK_EQ(type_, Type::kRemoved);
-  return static_cast<const Remove*>(delta_.get());
+  CHECK_EQ(type_, Type::kRemoved);
+  return &std::get<Remove>(delta_);
 }
 
 const TabStripModelChange::Move* TabStripModelChange::GetMove() const {
-  DCHECK_EQ(type_, Type::kMoved);
-  return static_cast<const Move*>(delta_.get());
+  CHECK_EQ(type_, Type::kMoved);
+  return &std::get<Move>(delta_);
 }
 
 const TabStripModelChange::Replace* TabStripModelChange::GetReplace() const {
-  DCHECK_EQ(type_, Type::kReplaced);
-  return static_cast<const Replace*>(delta_.get());
+  CHECK_EQ(type_, Type::kReplaced);
+  return &std::get<Replace>(delta_);
 }
 
-TabStripModelChange::TabStripModelChange(Type type,
-                                         std::unique_ptr<Delta> delta)
+TabStripModelChange::TabStripModelChange(Type type, Delta delta)
     : type_(type), delta_(std::move(delta)) {}
 
 void TabStripModelChange::RemovedTab::WriteIntoTrace(
@@ -130,7 +125,7 @@
 void TabStripModelChange::WriteIntoTrace(perfetto::TracedValue context) const {
   auto dict = std::move(context).WriteDictionary();
   dict.Add("type", type_);
-  dict.Add("delta", delta_);
+  std::visit([&dict](auto&& delta) { dict.Add("delta", delta); }, delta_);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/tabs/tab_strip_model_observer.h b/chrome/browser/ui/tabs/tab_strip_model_observer.h
index c0f7bd0..e3243f2 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_observer.h
+++ b/chrome/browser/ui/tabs/tab_strip_model_observer.h
@@ -58,15 +58,6 @@
     kInsertedIntoOtherTabStrip
   };
 
-  // Base class for all changes.
-  // TODO(dfried): would love to change this whole thing into a std::variant,
-  // but C++17 features are not yet approved for use in chromium.
-  struct Delta {
-    virtual ~Delta() = default;
-
-    virtual void WriteIntoTrace(perfetto::TracedValue context) const = 0;
-  };
-
   struct RemovedTab {
     RemovedTab(tabs::TabInterface* tab,
                int index,
@@ -96,9 +87,9 @@
 
   // WebContents were inserted. This implicitly changes the existing selection
   // model by calling IncrementFrom(index) on each index in |contents[i].index|.
-  struct Insert : public Delta {
+  struct Insert {
     Insert();
-    ~Insert() override;
+    ~Insert();
     Insert(Insert&& other);
     Insert& operator=(Insert&& other);
 
@@ -126,14 +117,14 @@
     // after processing all of `contents`.
     std::vector<ContentsWithIndex> contents;
 
-    void WriteIntoTrace(perfetto::TracedValue context) const override;
+    void WriteIntoTrace(perfetto::TracedValue context) const;
   };
 
   // WebContents were removed at |indices_before_removal|. This implicitly
   // changes the existing selection model by calling DecrementFrom(index).
-  struct Remove : public Delta {
+  struct Remove {
     Remove();
-    ~Remove() override;
+    ~Remove();
     Remove(Remove&& other);
     Remove& operator=(Remove&& other);
 
@@ -162,30 +153,30 @@
     // after processing all of `contents`.
     std::vector<RemovedTab> contents;
 
-    void WriteIntoTrace(perfetto::TracedValue context) const override;
+    void WriteIntoTrace(perfetto::TracedValue context) const;
   };
 
   // A tab was moved from `from_index` to `to_index`. This implicitly changes
   // the existing selection model by calling Move(from_index, to_index, 1).
-  struct Move : public Delta {
+  struct Move {
     raw_ptr<tabs::TabInterface> tab = nullptr;
     raw_ptr<content::WebContents> contents = nullptr;
     int from_index;
     int to_index;
 
-    void WriteIntoTrace(perfetto::TracedValue context) const override;
+    void WriteIntoTrace(perfetto::TracedValue context) const;
   };
 
   // The tab was replaced at the specified index. This is invoked when
   // prerendering swaps in a prerendered WebContents or when a tab's WebContents
   // is discarded to save memory.
-  struct Replace : public Delta {
+  struct Replace {
     raw_ptr<tabs::TabInterface> tab = nullptr;
     raw_ptr<content::WebContents> old_contents = nullptr;
     raw_ptr<content::WebContents> new_contents = nullptr;
     int index;
 
-    void WriteIntoTrace(perfetto::TracedValue context) const override;
+    void WriteIntoTrace(perfetto::TracedValue context) const;
   };
 
   TabStripModelChange();
@@ -206,10 +197,13 @@
   void WriteIntoTrace(perfetto::TracedValue context) const;
 
  private:
-  TabStripModelChange(Type type, std::unique_ptr<Delta> delta);
+  using Delta = std::variant<Insert, Remove, Move, Replace>;
+
+  TabStripModelChange(Type type, Delta delta);
 
   const Type type_ = kSelectionOnly;
-  std::unique_ptr<Delta> delta_;
+
+  Delta delta_;
 };
 
 // Struct to carry changes on selection/activation.
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_interactive_uitest.cc b/chrome/browser/ui/views/download/bubble/download_bubble_interactive_uitest.cc
index 9a1ac05..5f641cb 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_interactive_uitest.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_interactive_uitest.cc
@@ -46,7 +46,6 @@
 
 namespace {
 
-#if !BUILDFLAG(IS_MAC)
 // This waits for the download bubble widget to be shown.
 views::NamedWidgetShownWaiter CreateDownloadBubbleDialogWaiter() {
   return views::NamedWidgetShownWaiter{views::test::AnyWidgetTestPasskey{},
@@ -64,7 +63,6 @@
   bool is_hiding = bubble->animation_for_test()->IsClosing();
   return bubble->IsShowing() || (bubble->IsVisible() && !is_hiding);
 }
-#endif
 
 // TODO(chlily): Deduplicate this helper class into a test utils file.
 class TestDownloadManagerDelegate : public ChromeDownloadManagerDelegate {
@@ -103,6 +101,7 @@
       : InteractiveFeaturePromoTestT(UseDefaultTrackerAllowingPromos(
             {feature_engagement::kIPHDownloadEsbPromoFeature})) {
 #if BUILDFLAG(IS_MAC)
+    // TODO(chlily): Add test coverage for immersive fullscreen disabled on Mac.
     test_features_.InitWithFeatures({features::kImmersiveFullscreen}, {});
 #endif  // BUILDFLAG(IS_MAC)
   }
@@ -219,7 +218,6 @@
     });
   }
 
-#if !BUILDFLAG(IS_MAC)
   // Check for whether the exclusive access bubble is shown ("Press Esc to
   // exit fullscreen" or other similar message).
   auto IsExclusiveAccessBubbleDisplayed(bool displayed) {
@@ -243,7 +241,6 @@
                      : false);
     });
   }
-#endif
 
 #if BUILDFLAG(IS_MAC)
   auto EnterImmersiveFullscreen() {
@@ -417,27 +414,37 @@
 }
 #endif  // BUILDFLAG(IS_MAC)
 
-// This test is only for platforms where fullscreen is not immersive.
-// TODO(chlily): Add test coverage for Mac.
-#if !BUILDFLAG(IS_MAC)
 // Test that downloading a file in tab fullscreen (not browser fullscreen)
 // results in an exclusive access bubble, and the partial view, if enabled, is
 // displayed after the tab exits fullscreen.
 IN_PROC_BROWSER_TEST_F(
     DownloadBubbleInteractiveUiTest,
     ExclusiveAccessBubbleShownForTabFullscreenDownloadThenPartialView) {
+  using ui_test_utils::FullscreenWaiter;
+
   DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId);
 
   // Grab the fullscreen accelerator, which is used to exit fullscreen in the
   // test. For some reason, exiting tab fullscreen via JavaScript doesn't work
   // (times out).
   ui::Accelerator fullscreen_accelerator;
+#if BUILDFLAG(IS_MAC)
+  // SendAccelerator or ui_controls::SendKeyPress doesn't support fn key on
+  // Mac, that the default fullscreen hotkey wouldn't work.
+  // TODO: When SendAccelerator fixed on mac, remove this hard coded key.
+  fullscreen_accelerator =
+      ui::Accelerator(ui::VKEY_F, ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN);
+#else
   chrome::AcceleratorProviderForBrowser(browser())->GetAcceleratorForCommandId(
       IDC_FULLSCREEN, &fullscreen_accelerator);
+#endif
 
   views::NamedWidgetShownWaiter dialog_waiter =
       CreateDownloadBubbleDialogWaiter();
 
+  auto tab_fullscreen_waiter = std::make_unique<FullscreenWaiter>(
+      browser(), FullscreenWaiter::Expectation{.tab_fullscreen = true});
+
   RunTestSequenceInContext(
       browser()->window()->GetElementContext(),
       InstrumentTab(kWebContentsElementId),
@@ -447,7 +454,14 @@
       InParallel(
           ExecuteJs(kWebContentsElementId,
                     "() => document.documentElement.requestFullscreen()"),
-          InAnyContext(WaitForShow(kExclusiveAccessBubbleViewElementId))),
+          InAnyContext(WaitForShow(kExclusiveAccessBubbleViewElementId)),
+          Do([&]() {
+            tab_fullscreen_waiter->Wait();
+            // Reset the fullscreen waiter to wait for exiting fullscreen next
+            // time.
+            tab_fullscreen_waiter = std::make_unique<FullscreenWaiter>(
+                browser(), FullscreenWaiter::kNoFullscreen);
+          })),
       // The exclusive access bubble should notify about the fullscreen change.
       Check(IsExclusiveAccessBubbleDisplayed(true),
             "Exclusive access bubble is displayed upon entering fullscreen"),
@@ -464,8 +478,8 @@
             "Exclusive access bubble is for a download"),
 
       // Now exit fullscreen, and the partial view, if enabled, should be shown.
-      SendAccelerator(kBrowserViewElementId, fullscreen_accelerator),
-
+      InParallel(SendAccelerator(kBrowserViewElementId, fullscreen_accelerator),
+                 Do([&]() { tab_fullscreen_waiter->Wait(); })),
       If([&]() { return IsPartialViewEnabled(); },
          Steps(Do(WaitForDownloadBubbleShow(dialog_waiter)),
                Check(DownloadBubbleIsShowingDetails(true),
@@ -476,7 +490,6 @@
       Do(ChangeBubbleVisibility(false)), Do(ChangeButtonVisibility(false)),
       WaitForHide(kToolbarDownloadButtonElementId));
 }
-#endif
 
 // Tests that the partial view does not steal focus from the web contents, and
 // that the partial view is still closable when clicking outside of it, and that
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
index 2df9dfff..0370ec1 100644
--- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
@@ -413,6 +413,13 @@
   if (!browser_view) {
     return false;
   }
+#if BUILDFLAG(IS_MAC)
+  // In content fullscreen, we do not show the download bubble and the toolbar
+  // is not visible. Therefore, we must show the ExclusiveAccessBubble notice.
+  if (fullscreen_utils::IsInContentFullscreen(browser_)) {
+    return true;
+  }
+#endif
 #if BUILDFLAG(IS_CHROMEOS)
   if (chromeos::IsKioskSession()) {
     return false;
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_ui_controller.cc b/chrome/browser/ui/views/download/bubble/download_toolbar_ui_controller.cc
index 0c5cfbb..1196c86 100644
--- a/chrome/browser/ui/views/download/bubble/download_toolbar_ui_controller.cc
+++ b/chrome/browser/ui/views/download/bubble/download_toolbar_ui_controller.cc
@@ -231,6 +231,13 @@
   if (!browser_view_) {
     return false;
   }
+#if BUILDFLAG(IS_MAC)
+  // In content fullscreen, we do not show the download bubble and the toolbar
+  // is not visible. Therefore, we must show the ExclusiveAccessBubble notice.
+  if (fullscreen_utils::IsInContentFullscreen(browser_view_->browser())) {
+    return true;
+  }
+#endif
 #if BUILDFLAG(IS_CHROMEOS)
   if (chromeos::IsKioskSession()) {
     return false;
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 7dfc7e36..3975be5 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -321,6 +321,7 @@
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #include "chrome/browser/ui/views/promos/ios_promo_password_bubble.h"
+#include "components/segmentation_platform/public/result.h"
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
 #if defined(USE_AURA)
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index a72ecf8..d98bcf5 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -43,7 +43,6 @@
 #include "chrome/common/buildflags.h"
 #include "components/enterprise/buildflags/buildflags.h"
 #include "components/infobars/core/infobar_container.h"
-#include "components/segmentation_platform/public/result.h"
 #include "components/user_education/common/feature_promo/feature_promo_controller.h"
 #include "components/user_education/common/feature_promo/feature_promo_handle.h"
 #include "components/webapps/browser/banners/app_banner_manager.h"
@@ -123,6 +122,10 @@
 class BorderView;
 }  // namespace glic
 
+namespace segmentation_platform {
+struct ClassificationResult;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // BrowserView
 //
diff --git a/chrome/browser/ui/views/mahi/mahi_menu_view.cc b/chrome/browser/ui/views/mahi/mahi_menu_view.cc
index 318ca5d..3771d26 100644
--- a/chrome/browser/ui/views/mahi/mahi_menu_view.cc
+++ b/chrome/browser/ui/views/mahi/mahi_menu_view.cc
@@ -94,9 +94,15 @@
   button->SetImageModel(views::Button::ButtonState::STATE_NORMAL,
                         ui::ImageModel::FromVectorIcon(
                             icon, ui::kColorSysOnSurface, kButtonHeight));
+  button->SetImageModel(views::Button::ButtonState::STATE_DISABLED,
+                        ui::ImageModel::FromVectorIcon(
+                            icon, ui::kColorSysStateDisabled, kButtonHeight));
   button->SetTextColorId(views::LabelButton::ButtonState::STATE_NORMAL,
                          ui::kColorSysOnSurface);
+  button->SetTextColorId(views::LabelButton::ButtonState::STATE_DISABLED,
+                         ui::kColorSysStateDisabled);
   button->SetImageLabelSpacing(kButtonImageLabelSpacing);
+
   auto color_id = button->GetEnabled() ? ui::kColorSysTonalOutline
                                        : ui::kColorButtonBorderDisabled;
   button->SetBorder(views::CreatePaddedBorder(
diff --git a/chrome/browser/ui/views/page_action/page_action_controller.cc b/chrome/browser/ui/views/page_action/page_action_controller.cc
index 55295d4e..89a42bf 100644
--- a/chrome/browser/ui/views/page_action/page_action_controller.cc
+++ b/chrome/browser/ui/views/page_action/page_action_controller.cc
@@ -63,6 +63,17 @@
   model.SetImage(action_item->GetImage());
 }
 
+void PageActionController::OverrideText(actions::ActionId action_id,
+                                        const std::u16string& override_text) {
+  FindPageActionModel(action_id).SetOverrideText(
+      base::PassKey<PageActionController>(), override_text);
+}
+
+void PageActionController::ClearOverrideText(actions::ActionId action_id) {
+  FindPageActionModel(action_id).SetOverrideText(
+      base::PassKey<PageActionController>(), /*override_text=*/std::nullopt);
+}
+
 void PageActionController::AddObserver(
     actions::ActionId action_id,
     base::ScopedObservation<PageActionModel, PageActionModelObserver>&
diff --git a/chrome/browser/ui/views/page_action/page_action_controller.h b/chrome/browser/ui/views/page_action/page_action_controller.h
index c8776f9..afd202829 100644
--- a/chrome/browser/ui/views/page_action/page_action_controller.h
+++ b/chrome/browser/ui/views/page_action/page_action_controller.h
@@ -8,6 +8,7 @@
 #include <map>
 #include <memory>
 #include <set>
+#include <string>
 
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
@@ -48,6 +49,14 @@
   // Returns true if the page action will be shown and false otherwise.
   bool ShowIfNotPinned(actions::ActionId action_id);
 
+  // By default, in suggestion chip mode, the ActionItem text will be used as
+  // the control label. However, features can provide a custom text to use
+  // as the label. In that case, the custom text will take precedence over
+  // the ActionItem text.
+  void OverrideText(actions::ActionId action_id,
+                    const std::u16string& override_text);
+  void ClearOverrideText(actions::ActionId action_id);
+
   // Manages observers for the page action's underlying `PageActionModel`.
   void AddObserver(
       actions::ActionId action_id,
diff --git a/chrome/browser/ui/views/page_action/page_action_controller_unittest.cc b/chrome/browser/ui/views/page_action/page_action_controller_unittest.cc
index 51581ca2..609e2fb 100644
--- a/chrome/browser/ui/views/page_action/page_action_controller_unittest.cc
+++ b/chrome/browser/ui/views/page_action/page_action_controller_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/page_action/page_action_controller.h"
 
 #include <memory>
+#include <string>
 
 #include "base/scoped_observation.h"
 #include "chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.h"
@@ -18,6 +19,15 @@
 namespace page_actions {
 namespace {
 
+const std::u16string kText = u"Text";
+const std::u16string kOverrideText = u"Override Text";
+const std::u16string kOverrideOne = u"Override One";
+const std::u16string kOverrideTwo = u"Override Two";
+const std::u16string kOverrideThree = u"Override Three";
+const std::u16string kAnotherNewText = u"Another New Text";
+
+const std::u16string kTooltip = u"Tooltip";
+
 using ::actions::ActionItem;
 
 using TestPageActionModelObservation =
@@ -177,11 +187,11 @@
       controller()->CreateActionItemSubscription(action_item.get());
   controller()->AddObserver(0, observation);
 
-  action_item->SetText(u"Text");
-  action_item->SetTooltipText(u"Tooltip");
+  action_item->SetText(kText);
+  action_item->SetTooltipText(kTooltip);
 
-  EXPECT_EQ(observer.text(), u"Text");
-  EXPECT_EQ(observer.tooltip_text(), u"Tooltip");
+  EXPECT_EQ(kText, observer.text());
+  EXPECT_EQ(kTooltip, observer.tooltip_text());
 }
 
 TEST_F(PageActionControllerTest, ShowIfNotPinned) {
@@ -211,5 +221,80 @@
   actions::ActionManager::Get().ResetForTesting();
 }
 
+TEST_F(PageActionControllerTest, OverrideText) {
+  auto observer = PageActionTestObserver();
+  TestPageActionModelObservation observation(&observer);
+  controller()->Register(0);
+  auto action_item = BuildActionItem(0);
+  base::CallbackListSubscription subscription =
+      controller()->CreateActionItemSubscription(action_item.get());
+  controller()->AddObserver(0, observation);
+
+  action_item->SetText(kText);
+
+  controller()->OverrideText(0, kOverrideText);
+  EXPECT_EQ(kOverrideText, observer.text());
+}
+
+TEST_F(PageActionControllerTest, UpdateActionItemTextWithOverrideText) {
+  auto observer = PageActionTestObserver();
+  TestPageActionModelObservation observation(&observer);
+  controller()->Register(0);
+  auto action_item = BuildActionItem(0);
+  base::CallbackListSubscription subscription =
+      controller()->CreateActionItemSubscription(action_item.get());
+  controller()->AddObserver(0, observation);
+
+  action_item->SetText(kText);
+
+  controller()->OverrideText(0, kOverrideText);
+  EXPECT_EQ(kOverrideText, observer.text());
+
+  action_item->SetText(kAnotherNewText);
+  // The override text should still take precedence.
+  EXPECT_EQ(kOverrideText, observer.text());
+}
+
+TEST_F(PageActionControllerTest, ClearOverrideText) {
+  auto observer = PageActionTestObserver();
+  TestPageActionModelObservation observation(&observer);
+  controller()->Register(0);
+  auto action_item = BuildActionItem(0);
+  base::CallbackListSubscription subscription =
+      controller()->CreateActionItemSubscription(action_item.get());
+  controller()->AddObserver(0, observation);
+
+  action_item->SetText(kText);
+  controller()->OverrideText(0, kOverrideText);
+  controller()->ClearOverrideText(0);
+
+  // We should revert to the ActionItem text.
+  EXPECT_EQ(kText, observer.text());
+}
+
+TEST_F(PageActionControllerTest, MultipleTextOverrides) {
+  auto observer = PageActionTestObserver();
+  TestPageActionModelObservation observation(&observer);
+  controller()->Register(0);
+  auto action_item = BuildActionItem(0);
+  auto subscription =
+      controller()->CreateActionItemSubscription(action_item.get());
+  controller()->AddObserver(0, observation);
+
+  action_item->SetText(kText);
+
+  controller()->OverrideText(0, kOverrideOne);
+  EXPECT_EQ(kOverrideOne, observer.text());
+
+  controller()->OverrideText(0, kOverrideTwo);
+  EXPECT_EQ(kOverrideTwo, observer.text());
+
+  controller()->OverrideText(0, kOverrideThree);
+  EXPECT_EQ(kOverrideThree, observer.text());
+
+  controller()->ClearOverrideText(0);
+  EXPECT_EQ(kText, observer.text());
+}
+
 }  // namespace
 }  // namespace page_actions
diff --git a/chrome/browser/ui/views/page_action/page_action_model.cc b/chrome/browser/ui/views/page_action/page_action_model.cc
index 9b6ff32e..712d170 100644
--- a/chrome/browser/ui/views/page_action/page_action_model.cc
+++ b/chrome/browser/ui/views/page_action/page_action_model.cc
@@ -65,7 +65,7 @@
   NotifyChange();
 }
 const std::u16string PageActionModel::GetText() const {
-  return text_;
+  return override_text_.value_or(text_);
 }
 
 void PageActionModel::SetTooltipText(const std::u16string& tooltip) {
@@ -79,6 +79,16 @@
   return tooltip_;
 }
 
+void PageActionModel::SetOverrideText(
+    base::PassKey<PageActionController>,
+    const std::optional<std::u16string>& override_text) {
+  if (override_text_ == override_text) {
+    return;
+  }
+  override_text_ = override_text;
+  NotifyChange();
+}
+
 void PageActionModel::AddObserver(PageActionModelObserver* observer) {
   observer_list_.AddObserver(observer);
 }
diff --git a/chrome/browser/ui/views/page_action/page_action_model.h b/chrome/browser/ui/views/page_action/page_action_model.h
index b7caed5..3564ffe 100644
--- a/chrome/browser/ui/views/page_action/page_action_model.h
+++ b/chrome/browser/ui/views/page_action/page_action_model.h
@@ -30,6 +30,8 @@
   void SetShowRequested(base::PassKey<PageActionController>, bool requested);
   void SetActionItemEnabled(base::PassKey<PageActionController>, bool enabled);
   void SetActionItemVisible(base::PassKey<PageActionController>, bool visible);
+  void SetOverrideText(base::PassKey<PageActionController>,
+                       const std::optional<std::u16string>& override_text);
   // The model distills all visibility properties into a single result.
   bool GetVisible() const;
 
@@ -53,6 +55,8 @@
   bool action_item_enabled_ = false;
   bool action_item_visible_ = false;
   std::u16string text_;
+  // When set, it will always take precedence over `text_`.
+  std::optional<std::u16string> override_text_;
   std::u16string tooltip_;
   ui::ImageModel action_item_image_;
 
diff --git a/chrome/browser/ui/views/page_action/page_action_view_unittest.cc b/chrome/browser/ui/views/page_action/page_action_view_unittest.cc
index 6d79f37a..1c7ede5 100644
--- a/chrome/browser/ui/views/page_action/page_action_view_unittest.cc
+++ b/chrome/browser/ui/views/page_action/page_action_view_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/page_action/page_action_view.h"
 
 #include <memory>
+#include <string>
 
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.h"
@@ -245,5 +246,33 @@
   EXPECT_NE(updated_insets_true, updated_insets_false);
 }
 
+// Test that once a PageActionController is active, overriding the text updates
+// the view, and that the override persists until the controller is removed.
+TEST_F(PageActionViewTest, OverrideText) {
+  const std::u16string kInitialText = u"Initial Text";
+  actions::ActionItem* item = action_item();
+  item->SetEnabled(true);
+  item->SetVisible(true);
+  item->SetText(kInitialText);
+
+  PageActionView* view = page_action_view();
+  EXPECT_FALSE(view->GetVisible());
+  EXPECT_EQ(u"", view->GetText());
+
+  auto controller = NewPageActionController();
+  view->OnNewActiveController(controller.get());
+  EXPECT_EQ(kInitialText, view->GetText());
+
+  const std::u16string kOverrideText = u"Override Text";
+  controller->Show(0);
+  controller->OverrideText(0, kOverrideText);
+  EXPECT_TRUE(view->GetVisible());
+  EXPECT_EQ(kOverrideText, view->GetText());
+
+  view->OnNewActiveController(nullptr);
+  EXPECT_FALSE(view->GetVisible());
+  EXPECT_EQ(kOverrideText, view->GetText());
+}
+
 }  // namespace
 }  // namespace page_actions
diff --git a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_pixel_browsertest.cc b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_pixel_browsertest.cc
index 952b5a4..3f4f7dc 100644
--- a/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_pixel_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/dice_web_signin_interception_bubble_view_pixel_browsertest.cc
@@ -5,6 +5,7 @@
 #include <string>
 
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "chrome/browser/enterprise/browser_management/management_service_factory.h"
 #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
@@ -389,6 +390,13 @@
 
 IN_PROC_BROWSER_TEST_P(DiceWebSigninInterceptionBubblePixelTest,
                        InvokeUi_default) {
+#if BUILDFLAG(IS_WIN)
+  if (GetParam().test_suffix == "EnterpriseManagedIntercepted") {
+    // TODO(crbug.com/389737045): Enable for this variation once pixel tests
+    // are corrected.
+    GTEST_SKIP();
+  }
+#endif
   ShowAndVerifyUi();
 }
 
diff --git a/chrome/browser/ui/views/task_manager_view.cc b/chrome/browser/ui/views/task_manager_view.cc
index cdada453..0d95e25 100644
--- a/chrome/browser/ui/views/task_manager_view.cc
+++ b/chrome/browser/ui/views/task_manager_view.cc
@@ -623,6 +623,9 @@
   tab_table->SetModel(table_model_.get());
   tab_table->SetGrouper(this);
   tab_table->SetSortOnPaint(true);
+  if (table_config_.layout_refresh) {
+    tab_table->SetMouseHoveringEnabled(true);
+  }
   tab_table->set_observer(this);
   tab_table->set_context_menu_controller(this);
   set_context_menu_controller(this);
diff --git a/chrome/browser/ui/webauthn/passkey_upgrade_request_controller.cc b/chrome/browser/ui/webauthn/passkey_upgrade_request_controller.cc
index 985d800..8860684 100644
--- a/chrome/browser/ui/webauthn/passkey_upgrade_request_controller.cc
+++ b/chrome/browser/ui/webauthn/passkey_upgrade_request_controller.cc
@@ -7,14 +7,18 @@
 #include "chrome/browser/password_manager/account_password_store_factory.h"
 #include "chrome/browser/password_manager/profile_password_store_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/ui/passwords/passwords_client_ui_delegate.h"
 #include "chrome/browser/webauthn/enclave_manager_factory.h"
 #include "chrome/browser/webauthn/gpm_enclave_controller.h"
 #include "chrome/browser/webauthn/passkey_model_factory.h"
 #include "components/device_event_log/device_event_log.h"
+#include "components/password_manager/core/browser/features/password_manager_features_util.h"
 #include "components/password_manager/core/browser/form_parsing/form_data_parser.h"
 #include "components/password_manager/core/browser/password_store/password_store.h"
 #include "components/password_manager/core/browser/password_store/password_store_util.h"
+#include "components/password_manager/core/browser/password_sync_util.h"
+#include "components/sync/service/sync_service.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/document_user_data.h"
 #include "content/public/browser/render_frame_host.h"
@@ -67,13 +71,34 @@
 void PasskeyUpgradeRequestController::ContinuePendingUpgradeRequest() {
   CHECK_EQ(enclave_state_, EnclaveState::kReady);
   CHECK(pending_upgrade_request_);
+  CHECK(pending_callback_);
+
   pending_upgrade_request_ = false;
 
-  // TODO(crbug.com/377758786): The profile password store is probably wrong in
-  // some cases. Find out how to query GPM specifically.
-  scoped_refptr<password_manager::PasswordStoreInterface> password_store =
-      ProfilePasswordStoreFactory::GetForProfile(
-          profile(), ServiceAccessType::EXPLICIT_ACCESS);
+  // When looking for passwords that might be eligible to be upgraded, only
+  // consider passwords stored in GPM.
+  syncer::SyncService* sync_service =
+      SyncServiceFactory::GetForProfile(profile());
+  password_manager::PasswordStoreInterface* password_store = nullptr;
+  if (password_manager::features_util::IsOptedInForAccountStorage(
+          profile()->GetPrefs(), sync_service)) {
+    password_store = AccountPasswordStoreFactory::GetForProfile(
+                         profile(), ServiceAccessType::EXPLICIT_ACCESS)
+                         .get();
+  } else if (password_manager::sync_util::
+                 IsSyncFeatureEnabledIncludingPasswords(sync_service)) {
+    password_store = ProfilePasswordStoreFactory::GetForProfile(
+                         profile(), ServiceAccessType::EXPLICIT_ACCESS)
+                         .get();
+  }
+
+  if (!password_store) {
+    FIDO_LOG(EVENT)
+        << "Passkey upgrade failed without available password store";
+    std::move(pending_callback_).Run(false);
+    return;
+  }
+
   GURL url = origin().GetURL();
   password_manager::PasswordFormDigest form_digest(
       password_manager::PasswordForm::Scheme::kHtml,
diff --git a/chrome/browser/ui/webui/ash/lobster/lobster_page_handler_unittest.cc b/chrome/browser/ui/webui/ash/lobster/lobster_page_handler_unittest.cc
index 6088060c..28de48d 100644
--- a/chrome/browser/ui/webui/ash/lobster/lobster_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/ash/lobster/lobster_page_handler_unittest.cc
@@ -64,7 +64,9 @@
                       const std::string& description) override {
     return feedback_submission_status_;
   }
-  void LoadUI(std::optional<std::string> query, LobsterMode mode) override {}
+  void LoadUI(std::optional<std::string> query,
+              LobsterMode mode,
+              const gfx::Rect& caret_bounds) override {}
   void ShowUI() override {}
   void CloseUI() override {}
   void RecordWebUIMetricEvent(LobsterMetricState metric_state) override {
diff --git a/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc b/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
index 0607d8d..7d242edb4 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
@@ -307,10 +307,6 @@
        IDS_SETTINGS_SECURE_DNS_WITH_IDENTIFIERS_DESCRIPTION},
       {"secureDnsWithIdentifiersAndDomainConfigDescription",
        IDS_OS_SETTINGS_SECURE_DNS_WITH_IDENTIFIERS_AND_DOMAIN_CONFIG_DESCRIPTION},
-      {"secureDnsDialogTitle", IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TITLE},
-      {"secureDnsDialogBody", IDS_OS_SETTINGS_SECURE_DNS_DIALOG_BODY},
-      {"secureDnsDialogCancel", IDS_OS_SETTINGS_SECURE_DNS_DIALOG_CANCEL},
-      {"secureDnsDialogTurnOff", IDS_OS_SETTINGS_SECURE_DNS_DIALOG_TURN_OFF},
       {"secureDnsAutomaticModeDescription",
        IDS_OS_SETTINGS_SECURE_DNS_AUTOMATIC_MODE_DESCRIPTION},
       {"secureDnsSecureDropdownModeNetworkDefaultDescription",
@@ -665,9 +661,6 @@
 
   html_source->AddBoolean("showSecureDnsSetting", true);
   html_source->AddBoolean("showSecureDnsOsSettingLink", false);
-  html_source->AddBoolean(
-      "isDeprecateDnsDialogEnabled",
-      ash::features::IsOsSettingsDeprecateDnsDialogEnabled());
 
   ::settings::AddSecureDnsStrings(html_source);
   AddChromeOsSecureDnsStrings(html_source);
diff --git a/chrome/browser/ui/webui/ash/settings/pages/search/search_section_unittest.cc b/chrome/browser/ui/webui/ash/settings/pages/search/search_section_unittest.cc
index fa125c5..a026220d 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/search/search_section_unittest.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/search/search_section_unittest.cc
@@ -76,7 +76,15 @@
                    .value());
 }
 
-TEST_F(SearchSectionTest, DoesNotIncludeSunfishSettingsByDefault) {
+TEST_F(SearchSectionTest,
+       DoesNotIncludeSunfishSettingsWhenSunfishFeaturesDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(/*enabled_features=*/{}, /*disabled_features=*/
+                                {
+                                    features::kSunfishFeature,
+                                    features::kScannerUpdate,
+                                    features::kScannerDogfood,
+                                });
   search_section_ =
       std::make_unique<SearchSection>(profile(), search_tag_registry());
   std::unique_ptr<content::TestWebUIDataSource> html_source =
diff --git a/chrome/browser/webauthn/enclave_authenticator_browsertest.cc b/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
index 510d513..269b8b13 100644
--- a/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
+++ b/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
@@ -42,6 +42,7 @@
 #include "base/thread_annotations.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "chrome/browser/password_manager/account_password_store_factory.h"
 #include "chrome/browser/password_manager/profile_password_store_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
@@ -887,7 +888,12 @@
     sync_harness_ = SyncServiceImplHarness::Create(
         browser()->profile(), kEmail, "password",
         SyncServiceImplHarness::SigninType::FAKE_SIGNIN);
-    ASSERT_TRUE(sync_harness_->SetupSync());
+    if (sync_feature_enabled_) {
+      ASSERT_TRUE(sync_harness_->SetupSync());
+    } else {
+      // Sign in without full sync consent, opt into using account passwords.
+      ASSERT_TRUE(sync_harness_->SignInPrimaryAccount());
+    }
     sync_service->GetUserSettings()->SetSelectedTypes(
         /*sync_everything=*/false,
         /*types=*/{syncer::UserSelectableType::kPasswords});
@@ -1098,6 +1104,7 @@
                 crypto::ScopedFailingUserVerifyingKeyProvider>
       fake_uv_provider_;
   logging::ScopedVmoduleSwitches scoped_vmodule_;
+  bool sync_feature_enabled_ = true;
 };
 
 class EnclaveAuthenticatorWithPinBrowserTest
@@ -4142,14 +4149,29 @@
 }
 
 class EnclaveAuthenticatorConditionalCreateBrowserTest
-    : public EnclaveAuthenticatorWithPinBrowserTest {
+    : public EnclaveAuthenticatorWithPinBrowserTest,
+      public testing::WithParamInterface<bool> {
  protected:
   EnclaveAuthenticatorConditionalCreateBrowserTest() {
+    sync_feature_enabled_ = GetParam();
+
     scoped_feature_list_.InitAndEnableFeature(device::kWebAuthnPasskeyUpgrade);
     CHECK(base::FeatureList::IsEnabled(device::kWebAuthnPasskeyUpgrade));
     CHECK(base::FeatureList::IsEnabled(device::kWebAuthnEnclaveAuthenticator));
   }
 
+  bool use_account_password_store() { return !sync_feature_enabled_; }
+
+  password_manager::PasswordStoreInterface* password_store() {
+    return use_account_password_store()
+               ? AccountPasswordStoreFactory::GetForProfile(
+                     browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS)
+                     .get()
+               : ProfilePasswordStoreFactory::GetForProfile(
+                     browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS)
+                     .get();
+  }
+
   // Creates a credential to ensure the enclave authenticator is in a usable
   // state prior to making a conditional create request.
   void BootstrapEnclave() {
@@ -4183,10 +4205,6 @@
   }
 
   void InjectPassword(base::Time last_used) {
-    password_manager::PasswordStoreInterface* password_store =
-        ProfilePasswordStoreFactory::GetForProfile(
-            browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS)
-            .get();
     password_manager::PasswordForm saved_form;
     saved_form.signon_realm = https_server_.GetURL("example.com", "/").spec();
     saved_form.url = https_server_.GetURL("example.com",
@@ -4194,13 +4212,17 @@
     saved_form.username_value = u"bar@example.com";
     saved_form.password_value = u"hunter1";
     saved_form.date_last_used = last_used;
-    password_store->AddLogin(saved_form);
+    password_store()->AddLogin(saved_form);
   }
 
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(EnclaveAuthenticatorConditionalCreateBrowserTest,
+INSTANTIATE_TEST_SUITE_P(WithSyncFeatureEnabled,
+                         EnclaveAuthenticatorConditionalCreateBrowserTest,
+                         testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(EnclaveAuthenticatorConditionalCreateBrowserTest,
                        ConditionalCreate) {
   ASSERT_TRUE(ui_test_utils::NavigateToURL(
       browser(), https_server_.GetURL("www.example.com", "/title1.html")));
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 55beb6d..6f77ab3 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1736791001-f5a46224bb83ceb9e6b2cc5ccb8546d988065895-10021c5159e4121954dcc274c5eb9646ee6dad38.profdata
+chrome-android32-main-1736812635-cb2e1de919905e7391bc2854b6dee56823f3fbfe-7d3b695c550d443ed19e2b8a0598214c48556024.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index b9008e5b..8c7fabf 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1736794606-3b5562cc78b8a8c80dd894f57f809a19850c456e-ad66aa0ac8ad7552127ac34c2c812efe194edc69.profdata
+chrome-android64-main-1736812923-b91e22bd048e50ca156190c5f174498d600f0a08-9644bcf5a9588d562fa12611cda3252d26ee5760.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 90c46af..4dfa5e2 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1736798394-2eb45a99e528bce7e950ac6b6ad2a9c82ac72a2c-b80efb0bc63804804321a2b178d9a39df90c4d87.profdata
+chrome-mac-arm-main-1736805507-0b04020f195564cdf12834686b7ac6106027e38e-1fab80dad9a80b75211023046cb8d045515e945c.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index a4b18f9..1f5466b 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1736780267-6e6643332bea5129499835f448ff5a8f433c3030-121cfb01b818f003b8fd5ec34f347b123fa610a8.profdata
+chrome-win32-main-1736791001-caed23642957c755d4788f9416472485b042435f-10021c5159e4121954dcc274c5eb9646ee6dad38.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 96e1c84..f0d8c13 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1736769296-8be801aea43e420f465ae089961d0021065f5f4c-03b712dbd057f11864ee41f655aaa4cfdb1bf517.profdata
+chrome-win64-main-1736791001-766f078d3f5fb2b574e78bb139dc2e77dc5b8bc6-10021c5159e4121954dcc274c5eb9646ee6dad38.profdata
diff --git a/chrome/common/crash_keys.cc b/chrome/common/crash_keys.cc
index 82c1ce33..36f846b 100644
--- a/chrome/common/crash_keys.cc
+++ b/chrome/common/crash_keys.cc
@@ -206,6 +206,9 @@
     string_annotations = base::StrCat(
         {string_annotations, crash_key.Name(), "=", crash_key.Value()});
   }
+  if (string_annotations.empty()) {
+    return;
+  }
   command_line->AppendSwitchASCII(kStringAnnotationsSwitch, string_annotations);
 }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 78f92f6..0694f63 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1797,7 +1797,9 @@
         "../browser/extensions/api/runtime/runtime_apitest.cc",
         "../browser/extensions/api/web_request/web_request_apitest.cc",
         "../browser/extensions/background_script_executor_browsertest.cc",
+        "../browser/extensions/cross_origin_isolation_browsertest.cc",
         "../browser/extensions/desktop_android/desktop_android_extensions_browsertest.cc",
+        "../browser/extensions/extension_platform_browsertest_browsertest.cc",
         "../browser/extensions/system_cpu_apitest.cc",
         "../browser/extensions/system_memory_apitest.cc",
         "../browser/extensions/test_resources_browsertest.cc",
@@ -4220,6 +4222,7 @@
         "../browser/extensions/extension_loading_browsertest.cc",
         "../browser/extensions/extension_modules_apitest.cc",
         "../browser/extensions/extension_override_apitest.cc",
+        "../browser/extensions/extension_platform_browsertest_browsertest.cc",
         "../browser/extensions/extension_resource_request_policy_apitest.cc",
         "../browser/extensions/extension_security_exploit_browsertest.cc",
         "../browser/extensions/extension_shared_array_buffer_browsertest.cc",
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index c276be57..9453c3da 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -428,6 +428,7 @@
     "commerce/product_specifications:build_grdp",
     "commerce_internals:build_grdp",
     "cr_components:build_grdp",
+    "cr_components/cr_shortcut_input:build_grdp",
     "cr_components/help_bubble:build_grdp",
     "cr_components/history_clusters:build_grdp",
     "cr_components/history_embeddings:build_grdp",
@@ -482,6 +483,7 @@
     "$target_gen_dir/commerce/product_specifications/resources.grdp",
     "$target_gen_dir/commerce_internals/resources.grdp",
     "$target_gen_dir/cr_components/resources.grdp",
+    "$target_gen_dir/cr_components/cr_shortcut_input/resources.grdp",
     "$target_gen_dir/cr_components/help_bubble/resources.grdp",
     "$target_gen_dir/cr_components/history_clusters/resources.grdp",
     "$target_gen_dir/cr_components/history_embeddings/resources.grdp",
diff --git a/chrome/test/data/webui/chromeos/settings/os_privacy_page/secure_dns_test.ts b/chrome/test/data/webui/chromeos/settings/os_privacy_page/secure_dns_test.ts
index c94d76d1..8030d4c 100644
--- a/chrome/test/data/webui/chromeos/settings/os_privacy_page/secure_dns_test.ts
+++ b/chrome/test/data/webui/chromeos/settings/os_privacy_page/secure_dns_test.ts
@@ -13,12 +13,11 @@
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {SecureDnsInputElement, SettingsSecureDnsDialogElement, SettingsSecureDnsElement, SecureDnsResolverType} from 'chrome://os-settings/lazy_load.js';
+import {SecureDnsInputElement, SettingsSecureDnsElement, SecureDnsResolverType} from 'chrome://os-settings/lazy_load.js';
 import {PrivacyPageBrowserProxyImpl, ResolverOption, SecureDnsMode, LocalizedLinkElement, SecureDnsUiManagementMode, SettingsToggleButtonElement} from 'chrome://os-settings/os_settings.js';
-import {assertEquals, assertNull, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
-import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 
 import {TestPrivacyPageBrowserProxy} from './test_privacy_page_browser_proxy.js';
 
@@ -480,198 +479,3 @@
         });
   });
 });
-
-suite('SecureDnsDialog', () => {
-  let testBrowserProxy: TestPrivacyPageBrowserProxy;
-  let testElement: SettingsSecureDnsElement;
-  let secureDnsToggle: SettingsToggleButtonElement;
-  let secureDnsToggleDialog: SettingsSecureDnsDialogElement;
-  const isDeprecateDnsDialogEnabled =
-      loadTimeData.getBoolean('isDeprecateDnsDialogEnabled');
-
-  /**
-   * Checks that the select menu is shown and the toggle is properly
-   * configured for showing the configuration options.
-   */
-  function assertResolverSelectShown() {
-    assertTrue(secureDnsToggle.checked);
-    assertFalse(testElement.$.resolverSelect.hidden);
-  }
-
-  function setAndAssertSecureDnsDialog() {
-    secureDnsToggleDialog =
-        testElement.shadowRoot!.querySelector('#warningDialog')!;
-    assertTrue(!!secureDnsToggleDialog);
-    assertTrue(!!secureDnsToggleDialog.$.dialog);
-    assertTrue(isVisible(secureDnsToggleDialog.$.cancelButton));
-    assertTrue(isVisible(secureDnsToggleDialog.$.disableButton));
-  }
-
-  function getResolverOptions(): HTMLElement {
-    const options =
-        testElement.shadowRoot!.querySelector<HTMLElement>('#resolverOptions');
-    assertTrue(!!options);
-    return options;
-  }
-
-  suiteSetup(function() {
-    loadTimeData.overrideValues({
-      showSecureDnsSetting: true,
-    });
-  });
-
-  setup(async function() {
-    clearBody();
-
-    testBrowserProxy = new TestPrivacyPageBrowserProxy();
-    PrivacyPageBrowserProxyImpl.setInstance(testBrowserProxy);
-    testElement = document.createElement('settings-secure-dns');
-    testElement.prefs = {
-      'dns_over_https': {
-        'mode': {'value': SecureDnsMode.AUTOMATIC},
-        'templates': {'value': ''},
-      },
-    };
-    document.body.appendChild(testElement);
-
-    await testBrowserProxy.whenCalled('getSecureDnsSetting');
-    await flushTasks();
-
-    secureDnsToggle =
-        testElement.shadowRoot!.querySelector('#secureDnsToggle')!;
-    assertTrue(isVisible(secureDnsToggle));
-
-    assertResolverSelectShown();
-    assertEquals(
-        SecureDnsResolverType.AUTOMATIC, testElement.$.resolverSelect.value);
-  });
-
-  if (isDeprecateDnsDialogEnabled) {
-    test(
-        'No warning dialog appears when secure DNS is toggled off',
-        async () => {
-          // Initiate a toggle change from on to off.
-          secureDnsToggle.click();
-          await flushTasks();
-
-          secureDnsToggleDialog =
-              testElement.shadowRoot!.querySelector('#warningDialog')!;
-          assertNull(secureDnsToggleDialog);
-          assertFalse(secureDnsToggle.checked);
-          assertTrue(getResolverOptions().hidden);
-        });
-  } else {
-    test('SecureDnsDialogSanityCheck', () => {
-      // Initiate a toggle change from on to off, opens the warning dialog.
-      secureDnsToggle.click();
-      flush();
-
-      setAndAssertSecureDnsDialog();
-    });
-
-    test('SecureDnsDialogCancel', async () => {
-      // Initiate a toggle change from on to off, opens the warning dialog.
-      secureDnsToggle.click();
-      flush();
-      setAndAssertSecureDnsDialog();
-
-      secureDnsToggleDialog.$.cancelButton.click();
-      flush();
-
-      // Wait for onDisableDnsDialogClosed_ to finish.
-      await flushTasks();
-      await waitAfterNextRender(secureDnsToggle);
-
-      assertFalse(secureDnsToggleDialog.$.dialog.open);
-      assertTrue(secureDnsToggle.checked);
-      assertResolverSelectShown();
-      assertEquals(
-          SecureDnsResolverType.AUTOMATIC, testElement.$.resolverSelect.value);
-    });
-
-    test('SecureDnsDialogOnToOff', () => {
-      // Initiate a toggle change from on to off, opens the warning dialog.
-      secureDnsToggle.click();
-      flush();
-      setAndAssertSecureDnsDialog();
-
-      // Turn off the toggle
-      secureDnsToggleDialog.$.disableButton.click();
-      webUIListenerCallback('secure-dns-setting-changed', {
-        mode: SecureDnsMode.OFF,
-        config: '',
-        osMode: SecureDnsMode.OFF,
-        osConfig: '',
-        managementMode: SecureDnsUiManagementMode.NO_OVERRIDE,
-      });
-      flush();
-
-      assertFalse(secureDnsToggle.checked);
-      assertTrue(getResolverOptions().hidden);
-    });
-
-    test('SecureDnsDialogSecureOffToOn', () => {
-      // If the user selects Custom Secure mode with an invalid input, we will
-      // not register that the user wants to use secure mode (see the comment on
-      // secure_dns_dialog.ts), however, when toggled off and on, we will still
-      // show that the user had selected Custom before.
-
-      // Select Secure in the menu button with no input and config.
-      webUIListenerCallback('secure-dns-setting-changed', {
-        mode: SecureDnsMode.SECURE,
-        config: '',
-        osMode: SecureDnsMode.SECURE,
-        osConfig: '',
-        managementMode: SecureDnsUiManagementMode.NO_OVERRIDE,
-      });
-      testElement.prefs = {
-        'dns_over_https': {
-          'mode': {'value': SecureDnsMode.SECURE},
-          'templates': {'value': ''},
-        },
-      };
-
-      // Simulate that the toggle is off.
-      webUIListenerCallback('secure-dns-setting-changed', {
-        mode: SecureDnsMode.OFF,
-        config: '',
-        osMode: SecureDnsMode.OFF,
-        osConfig: '',
-        managementMode: SecureDnsUiManagementMode.NO_OVERRIDE,
-      });
-      testElement.prefs = {
-        'dns_over_https':
-            {'mode': {'value': SecureDnsMode.OFF}, 'templates': {'value': ''}},
-      };
-
-      // Turn on the toggle
-      secureDnsToggle.click();
-      flush();
-      assertTrue(secureDnsToggle.checked);
-      assertResolverSelectShown();
-      assertEquals(
-          SecureDnsResolverType.CUSTOM, testElement.$.resolverSelect.value);
-
-      // Turn off the toggle, this will dispatch an event from the dialog since
-      // the invalid Custom secure mode was not registered to the pref. For more
-      // info, see the comment in secure_dns_dialog.ts.
-      secureDnsToggle.click();
-      flush();
-      setAndAssertSecureDnsDialog();
-
-      secureDnsToggleDialog.$.disableButton.click();
-      flush();
-      assertFalse(secureDnsToggle.checked);
-      assertTrue(getResolverOptions().hidden);
-
-      // Turn on the toggle. The selected menu option should still be secure
-      // mode.
-      secureDnsToggle.click();
-      flush();
-      assertTrue(secureDnsToggle.checked);
-      assertResolverSelectShown();
-      assertEquals(
-          SecureDnsResolverType.CUSTOM, testElement.$.resolverSelect.value);
-    });
-  }
-});
diff --git a/chrome/test/data/webui/chromeos/settings/os_settings_browsertest.cc b/chrome/test/data/webui/chromeos/settings/os_settings_browsertest.cc
index 976805e..2ba98d8 100644
--- a/chrome/test/data/webui/chromeos/settings/os_settings_browsertest.cc
+++ b/chrome/test/data/webui/chromeos/settings/os_settings_browsertest.cc
@@ -293,13 +293,6 @@
       ash::features::kCrosPrivacyHub};
 };
 
-class OSSettingsPrivacyTestDeprecateDnsDialogEnabled
-    : public OSSettingsMochaTest {
- private:
-  base::test::ScopedFeatureList scoped_feature_list_{
-      ash::features::kOsSettingsDeprecateDnsDialog};
-};
-
 using OSSettingsPrivacyPageTestPrivacyHubSubpage = OSSettingsMochaTest;
 
 class OSSettingsResetTestSanitizeEnabled : public OSSettingsMochaTest {
@@ -1467,17 +1460,6 @@
                   "runMochaSuite('SettingsSecureDns')");
 }
 
-IN_PROC_BROWSER_TEST_F(OSSettingsPrivacyTestDeprecateDnsDialogEnabled,
-                       OsPrivacyPageDeprecateDnsDialog) {
-  RunSettingsTest("os_privacy_page/secure_dns_test.js",
-                  "runMochaSuite('SecureDnsDialog')");
-}
-
-IN_PROC_BROWSER_TEST_F(OSSettingsMochaTest, OsPrivacyPageSecureDnsDialog) {
-  RunSettingsTest("os_privacy_page/secure_dns_test.js",
-                  "runMochaSuite('SecureDnsDialog')");
-}
-
 IN_PROC_BROWSER_TEST_F(OSSettingsMochaTest, OsPrivacyPageSmartPrivacySubpage) {
   RunSettingsTest("os_privacy_page/smart_privacy_subpage_test.js");
 }
diff --git a/chrome/test/data/webui/cr_components/cr_components_interactive_test.cc b/chrome/test/data/webui/cr_components/cr_components_interactive_test.cc
index 8f53a0e..3393511 100644
--- a/chrome/test/data/webui/cr_components/cr_components_interactive_test.cc
+++ b/chrome/test/data/webui/cr_components/cr_components_interactive_test.cc
@@ -14,6 +14,11 @@
   RunTest("cr_components/most_visited_focus_test.js", "mocha.run()");
 }
 
+IN_PROC_BROWSER_TEST_F(CrComponentsFocusTest, CrShortcutInput) {
+  RunTest("cr_components/cr_shortcut_input/cr_shortcut_input_test.js",
+          "mocha.run()");
+}
+
 class CrComponentsHistoryClustersFocusTest : public WebUIMochaFocusTest {
  protected:
   CrComponentsHistoryClustersFocusTest() {
diff --git a/chrome/test/data/webui/cr_components/cr_shortcut_input/BUILD.gn b/chrome/test/data/webui/cr_components/cr_shortcut_input/BUILD.gn
new file mode 100644
index 0000000..740747d
--- /dev/null
+++ b/chrome/test/data/webui/cr_components/cr_shortcut_input/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2025 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/test/data/webui/build_webui_tests.gni")
+
+assert(!is_android && !is_ios)
+
+build_webui_tests("build") {
+  files = [ "cr_shortcut_input_test.ts" ]
+
+  ts_deps = [
+    "//third_party/lit/v3_0:build_ts",
+    "//ui/webui/resources/cr_components/cr_shortcut_input:build_ts",
+    "//ui/webui/resources/js:build_ts",
+  ]
+}
diff --git a/chrome/test/data/webui/cr_components/cr_shortcut_input/cr_shortcut_input_test.ts b/chrome/test/data/webui/cr_components/cr_shortcut_input/cr_shortcut_input_test.ts
new file mode 100644
index 0000000..83fda88
--- /dev/null
+++ b/chrome/test/data/webui/cr_components/cr_shortcut_input/cr_shortcut_input_test.ts
@@ -0,0 +1,135 @@
+// Copyright 2016 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/** @fileoverview Suite of tests for cr-shortcut-input. */
+
+import 'chrome://resources/cr_components/cr_shortcut_input/cr_shortcut_input.js';
+
+import type {CrShortcutInputElement} from 'chrome://resources/cr_components/cr_shortcut_input/cr_shortcut_input.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import type {Modifier} from 'chrome://webui-test/keyboard_mock_interactions.js';
+import {keyDownOn, keyUpOn} from 'chrome://webui-test/keyboard_mock_interactions.js';
+import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js';
+
+suite('CrShortcutInputTest', function() {
+  let input: CrShortcutInputElement;
+
+  setup(function() {
+    loadTimeData.resetForTesting({
+      shortcutSet: 'set',
+      shortcutNotSet: 'not set',
+      shortcutTypeAShortcut: 'type',
+      shortcutIncludeStartModifier: 'include modifier',
+      shortcutTooManyModifiers: 'too many modifier',
+      shortcutNeedCharacter: 'need character',
+    });
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    input = document.createElement('cr-shortcut-input');
+    document.body.appendChild(input);
+  });
+
+  async function assertError(
+      isKeyDown: boolean, keyCode: number, modifiers: Modifier[],
+      expectedErrorStringId: string) {
+    const field = input.$.input;
+    if (isKeyDown) {
+      keyDownOn(field, keyCode, modifiers);
+    } else {
+      keyUpOn(field, keyCode, modifiers);
+    }
+    await microtasksFinished();
+    assertEquals('', field.value);
+    assertEquals(
+        loadTimeData.getString(expectedErrorStringId), field.errorMessage);
+  }
+
+  test('Basic', async function() {
+    const field = input.$.input;
+    assertEquals('', field.value);
+
+    // Click the edit button. Capture should start.
+    const whenInputCaptureChange =
+        eventToPromise('input-capture-change', input);
+    input.$.edit.click();
+    let event = await whenInputCaptureChange;
+    assertTrue(event.detail);
+    await microtasksFinished();
+    assertEquals('', field.value);
+
+    // Press character.
+    await assertError(true, 65, [], 'shortcutIncludeStartModifier');
+    // Add shift to character.
+    await assertError(true, 65, ['shift'], 'shortcutIncludeStartModifier');
+    // Press ctrl.
+    await assertError(true, 17, ['ctrl'], 'shortcutNeedCharacter');
+    // Add shift.
+    await assertError(true, 17, ['ctrl', 'shift'], 'shortcutNeedCharacter');
+    // Remove shift.
+    await assertError(false, 17, ['ctrl'], 'shortcutNeedCharacter');
+    // Add alt (ctrl + alt is invalid).
+    await assertError(true, 17, ['ctrl', 'alt'], 'shortcutTooManyModifiers');
+    // Remove alt.
+    await assertError(false, 17, ['ctrl'], 'shortcutNeedCharacter');
+
+    // Add 'A'. Once a valid shortcut is typed (like Ctrl + A), it is
+    // committed.
+    const whenShortcutUpdate = eventToPromise('shortcut-updated', input);
+    keyDownOn(field, 65, ['ctrl']);
+    event = await whenShortcutUpdate;
+    assertEquals('Ctrl+A', event.detail);
+
+    await microtasksFinished();
+    assertEquals('Ctrl + A', field.value);
+    assertEquals('Ctrl+A', input.shortcut);
+
+    // Test clearing the shortcut.
+    const clearShortcutPromise = eventToPromise('shortcut-updated', input);
+    input.$.edit.click();
+    assertEquals(input.$.input, input.shadowRoot!.activeElement);
+    event = await clearShortcutPromise;
+    await microtasksFinished();
+    assertEquals('', event.detail);
+    field.blur();
+    assertEquals('', input.shortcut);
+
+    // The `input-capture-change` event should happen twice when the edit button
+    // is clicked. The first event is triggered when the mouse down happens and
+    // the input capture should stop. The second event occurs during mouse up
+    // which triggers the button to start the input capture again.
+    const inputCaptureChangeResults: boolean[] = [];
+    input.addEventListener('input-capture-change', (e) => {
+      inputCaptureChangeResults.push((e as CustomEvent<boolean>).detail);
+    });
+
+    input.$.edit.click();
+    await microtasksFinished();
+    assertEquals(2, inputCaptureChangeResults.length);
+    assertFalse(inputCaptureChangeResults[0]!);
+    assertTrue(inputCaptureChangeResults[1]!);
+
+    // Test ending capture using the escape key.
+    const stopInputCapturePromise =
+        eventToPromise('input-capture-change', input);
+    input.$.edit.click();
+    keyDownOn(field, 27);  // Escape key.
+    event = await stopInputCapturePromise;
+    assertFalse(event.detail);
+  });
+
+  test('AriaLabelUpdates', async function() {
+    // Verify that the aria labels are initially empty
+    assertEquals('', input.$.input.ariaLabel);
+    assertEquals('', input.$.edit.ariaLabel);
+
+    // Update the input and edit button aria labels
+    const inputAriaLabel = 'input';
+    const editButtonAriaLabel = 'edit';
+    input.inputAriaLabel = inputAriaLabel;
+    input.editButtonAriaLabel = editButtonAriaLabel;
+    await microtasksFinished();
+    assertEquals(inputAriaLabel, input.$.input.ariaLabel);
+    assertEquals(editButtonAriaLabel, input.$.edit.ariaLabel);
+  });
+});
diff --git a/chrome/test/data/webui/extensions/BUILD.gn b/chrome/test/data/webui/extensions/BUILD.gn
index 12dd6f0..284012a0 100644
--- a/chrome/test/data/webui/extensions/BUILD.gn
+++ b/chrome/test/data/webui/extensions/BUILD.gn
@@ -40,7 +40,6 @@
     "review_panel_test.ts",
     "runtime_host_permissions_test.ts",
     "runtime_hosts_dialog_test.ts",
-    "shortcut_input_test.ts",
     "sidebar_test.ts",
     "site_permissions_by_site_test.ts",
     "site_permissions_edit_permissions_dialog_test.ts",
@@ -62,6 +61,7 @@
   ts_deps = [
     "//chrome/browser/resources/extensions:build_ts",
     "//third_party/lit/v3_0:build_ts",
+    "//ui/webui/resources/cr_components/cr_shortcut_input:build_ts",
     "//ui/webui/resources/cr_elements:build_ts",
     "//ui/webui/resources/js:build_ts",
   ]
diff --git a/chrome/test/data/webui/extensions/extensions_browsertest.cc b/chrome/test/data/webui/extensions/extensions_browsertest.cc
index e68ae1f..cc60afe 100644
--- a/chrome/test/data/webui/extensions/extensions_browsertest.cc
+++ b/chrome/test/data/webui/extensions/extensions_browsertest.cc
@@ -719,14 +719,6 @@
   RunTestCase("Layout");
 }
 
-IN_PROC_BROWSER_TEST_F(CrExtensionsShortcutTest, IsValidKeyCode) {
-  RunTestCase("IsValidKeyCode");
-}
-
-IN_PROC_BROWSER_TEST_F(CrExtensionsShortcutTest, KeyStrokeToString) {
-  RunTestCase("KeyStrokeToString");
-}
-
 IN_PROC_BROWSER_TEST_F(CrExtensionsShortcutTest, ScopeChange) {
   RunTestCase("ScopeChange");
 }
diff --git a/chrome/test/data/webui/extensions/extensions_focus_test.cc b/chrome/test/data/webui/extensions/extensions_focus_test.cc
index b521d2b..1b9361cb 100644
--- a/chrome/test/data/webui/extensions/extensions_focus_test.cc
+++ b/chrome/test/data/webui/extensions/extensions_focus_test.cc
@@ -8,13 +8,6 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 
-using CrExtensionsShortcutInputTest = WebUIMochaFocusTest;
-IN_PROC_BROWSER_TEST_F(CrExtensionsShortcutInputTest, Basic) {
-  set_test_loader_host(chrome::kChromeUIExtensionsHost);
-  RunTest("extensions/shortcut_input_test.js",
-          "runMochaTest('ExtensionShortcutInputTest', 'Basic')");
-}
-
 class CrExtensionsFocusTest : public WebUIMochaFocusTest {
  protected:
   CrExtensionsFocusTest() {
@@ -27,6 +20,11 @@
           "runMochaTest('ExtensionManagerUnitTest', 'UninstallFocus')");
 }
 
+IN_PROC_BROWSER_TEST_F(CrExtensionsFocusTest, UpdateShortcut) {
+  RunTest("extensions/keyboard_shortcuts_test.js",
+          "runMochaTest('ExtensionShortcutTest', 'UpdateShortcut')");
+}
+
 class CrExtensionsOptionsPageTest : public ExtensionSettingsTestBase {
  protected:
   void OnWebContentsAvailable(content::WebContents* web_contents) override {
diff --git a/chrome/test/data/webui/extensions/keyboard_shortcuts_test.ts b/chrome/test/data/webui/extensions/keyboard_shortcuts_test.ts
index 0c0beb07..8929990 100644
--- a/chrome/test/data/webui/extensions/keyboard_shortcuts_test.ts
+++ b/chrome/test/data/webui/extensions/keyboard_shortcuts_test.ts
@@ -4,9 +4,11 @@
 
 /** @fileoverview Suite of tests for extension-keyboard-shortcuts. */
 
+import 'chrome://extensions/extensions.js';
+
 import type {ExtensionsKeyboardShortcutsElement} from 'chrome://extensions/extensions.js';
-import {isValidKeyCode, Key, keystrokeToString} from 'chrome://extensions/extensions.js';
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {keyDownOn, keyUpOn} from 'chrome://webui-test/keyboard_mock_interactions.js';
 import {isChildVisible, microtasksFinished} from 'chrome://webui-test/test_util.js';
 
 import {TestService} from './test_service.js';
@@ -88,34 +90,6 @@
     assertEquals(2, commands.length);
   });
 
-  test('IsValidKeyCode', function() {
-    assertTrue(isValidKeyCode('A'.charCodeAt(0)));
-    assertTrue(isValidKeyCode('F'.charCodeAt(0)));
-    assertTrue(isValidKeyCode('Z'.charCodeAt(0)));
-    assertTrue(isValidKeyCode('4'.charCodeAt(0)));
-    assertTrue(isValidKeyCode(Key.PAGE_UP));
-    assertTrue(isValidKeyCode(Key.MEDIA_PLAY_PAUSE));
-    assertTrue(isValidKeyCode(Key.DOWN));
-    assertFalse(isValidKeyCode(16));   // Shift
-    assertFalse(isValidKeyCode(17));   // Ctrl
-    assertFalse(isValidKeyCode(18));   // Alt
-    assertFalse(isValidKeyCode(113));  // F2
-    assertFalse(isValidKeyCode(144));  // Num Lock
-    assertFalse(isValidKeyCode(43));   // +
-    assertFalse(isValidKeyCode(27));   // Escape
-  });
-
-  test('KeyStrokeToString', function() {
-    const charCodeA = 'A'.charCodeAt(0);
-    let e = new KeyboardEvent('keydown', {keyCode: charCodeA});
-    assertEquals('A', keystrokeToString(e));
-    e = new KeyboardEvent('keydown', {keyCode: charCodeA, ctrlKey: true});
-    assertEquals('Ctrl+A', keystrokeToString(e));
-    e = new KeyboardEvent(
-        'keydown', {keyCode: charCodeA, ctrlKey: true, shiftKey: true});
-    assertEquals('Ctrl+Shift+A', keystrokeToString(e));
-  });
-
   test('ScopeChange', async function() {
     const selectElement = keyboardShortcuts.shadowRoot!.querySelector('select');
     assertTrue(!!selectElement);
@@ -128,4 +102,100 @@
     await microtasksFinished();
     assertEquals(selectElement.value, params[2]);
   });
+
+
+  test('UpdateShortcut', async function() {
+    const shortcutInput =
+        keyboardShortcuts.shadowRoot!.querySelector('cr-shortcut-input');
+    assertTrue(!!shortcutInput);
+    const field = shortcutInput.$.input;
+    assertEquals('Ctrl + W', field.value);
+
+    // Click the edit button. Capture should start.
+    shortcutInput.$.edit.click();
+    let arg = await testDelegate.whenCalled('setShortcutHandlingSuspended');
+
+    assertTrue(arg);
+    testDelegate.reset();
+    await microtasksFinished();
+    assertEquals('', field.value);
+
+    // Press character.
+    keyDownOn(field, 65, []);
+    await microtasksFinished();
+    assertEquals('', field.value);
+    assertTrue(field.errorMessage!.startsWith('Include'));
+    // Add shift to character.
+    keyDownOn(field, 65, ['shift']);
+    await microtasksFinished();
+    assertEquals('', field.value);
+    assertTrue(field.errorMessage!.startsWith('Include'));
+    // Press ctrl.
+    keyDownOn(field, 17, ['ctrl']);
+    await microtasksFinished();
+    assertEquals('', field.value);
+    assertEquals('Type a letter', field.errorMessage);
+    // Add shift.
+    keyDownOn(field, 16, ['ctrl', 'shift']);
+    await microtasksFinished();
+    assertEquals('', field.value);
+    assertEquals('Type a letter', field.errorMessage);
+    // Remove shift.
+    keyUpOn(field, 16, ['ctrl']);
+    await microtasksFinished();
+    assertEquals('', field.value);
+    assertEquals('Type a letter', field.errorMessage);
+    // Add alt (ctrl + alt is invalid).
+    keyDownOn(field, 18, ['ctrl', 'alt']);
+    await microtasksFinished();
+    assertEquals('', field.value);
+    // Remove alt.
+    keyUpOn(field, 18, ['ctrl']);
+    await microtasksFinished();
+    assertEquals('', field.value);
+    assertEquals('Type a letter', field.errorMessage);
+
+    // Add 'A'. Once a valid shortcut is typed (like Ctrl + A), it is
+    // committed.
+    keyDownOn(field, 65, ['ctrl']);
+    arg = await testDelegate.whenCalled('updateExtensionCommandKeybinding');
+    testDelegate.reset();
+
+    assertDeepEquals(
+        [oneCommand.id, oneCommand.commands[0]!.name, 'Ctrl+A'], arg);
+    await microtasksFinished();
+    assertEquals('Ctrl + A', field.value);
+    assertEquals('Ctrl+A', shortcutInput.shortcut);
+
+    // Test clearing the shortcut.
+    shortcutInput.$.edit.click();
+    assertEquals(
+        shortcutInput.$.input, shortcutInput.shadowRoot!.activeElement);
+    arg = await testDelegate.whenCalled('updateExtensionCommandKeybinding');
+    await microtasksFinished();
+
+    field.blur();
+    testDelegate.reset();
+    assertDeepEquals([oneCommand.id, oneCommand.commands[0]!.name, ''], arg);
+    assertEquals('', shortcutInput.shortcut);
+
+    // The click event causes the input element to lose focus on mouse down
+    // but regains focus on mouse up after triggering the edit button on mouse
+    // up. This should ultimately result in shortcuts being suspended.
+    shortcutInput.$.edit.click();
+    await testDelegate.whenCalled('setShortcutHandlingSuspended');
+    await microtasksFinished();
+    const shortcutSuspendedArgs =
+        testDelegate.getArgs('setShortcutHandlingSuspended');
+    assertEquals(2, testDelegate.getCallCount('setShortcutHandlingSuspended'));
+    assertFalse(shortcutSuspendedArgs[0]);
+    assertTrue(shortcutSuspendedArgs[1]);
+    testDelegate.reset();
+
+    // Test ending capture using the escape key.
+    shortcutInput.$.edit.click();
+    keyDownOn(field, 27);  // Escape key.
+    arg = await testDelegate.whenCalled('setShortcutHandlingSuspended');
+    assertFalse(arg);
+  });
 });
diff --git a/chrome/test/data/webui/extensions/shortcut_input_test.ts b/chrome/test/data/webui/extensions/shortcut_input_test.ts
deleted file mode 100644
index 353758d..0000000
--- a/chrome/test/data/webui/extensions/shortcut_input_test.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @fileoverview Suite of tests for extension-shortcut-input. */
-
-import 'chrome://extensions/extensions.js';
-
-import type {ExtensionsShortcutInputElement} from 'chrome://extensions/extensions.js';
-import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {keyDownOn, keyUpOn} from 'chrome://webui-test/keyboard_mock_interactions.js';
-import {microtasksFinished} from 'chrome://webui-test/test_util.js';
-
-import {TestService} from './test_service.js';
-import {createExtensionInfo} from './test_util.js';
-
-suite('ExtensionShortcutInputTest', function() {
-  let input: ExtensionsShortcutInputElement;
-  let testService: TestService;
-
-  setup(function() {
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-    input = document.createElement('extensions-shortcut-input');
-    testService = new TestService();
-    input.delegate = testService;
-    input.command = {
-      description: 'Command description',
-      keybinding: 'Ctrl+W',
-      name: 'Command',
-      isActive: true,
-      scope: chrome.developerPrivate.CommandScope.CHROME,
-      isExtensionAction: true,
-    };
-    input.item = createExtensionInfo({id: 'itemid'});
-    document.body.appendChild(input);
-  });
-
-  test('Basic', async function() {
-    const field = input.$.input;
-    assertEquals('', field.value);
-
-    // Click the edit button. Capture should start.
-    input.$.edit.click();
-    let arg = await testService.whenCalled('setShortcutHandlingSuspended');
-
-    assertTrue(arg);
-    testService.reset();
-    await microtasksFinished();
-    assertEquals('', field.value);
-
-    // Press character.
-    keyDownOn(field, 65, []);
-    await microtasksFinished();
-    assertEquals('', field.value);
-    assertTrue(field.errorMessage!.startsWith('Include'));
-    // Add shift to character.
-    keyDownOn(field, 65, ['shift']);
-    await microtasksFinished();
-    assertEquals('', field.value);
-    assertTrue(field.errorMessage!.startsWith('Include'));
-    // Press ctrl.
-    keyDownOn(field, 17, ['ctrl']);
-    await microtasksFinished();
-    assertEquals('', field.value);
-    assertEquals('Type a letter', field.errorMessage);
-    // Add shift.
-    keyDownOn(field, 16, ['ctrl', 'shift']);
-    await microtasksFinished();
-    assertEquals('', field.value);
-    assertEquals('Type a letter', field.errorMessage);
-    // Remove shift.
-    keyUpOn(field, 16, ['ctrl']);
-    await microtasksFinished();
-    assertEquals('', field.value);
-    assertEquals('Type a letter', field.errorMessage);
-    // Add alt (ctrl + alt is invalid).
-    keyDownOn(field, 18, ['ctrl', 'alt']);
-    await microtasksFinished();
-    assertEquals('', field.value);
-    // Remove alt.
-    keyUpOn(field, 18, ['ctrl']);
-    await microtasksFinished();
-    assertEquals('', field.value);
-    assertEquals('Type a letter', field.errorMessage);
-
-    // Add 'A'. Once a valid shortcut is typed (like Ctrl + A), it is
-    // committed.
-    keyDownOn(field, 65, ['ctrl']);
-    arg = await testService.whenCalled('updateExtensionCommandKeybinding');
-
-    testService.reset();
-    assertDeepEquals(['itemid', 'Command', 'Ctrl+A'], arg);
-    await microtasksFinished();
-    assertEquals('Ctrl + A', field.value);
-    assertEquals('Ctrl+A', input.shortcut);
-
-    // Test clearing the shortcut.
-    input.$.edit.click();
-    assertEquals(input.$.input, input.shadowRoot!.activeElement);
-    arg = await testService.whenCalled('updateExtensionCommandKeybinding');
-    await microtasksFinished();
-
-    field.blur();
-    testService.reset();
-    assertDeepEquals(['itemid', 'Command', ''], arg);
-    assertEquals('', input.shortcut);
-
-    input.$.edit.click();
-    arg = await testService.whenCalled('setShortcutHandlingSuspended');
-    await microtasksFinished();
-    testService.reset();
-    assertTrue(arg);
-
-    // Test ending capture using the escape key.
-    input.$.edit.click();
-    keyDownOn(field, 27);  // Escape key.
-    arg = await testService.whenCalled('setShortcutHandlingSuspended');
-    assertFalse(arg);
-  });
-});
diff --git a/chrome/browser/ash/arc/video/BUILD.gn b/chromeos/ash/experiences/arc/video/BUILD.gn
similarity index 100%
rename from chrome/browser/ash/arc/video/BUILD.gn
rename to chromeos/ash/experiences/arc/video/BUILD.gn
diff --git a/chromeos/ash/experiences/arc/video/DEPS b/chromeos/ash/experiences/arc/video/DEPS
new file mode 100644
index 0000000..d6e2030
--- /dev/null
+++ b/chromeos/ash/experiences/arc/video/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+content/public/common/content_switches.h",
+]
diff --git a/chrome/browser/ash/arc/video/OWNERS b/chromeos/ash/experiences/arc/video/OWNERS
similarity index 100%
rename from chrome/browser/ash/arc/video/OWNERS
rename to chromeos/ash/experiences/arc/video/OWNERS
diff --git a/chrome/browser/ash/arc/video/gpu_arc_video_service_host.cc b/chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.cc
similarity index 99%
rename from chrome/browser/ash/arc/video/gpu_arc_video_service_host.cc
rename to chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.cc
index 3149de6..711c73b 100644
--- a/chrome/browser/ash/arc/video/gpu_arc_video_service_host.cc
+++ b/chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ash/arc/video/gpu_arc_video_service_host.h"
+#include "chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.h"
 
 #include <memory>
 #include <string>
diff --git a/chrome/browser/ash/arc/video/gpu_arc_video_service_host.h b/chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.h
similarity index 90%
rename from chrome/browser/ash/arc/video/gpu_arc_video_service_host.h
rename to chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.h
index a105912..5cb9be2d 100644
--- a/chrome/browser/ash/arc/video/gpu_arc_video_service_host.h
+++ b/chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ASH_ARC_VIDEO_GPU_ARC_VIDEO_SERVICE_HOST_H_
-#define CHROME_BROWSER_ASH_ARC_VIDEO_GPU_ARC_VIDEO_SERVICE_HOST_H_
+#ifndef CHROMEOS_ASH_EXPERIENCES_ARC_VIDEO_GPU_ARC_VIDEO_SERVICE_HOST_H_
+#define CHROMEOS_ASH_EXPERIENCES_ARC_VIDEO_GPU_ARC_VIDEO_SERVICE_HOST_H_
 
 #include "ash/components/arc/mojom/video.mojom.h"
 #include "base/memory/raw_ptr.h"
@@ -70,4 +70,4 @@
 
 }  // namespace arc
 
-#endif  // CHROME_BROWSER_ASH_ARC_VIDEO_GPU_ARC_VIDEO_SERVICE_HOST_H_
+#endif  // CHROMEOS_ASH_EXPERIENCES_ARC_VIDEO_GPU_ARC_VIDEO_SERVICE_HOST_H_
diff --git a/clank b/clank
index ff16da3..13571b2 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit ff16da349347e90984ba1bc206cafccad280e3c6
+Subproject commit 13571b2b78ee3c8179518064da9ed1a3f5da47ec
diff --git a/components/autofill/android/java/strings/autofill_strings.grd b/components/autofill/android/java/strings/autofill_strings.grd
index 1c7b7eccb..7b6333f 100644
--- a/components/autofill/android/java/strings/autofill_strings.grd
+++ b/components/autofill/android/java/strings/autofill_strings.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="values-af/autofill_strings.xml" lang="af" type="android" />
     <output filename="values-am/autofill_strings.xml" lang="am" type="android" />
diff --git a/components/autofill/core/browser/geo/autofill_address_rewriter_resources.grd b/components/autofill/core/browser/geo/autofill_address_rewriter_resources.grd
index 1db1460..51329f19 100644
--- a/components/autofill/core/browser/geo/autofill_address_rewriter_resources.grd
+++ b/components/autofill/core/browser/geo/autofill_address_rewriter_resources.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-  <grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+  <grit latest_public_release="0" current_release="1">
     <outputs>
       <output filename="grit/autofill_address_rewriter_resources.h" type="rc_header">
           <emit emit_type="prepend">
diff --git a/components/components_chromium_strings.grd b/components/components_chromium_strings.grd
index 87c402d..a5a2c3bed 100644
--- a/components/components_chromium_strings.grd
+++ b/components/components_chromium_strings.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="grit/components_branded_strings.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/components/components_google_chrome_strings.grd b/components/components_google_chrome_strings.grd
index e237c2d..f248690 100644
--- a/components/components_google_chrome_strings.grd
+++ b/components/components_google_chrome_strings.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="grit/components_branded_strings.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/components/components_locale_settings.grd b/components/components_locale_settings.grd
index 8a13525..4adcb82 100644
--- a/components/components_locale_settings.grd
+++ b/components/components_locale_settings.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/components_locale_settings.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/components/components_strings.grd b/components/components_strings.grd
index 3f6b44c..a9a79dc 100644
--- a/components/components_strings.grd
+++ b/components/components_strings.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="grit/components_strings.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/components/embedder_support/android/java/strings/web_contents_delegate_android_strings.grd b/components/embedder_support/android/java/strings/web_contents_delegate_android_strings.grd
index aa1a7d7..2a9e9b7 100644
--- a/components/embedder_support/android/java/strings/web_contents_delegate_android_strings.grd
+++ b/components/embedder_support/android/java/strings/web_contents_delegate_android_strings.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="values-af/web_contents_delegate_android_strings.xml" lang="af" type="android" />
     <output filename="values-am/web_contents_delegate_android_strings.xml" lang="am" type="android" />
diff --git a/components/headless/command_handler/headless_command.grd b/components/headless/command_handler/headless_command.grd
index 7c1277e..6b5f538 100644
--- a/components/headless/command_handler/headless_command.grd
+++ b/components/headless/command_handler/headless_command.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/headless_command_resources.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/components/javascript_dialogs/android/javascript_dialogs_android_strings.grd b/components/javascript_dialogs/android/javascript_dialogs_android_strings.grd
index 3ad3c461..b8d0435 100644
--- a/components/javascript_dialogs/android/javascript_dialogs_android_strings.grd
+++ b/components/javascript_dialogs/android/javascript_dialogs_android_strings.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="values-af/javascript_dialogs_android_strings.xml" lang="af" type="android" />
     <output filename="values-am/javascript_dialogs_android_strings.xml" lang="am" type="android" />
diff --git a/components/media_router/browser/android/java/strings/android_chrome_media_router_strings.grd b/components/media_router/browser/android/java/strings/android_chrome_media_router_strings.grd
index 00bc45f..65193ae 100644
--- a/components/media_router/browser/android/java/strings/android_chrome_media_router_strings.grd
+++ b/components/media_router/browser/android/java/strings/android_chrome_media_router_strings.grd
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- android_chrome_strings.grd contains strings for VR DFM of Chrome for Android. -->
-<grit current_release="1" latest_public_release="0" output_all_resource_defines="false">
+<grit current_release="1" latest_public_release="0">
   <outputs>
     <output filename="values-af/android_chrome_media_router_strings.xml" lang="af" type="android" />
     <output filename="values-am/android_chrome_media_router_strings.xml" lang="am" type="android" />
diff --git a/components/metrics/server_urls.grd b/components/metrics/server_urls.grd
index 21c7785a..c92980f 100644
--- a/components/metrics/server_urls.grd
+++ b/components/metrics/server_urls.grd
@@ -10,7 +10,7 @@
 rather than the code.
 -->
 
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/metrics_server_urls.h" type="rc_header">
       <emit emit_type="prepend"></emit>
diff --git a/components/omnibox/resources/omnibox_pedal_synonyms.grd b/components/omnibox/resources/omnibox_pedal_synonyms.grd
index 2d363bc..f0534099 100644
--- a/components/omnibox/resources/omnibox_pedal_synonyms.grd
+++ b/components/omnibox/resources/omnibox_pedal_synonyms.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
 
   <outputs>
     <output filename="grit/omnibox_pedal_synonyms.h" type="rc_header">
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index c37133f..d2dce19 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit c37133f0c4465e77570c88c93940ae58a3d35819
+Subproject commit d2dce193b98bc200e7b9d6c6894fe09fdcb16b39
diff --git a/components/optimization_guide/proto/model_quality_service.proto b/components/optimization_guide/proto/model_quality_service.proto
index 1d73200..9c376d31 100644
--- a/components/optimization_guide/proto/model_quality_service.proto
+++ b/components/optimization_guide/proto/model_quality_service.proto
@@ -22,6 +22,7 @@
 import "components/optimization_guide/proto/features/model_prototyping.proto";
 import "components/optimization_guide/proto/features/password_change_submission.proto";
 import "components/optimization_guide/proto/features/product_specifications.proto";
+import "components/optimization_guide/proto/features/scam_detection.proto";
 import "components/optimization_guide/proto/features/tab_organization.proto";
 import "components/optimization_guide/proto/features/wallpaper_search.proto";
 import "components/optimization_guide/proto/features/bling_prototyping.proto";
@@ -61,6 +62,8 @@
 
     PasswordChangeSubmissionLoggingData password_change_submission = 16;
 
+    ScamDetectionLoggingData scam_detection = 17;
+
     DefaultLoggingData default = 1000;
   }
 }
diff --git a/components/permissions/android/permissions_android_strings.grd b/components/permissions/android/permissions_android_strings.grd
index 4abcbed..ec8aaa6a 100644
--- a/components/permissions/android/permissions_android_strings.grd
+++ b/components/permissions/android/permissions_android_strings.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="values-af/permissions_android_strings.xml" lang="af" type="android" />
     <output filename="values-am/permissions_android_strings.xml" lang="am" type="android" />
diff --git a/components/policy/resources/policy_templates.build.grd b/components/policy/resources/policy_templates.build.grd
index c11f282..be5d20c 100644
--- a/components/policy/resources/policy_templates.build.grd
+++ b/components/policy/resources/policy_templates.build.grd
@@ -11,7 +11,7 @@
 -->
 
 <grit base_dir="." latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="app/policy/translations/policy_templates_de-DE.json" type="policy_templates" lang="de" />
     <output filename="app/policy/translations/policy_templates_en-US.json" type="policy_templates" lang="en" />
diff --git a/components/policy/resources/policy_templates.grd b/components/policy/resources/policy_templates.grd
index 50ac319..05d6572 100644
--- a/components/policy/resources/policy_templates.grd
+++ b/components/policy/resources/policy_templates.grd
@@ -15,7 +15,7 @@
 -->
 
 <grit base_dir="." latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="app/policy/translations/policy_templates_de-DE.json" type="policy_templates" lang="de" />
     <output filename="app/policy/translations/policy_templates_en-US.json" type="policy_templates" lang="en" />
diff --git a/components/privacy_sandbox/android/BUILD.gn b/components/privacy_sandbox/android/BUILD.gn
index 8a62fde..e2fa9ef 100644
--- a/components/privacy_sandbox/android/BUILD.gn
+++ b/components/privacy_sandbox/android/BUILD.gn
@@ -51,6 +51,7 @@
     ":java",
     "//base:base_java",
     "//base:base_java_test_support",
+    "//components/browser_ui/settings/android:java",
     "//components/browser_ui/settings/android:test_support_java",
     "//components/browser_ui/site_settings/android:java",
     "//components/content_settings/android:content_settings_enums_java",
@@ -62,6 +63,7 @@
     "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
     "//third_party/androidx:androidx_preference_preference_java",
     "//third_party/androidx:androidx_recyclerview_recyclerview_java",
+    "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/hamcrest:hamcrest_java",
     "//third_party/hamcrest:hamcrest_library_java",
diff --git a/components/privacy_sandbox/android/javatests/src/org/chromium/components/privacy_sandbox/TrackingProtectionSettingsTest.java b/components/privacy_sandbox/android/javatests/src/org/chromium/components/privacy_sandbox/TrackingProtectionSettingsTest.java
index 66a5aee..4b09689 100644
--- a/components/privacy_sandbox/android/javatests/src/org/chromium/components/privacy_sandbox/TrackingProtectionSettingsTest.java
+++ b/components/privacy_sandbox/android/javatests/src/org/chromium/components/privacy_sandbox/TrackingProtectionSettingsTest.java
@@ -5,18 +5,22 @@
 package org.chromium.components.privacy_sandbox;
 
 import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static org.chromium.ui.test.util.ViewUtils.clickOnClickableSpan;
+
 import android.content.Context;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.espresso.contrib.RecyclerViewActions;
 import androidx.test.filters.SmallTest;
 
@@ -26,6 +30,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import org.chromium.base.library_loader.LibraryLoader;
@@ -33,6 +38,7 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.components.browser_ui.settings.BlankUiTestActivitySettingsTestRule;
+import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher;
 import org.chromium.components.browser_ui.site_settings.SiteSettingsDelegate;
 import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge;
 import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridgeJni;
@@ -54,6 +60,8 @@
 
     @Mock private SiteSettingsDelegate mSiteSettingsDelegate;
 
+    @Mock private SettingsCustomTabLauncher mCustomTabLauncher;
+
     private TrackingProtectionSettings mFragment;
 
     @BeforeClass
@@ -79,13 +87,15 @@
                 (fragment) -> {
                     ((TrackingProtectionSettings) fragment)
                             .setTrackingProtectionDelegate(mDelegate);
+                    ((TrackingProtectionSettings) fragment)
+                            .setCustomTabLauncher(mCustomTabLauncher);
                 });
         mFragment = (TrackingProtectionSettings) mSettingsRule.getPreferenceFragment();
     }
 
     @Test
     @SmallTest
-    public void testShowTrackingProtectionRewindUi() {
+    public void launchTrackingProtectionPage() {
         when(mDelegate.isBlockAll3pcEnabled()).thenReturn(true);
         when(mDelegate.isDoNotTrackEnabled()).thenReturn(true);
 
@@ -97,7 +107,7 @@
 
     @Test
     @SmallTest
-    public void testIpFpProtectionsDisplayedInLaunchUi() {
+    public void launchWithIpAndFpProtection_extraContentDisplayed() {
         when(mDelegate.isBlockAll3pcEnabled()).thenReturn(true);
         when(mDelegate.isDoNotTrackEnabled()).thenReturn(true);
         when(mDelegate.shouldDisplayIpProtection()).thenReturn(true);
@@ -105,12 +115,66 @@
 
         launchTrackingProtectionSettings();
 
+        Context context = ApplicationProvider.getApplicationContext();
+        String fp_protection =
+                context.getString(R.string.tracking_protection_fingerprinting_protection_learn_more)
+                        .replaceAll("<link>|</link>", "");
+        String ip_protection =
+                context.getString(R.string.tracking_protection_ip_protection_learn_more)
+                        .replaceAll("<link>|</link>", "");
+        onView(withId(R.id.recycler_view))
+                .perform(RecyclerViewActions.scrollTo(hasDescendant(withText(fp_protection))));
+        onView(withText(ip_protection)).check(matches(isDisplayed()));
+        onView(withText(fp_protection)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @SmallTest
+    public void changeToggleValues_propagatedToBackend() {
+        when(mDelegate.isBlockAll3pcEnabled()).thenReturn(true);
+        when(mDelegate.isDoNotTrackEnabled()).thenReturn(true);
+        when(mDelegate.shouldDisplayIpProtection()).thenReturn(true);
+        when(mDelegate.shouldDisplayFingerprintingProtection()).thenReturn(true);
+        when(mDelegate.isIpProtectionEnabled()).thenReturn(false);
+        when(mDelegate.isFingerprintingProtectionEnabled()).thenReturn(false);
+
+        launchTrackingProtectionSettings();
+
+        onView(withText(R.string.tracking_protection_block_cookies_toggle_title)).perform(click());
+        verify(mDelegate).setBlockAll3pc(/* enabled= */ Mockito.eq(false));
+
         onView(withId(R.id.recycler_view))
                 .perform(
                         RecyclerViewActions.scrollTo(
-                                hasDescendant(withText(containsString("Learn how limiting")))));
-        onView(withText(containsString("Learn how IP protection"))).check(matches(isDisplayed()));
-        onView(withText(containsString("Learn how limiting digital")))
-                .check(matches(isDisplayed()));
+                                hasDescendant(
+                                        withText(
+                                                R.string
+                                                        .tracking_protection_fingerprinting_protection_title))));
+
+        onView(withText(R.string.tracking_protection_ip_protection_toggle_title)).perform(click());
+        verify(mDelegate).setIpProtection(/* enabled= */ Mockito.eq(true));
+        onView(withText(R.string.tracking_protection_fingerprinting_protection_title))
+                .perform(click());
+        verify(mDelegate).setFingerprintingProtection(/* enabled= */ Mockito.eq(true));
+    }
+
+    @Test
+    @SmallTest
+    public void clickOnLearnMore_cctIsOpened() {
+        when(mDelegate.isBlockAll3pcEnabled()).thenReturn(true);
+        when(mDelegate.isDoNotTrackEnabled()).thenReturn(true);
+
+        launchTrackingProtectionSettings();
+
+        Context context = ApplicationProvider.getApplicationContext();
+        String tp_learn_more =
+                context.getString(
+                                R.string.privacy_sandbox_tracking_protection_bullet_two_description)
+                        .replaceAll("<link>|</link>", "");
+        onView(withText(tp_learn_more)).perform(clickOnClickableSpan(/* spanIndex= */ 0));
+        verify(mCustomTabLauncher)
+                .openUrlInCct(
+                        /* context= */ Mockito.any(),
+                        /* url= */ Mockito.eq(TrackingProtectionSettings.LEARN_MORE_URL));
     }
 }
diff --git a/components/privacy_sandbox_strings.grd b/components/privacy_sandbox_strings.grd
index 7ae46df..57849ce 100644
--- a/components/privacy_sandbox_strings.grd
+++ b/components/privacy_sandbox_strings.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="grit/privacy_sandbox_strings.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/components/search_engine_descriptions_strings.grd b/components/search_engine_descriptions_strings.grd
index 8c18b5b..b1568f7 100644
--- a/components/search_engine_descriptions_strings.grd
+++ b/components/search_engine_descriptions_strings.grd
@@ -3,7 +3,7 @@
 locale. The strings in the file are not translated by the Chrome translation
 process but are generated using a script that pulls them from outside the repo.-->
 
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/search_engine_descriptions_strings.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/components/user_manager/fake_user_manager.cc b/components/user_manager/fake_user_manager.cc
index f6a06a8..6b69163 100644
--- a/components/user_manager/fake_user_manager.cc
+++ b/components/user_manager/fake_user_manager.cc
@@ -59,28 +59,6 @@
   return user;
 }
 
-UserList FakeUserManager::GetUsersAllowedForMultiUserSignIn() const {
-  UserList result;
-  for (UserList::const_iterator it = users_.begin(); it != users_.end(); ++it) {
-    if ((*it)->GetType() == UserType::kRegular && !(*it)->is_logged_in()) {
-      result.push_back(*it);
-    }
-  }
-  return result;
-}
-
-void FakeUserManager::UpdateUserAccountData(
-    const AccountId& account_id,
-    const UserAccountData& account_data) {
-  for (User* user : users_) {
-    if (user->GetAccountId() == account_id) {
-      user->set_display_name(account_data.display_name());
-      user->set_given_name(account_data.given_name());
-      return;
-    }
-  }
-}
-
 void FakeUserManager::LogoutAllUsers() {
   primary_user_ = nullptr;
   active_user_ = nullptr;
@@ -154,28 +132,6 @@
   }
 }
 
-void FakeUserManager::SaveUserDisplayName(const AccountId& account_id,
-                                          const std::u16string& display_name) {
-  for (UserList::iterator it = users_.begin(); it != users_.end(); ++it) {
-    if ((*it)->GetAccountId() == account_id) {
-      (*it)->set_display_name(display_name);
-      return;
-    }
-  }
-}
-
-const UserList& FakeUserManager::GetLRULoggedInUsers() const {
-  return users_;
-}
-
-UserList FakeUserManager::GetUnlockUsers() const {
-  return users_;
-}
-
-bool FakeUserManager::IsKnownUser(const AccountId& account_id) const {
-  return true;
-}
-
 bool FakeUserManager::IsUserNonCryptohomeDataEphemeral(
     const AccountId& account_id) const {
   return base::Contains(accounts_with_ephemeral_non_cryptohome_data_,
diff --git a/components/user_manager/fake_user_manager.h b/components/user_manager/fake_user_manager.h
index df6ccb4..4ca5ab40 100644
--- a/components/user_manager/fake_user_manager.h
+++ b/components/user_manager/fake_user_manager.h
@@ -54,33 +54,11 @@
                                       bool is_ephemeral);
 
   // UserManager overrides.
-  UserList GetUsersAllowedForMultiUserSignIn() const override;
-  void UpdateUserAccountData(const AccountId& account_id,
-                             const UserAccountData& account_data) override;
-
-  // Set the user as logged in.
   void UserLoggedIn(const AccountId& account_id,
                     const std::string& username_hash,
                     bool browser_restart,
                     bool is_child) override;
-
   void SwitchActiveUser(const AccountId& account_id) override;
-  void SaveUserDisplayName(const AccountId& account_id,
-                           const std::u16string& display_name) override;
-
-  // Not implemented.
-  void Shutdown() override {}
-  const UserList& GetLRULoggedInUsers() const override;
-  UserList GetUnlockUsers() const override;
-  void OnSessionStarted() override {}
-  bool IsKnownUser(const AccountId& account_id) const override;
-  void SaveUserOAuthStatus(const AccountId& account_id,
-                           User::OAuthTokenStatus oauth_token_status) override {
-  }
-  void SaveForceOnlineSignin(const AccountId& account_id,
-                             bool force_online_signin) override {}
-  void SaveUserDisplayEmail(const AccountId& account_id,
-                            const std::string& display_email) override {}
   bool IsUserNonCryptohomeDataEphemeral(
       const AccountId& account_id) const override;
   bool IsUserCryptohomeDataEphemeral(
diff --git a/components/webapps/browser/android/android_webapps_strings.grd b/components/webapps/browser/android/android_webapps_strings.grd
index 47a0e3e71..b0cca84 100644
--- a/components/webapps/browser/android/android_webapps_strings.grd
+++ b/components/webapps/browser/android/android_webapps_strings.grd
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="values-af/android_webapps_strings.xml" lang="af" type="android" />
     <output filename="values-am/android_webapps_strings.xml" lang="am" type="android" />
diff --git a/content/browser/devtools/protocol/target_handler.cc b/content/browser/devtools/protocol/target_handler.cc
index 0631d42..02677b3 100644
--- a/content/browser/devtools/protocol/target_handler.cc
+++ b/content/browser/devtools/protocol/target_handler.cc
@@ -1191,7 +1191,6 @@
     std::optional<int> top,
     std::optional<int> width,
     std::optional<int> height,
-    std::optional<std::string> window_state,
     std::optional<std::string> context_id,
     std::optional<bool> enable_begin_frame_control,
     std::optional<bool> new_window,
diff --git a/content/browser/devtools/protocol/target_handler.h b/content/browser/devtools/protocol/target_handler.h
index 5521f28..6715e2f4 100644
--- a/content/browser/devtools/protocol/target_handler.h
+++ b/content/browser/devtools/protocol/target_handler.h
@@ -119,7 +119,6 @@
                         std::optional<int> top,
                         std::optional<int> width,
                         std::optional<int> height,
-                        std::optional<std::string> window_state,
                         std::optional<std::string> context_id,
                         std::optional<bool> enable_begin_frame_control,
                         std::optional<bool> new_window,
diff --git a/content/browser/fenced_frame/fenced_frame.cc b/content/browser/fenced_frame/fenced_frame.cc
index c6cdb479..3901d22 100644
--- a/content/browser/fenced_frame/fenced_frame.cc
+++ b/content/browser/fenced_frame/fenced_frame.cc
@@ -218,6 +218,12 @@
   return nullptr;
 }
 
+bool FencedFrame::OnRenderFrameProxyVisibilityChanged(
+    RenderFrameProxyHost* render_frame_proxy_host,
+    blink::mojom::FrameVisibility visibility) {
+  return false;
+}
+
 FrameTree* FencedFrame::GetPictureInPictureOpenerFrameTree() {
   return nullptr;
 }
diff --git a/content/browser/fenced_frame/fenced_frame.h b/content/browser/fenced_frame/fenced_frame.h
index 66cfce7..0ea90b6 100644
--- a/content/browser/fenced_frame/fenced_frame.h
+++ b/content/browser/fenced_frame/fenced_frame.h
@@ -70,6 +70,9 @@
   void SetFocusedFrame(FrameTreeNode* node, SiteInstanceGroup* source) override;
   FrameTree* GetOwnedPictureInPictureFrameTree() override;
   FrameTree* GetPictureInPictureOpenerFrameTree() override;
+  bool OnRenderFrameProxyVisibilityChanged(
+      RenderFrameProxyHost* render_frame_proxy_host,
+      blink::mojom::FrameVisibility visibility) override;
 
   // Returns the devtools frame token of the fenced frame's inner FrameTree's
   // main frame.
diff --git a/content/browser/guest_page_holder_impl.cc b/content/browser/guest_page_holder_impl.cc
index 30fd66d3..6c8c488 100644
--- a/content/browser/guest_page_holder_impl.cc
+++ b/content/browser/guest_page_holder_impl.cc
@@ -5,6 +5,7 @@
 #include "content/browser/guest_page_holder_impl.h"
 
 #include "base/notimplemented.h"
+#include "content/browser/renderer_host/cross_process_frame_connector.h"
 #include "content/browser/renderer_host/frame_tree.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -301,4 +302,25 @@
   return &guest_page->frame_tree();
 }
 
+bool GuestPageHolderImpl::OnRenderFrameProxyVisibilityChanged(
+    RenderFrameProxyHost* render_frame_proxy_host,
+    blink::mojom::FrameVisibility visibility) {
+  CHECK(base::FeatureList::IsEnabled(features::kGuestViewMPArch));
+
+  if (render_frame_proxy_host->frame_tree_node() != frame_tree_.root()) {
+    return false;
+  }
+  const bool hidden_with_parent_state =
+      render_frame_proxy_host->cross_process_frame_connector()->IsHidden() ||
+      render_frame_proxy_host->cross_process_frame_connector()
+              ->EmbedderVisibility() != Visibility::VISIBLE;
+  frame_tree_.ForEachRenderViewHost([hidden_with_parent_state](
+                                        RenderViewHostImpl* rvh) {
+    rvh->SetFrameTreeVisibility(
+        hidden_with_parent_state ? blink::mojom::PageVisibilityState::kHidden
+                                 : blink::mojom::PageVisibilityState::kVisible);
+  });
+  return false;
+}
+
 }  // namespace content
diff --git a/content/browser/guest_page_holder_impl.h b/content/browser/guest_page_holder_impl.h
index e168b05..68c5c56 100644
--- a/content/browser/guest_page_holder_impl.h
+++ b/content/browser/guest_page_holder_impl.h
@@ -58,6 +58,9 @@
   void SetFocusedFrame(FrameTreeNode* node, SiteInstanceGroup* source) override;
   FrameTree* GetOwnedPictureInPictureFrameTree() override;
   FrameTree* GetPictureInPictureOpenerFrameTree() override;
+  bool OnRenderFrameProxyVisibilityChanged(
+      RenderFrameProxyHost* render_frame_proxy_host,
+      blink::mojom::FrameVisibility visibility) override;
 
   // NavigationControllerDelegate implementation.
   void NotifyNavigationStateChangedFromController(
diff --git a/content/browser/preloading/prerender/prerender_host.cc b/content/browser/preloading/prerender/prerender_host.cc
index 68225dae..bb604ae7 100644
--- a/content/browser/preloading/prerender/prerender_host.cc
+++ b/content/browser/preloading/prerender/prerender_host.cc
@@ -368,6 +368,12 @@
   return nullptr;
 }
 
+bool PrerenderHost::OnRenderFrameProxyVisibilityChanged(
+    RenderFrameProxyHost* render_frame_proxy_host,
+    blink::mojom::FrameVisibility visibility) {
+  return false;
+}
+
 FrameTreeNodeId PrerenderHost::GetOuterDelegateFrameTreeNodeId() {
   // A prerendered FrameTree is not "inner to" or "nested inside" another
   // FrameTree; it exists in parallel to the primary FrameTree of the current
diff --git a/content/browser/preloading/prerender/prerender_host.h b/content/browser/preloading/prerender/prerender_host.h
index 5e22941..11547c6 100644
--- a/content/browser/preloading/prerender/prerender_host.h
+++ b/content/browser/preloading/prerender/prerender_host.h
@@ -208,6 +208,9 @@
   void SetFocusedFrame(FrameTreeNode* node, SiteInstanceGroup* source) override;
   FrameTree* GetOwnedPictureInPictureFrameTree() override;
   FrameTree* GetPictureInPictureOpenerFrameTree() override;
+  bool OnRenderFrameProxyVisibilityChanged(
+      RenderFrameProxyHost* render_frame_proxy_host,
+      blink::mojom::FrameVisibility visibility) override;
 
   // NavigationControllerDelegate
   void NotifyNavigationStateChangedFromController(
diff --git a/content/browser/renderer_host/cross_process_frame_connector.cc b/content/browser/renderer_host/cross_process_frame_connector.cc
index adf0e25..b19e810 100644
--- a/content/browser/renderer_host/cross_process_frame_connector.cc
+++ b/content/browser/renderer_host/cross_process_frame_connector.cc
@@ -380,8 +380,11 @@
   // the visibility. The Show/Hide methods will not be called if an inner
   // WebContents exists since the corresponding WebContents will itself call
   // Show/Hide on all the RenderWidgetHostViews (including this) one.
-  if (view_->host()->delegate()->OnRenderFrameProxyVisibilityChanged(
-          frame_proxy_in_parent_renderer_, visibility_)) {
+  if (view_->host()
+          ->frame_tree()
+          ->delegate()
+          ->OnRenderFrameProxyVisibilityChanged(frame_proxy_in_parent_renderer_,
+                                                visibility_)) {
     return;
   }
 
@@ -602,22 +605,26 @@
 }
 
 bool CrossProcessFrameConnector::IsVisible() {
-  if (visibility_ == blink::mojom::FrameVisibility::kNotRendered)
+  if (visibility_ == blink::mojom::FrameVisibility::kNotRendered ||
+      intersection_state().viewport_intersection.IsEmpty()) {
     return false;
-  if (intersection_state().viewport_intersection.IsEmpty())
-    return false;
+  }
 
-  if (!current_child_frame_host())
+  if (!current_child_frame_host()) {
     return true;
+  }
 
-  Visibility embedder_visibility =
-      current_child_frame_host()->delegate()->GetVisibility();
-  if (embedder_visibility != Visibility::VISIBLE)
+  if (EmbedderVisibility() != Visibility::VISIBLE) {
     return false;
+  }
 
   return true;
 }
 
+Visibility CrossProcessFrameConnector::EmbedderVisibility() {
+  return current_child_frame_host()->delegate()->GetVisibility();
+}
+
 RenderFrameHostImpl* CrossProcessFrameConnector::current_child_frame_host()
     const {
   return frame_proxy_in_parent_renderer_
diff --git a/content/browser/renderer_host/cross_process_frame_connector.h b/content/browser/renderer_host/cross_process_frame_connector.h
index 4ab5c6c..264397ff 100644
--- a/content/browser/renderer_host/cross_process_frame_connector.h
+++ b/content/browser/renderer_host/cross_process_frame_connector.h
@@ -15,6 +15,7 @@
 #include "components/viz/common/surfaces/local_surface_id.h"
 #include "components/viz/common/surfaces/surface_id.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/visibility.h"
 #include "third_party/blink/public/common/frame/frame_visual_properties.h"
 #include "third_party/blink/public/mojom/frame/intrinsic_sizing_info.mojom-forward.h"
 #include "third_party/blink/public/mojom/frame/lifecycle.mojom.h"
@@ -323,6 +324,9 @@
     child_frame_crash_shown_closure_for_testing_ = std::move(closure);
   }
 
+  // Returns the embedder's visibility.
+  Visibility EmbedderVisibility();
+
  protected:
   friend class MockCrossProcessFrameConnector;
   friend class SitePerProcessBrowserTestBase;
diff --git a/content/browser/renderer_host/frame_tree.h b/content/browser/renderer_host/frame_tree.h
index 0765e42..d14928c 100644
--- a/content/browser/renderer_host/frame_tree.h
+++ b/content/browser/renderer_host/frame_tree.h
@@ -47,6 +47,7 @@
 class BrowserContext;
 class PageDelegate;
 class RenderFrameHostDelegate;
+class RenderFrameProxyHost;
 class RenderViewHostDelegate;
 class RenderViewHostImpl;
 class RenderFrameHostManager;
@@ -199,6 +200,16 @@
     // Returns this FrameTree's opener if this FrameTree represents a
     // picture-in-picture window.
     virtual FrameTree* GetPictureInPictureOpenerFrameTree() = 0;
+
+    // Called when the visibility of the RenderFrameProxyHost changes.
+    // This method should only handle visibility for inner WebContents and
+    // will eventually notify all the RenderWidgetHostViews belonging to that
+    // WebContents. If this is not an inner WebContents or the inner WebContents
+    // FrameTree root does not match `render_frame_proxy_host` FrameTreeNode it
+    // should return false.
+    virtual bool OnRenderFrameProxyVisibilityChanged(
+        RenderFrameProxyHost* render_frame_proxy_host,
+        blink::mojom::FrameVisibility visibility) = 0;
   };
 
   // Type of FrameTree instance.
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index ac8f86a0..429e12a 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -3978,10 +3978,11 @@
     if (previous_rfh) {
       // When migrating a frame to a new/different render process, use the frame
       // size we already have from the existing RenderFrameHost.
-      if (params->widget_params->visual_properties.new_size_device_px
-              .IsZero()) {
-        params->widget_params->visual_properties.new_size_device_px =
-            previous_rfh->GetFrameSize().value_or(gfx::Size());
+      if (params->widget_params->visual_properties.new_size.IsZero()) {
+        float dsf = rwh->GetScreenInfo().device_scale_factor;
+        params->widget_params->visual_properties.new_size =
+            gfx::ScaleToRoundedSize(
+                previous_rfh->GetFrameSize().value_or(gfx::Size()), 1.f / dsf);
       }
 
       params->widget_params->reuse_compositor =
diff --git a/content/browser/renderer_host/render_widget_host_browsertest.cc b/content/browser/renderer_host/render_widget_host_browsertest.cc
index 3e45ef21..c147ddf 100644
--- a/content/browser/renderer_host/render_widget_host_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_browsertest.cc
@@ -912,12 +912,9 @@
   WaitForVisualPropertiesAck();
   EXPECT_EQ(base::NumberToString(offset) + "px",
             EvalJs(shell(), "getComputedStyle(video).width").ExtractString());
-  // Rounding of GetVisibleViewportSize in the presence of a non-integer
-  // devicePixelRatio device can make this off by one vs the video height.
-  EXPECT_NEAR(
+  EXPECT_EQ(
       root_view_size.height(),
-      EvalJs(shell(), "parseInt(getComputedStyle(video).height)").ExtractInt(),
-      1);
+      EvalJs(shell(), "parseInt(getComputedStyle(video).height)").ExtractInt());
 
   emulated_display_feature.orientation =
       DisplayFeature::Orientation::kHorizontal;
@@ -928,26 +925,21 @@
   WaitForVisualPropertiesAck();
   EXPECT_EQ(base::NumberToString(offset) + "px",
             EvalJs(shell(), "getComputedStyle(video).height").ExtractString());
-  EXPECT_NEAR(
+  EXPECT_EQ(
       root_view_size.width(),
-      EvalJs(shell(), "parseInt(getComputedStyle(video).width)").ExtractInt(),
-      1);
+      EvalJs(shell(), "parseInt(getComputedStyle(video).width)").ExtractInt());
 
   // No display feature/viewport segments are set, the video should go
   // fullscreen.
   view()->SetDisplayFeatureForTesting(nullptr);
   host()->SynchronizeVisualProperties();
   WaitForVisualPropertiesAck();
-  // Rounding of GetVisibleViewportSize in the presence of a non-integer
-  // devicePixelRatio device can make this off by one vs the video height.
-  EXPECT_NEAR(
+  EXPECT_EQ(
       root_view_size.height(),
-      EvalJs(shell(), "parseInt(getComputedStyle(video).height)").ExtractInt(),
-      1);
-  EXPECT_NEAR(
+      EvalJs(shell(), "parseInt(getComputedStyle(video).height)").ExtractInt());
+  EXPECT_EQ(
       root_view_size.width(),
-      EvalJs(shell(), "parseInt(getComputedStyle(video).width)").ExtractInt(),
-      1);
+      EvalJs(shell(), "parseInt(getComputedStyle(video).width)").ExtractInt());
 
   constexpr char kExitFullscreenScript[] = R"JS(
     document.exitFullscreen().then(() => {
@@ -964,10 +956,9 @@
   WaitForVisualPropertiesAck();
   EXPECT_EQ(base::NumberToString(offset) + "px",
             EvalJs(shell(), "getComputedStyle(video).height").ExtractString());
-  EXPECT_NEAR(
+  EXPECT_EQ(
       root_view_size.width(),
-      EvalJs(shell(), "parseInt(getComputedStyle(video).width)").ExtractInt(),
-      1);
+      EvalJs(shell(), "parseInt(getComputedStyle(video).width)").ExtractInt());
 }
 
 // Tests that the renderer receives the root widget's viewport segments and
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index d0135cf..da659cff 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -1051,8 +1051,7 @@
   visual_properties.min_size_for_auto_resize = min_size_for_auto_resize_;
   visual_properties.max_size_for_auto_resize = max_size_for_auto_resize_;
 
-  visual_properties.new_size_device_px =
-      view_->GetRequestedRendererSizeDevicePx();
+  visual_properties.new_size = view_->GetRequestedRendererSize();
 
   // This widget is for a frame that is the main frame of the outermost frame
   // tree. That makes it the top-most frame. OR this is a non-frame widget.
@@ -1266,9 +1265,9 @@
     blink_widget_->UpdateVisualProperties(*visual_properties);
   }
 
-  bool width_changed = !old_visual_properties_ ||
-                       old_visual_properties_->new_size_device_px.width() !=
-                           visual_properties->new_size_device_px.width();
+  bool width_changed =
+      !old_visual_properties_ || old_visual_properties_->new_size.width() !=
+                                     visual_properties->new_size.width();
 
   // WidgetBase::UpdateSurfaceAndScreenInfo uses similar logic to detect
   // orientation changes on the display currently showing the widget.
@@ -2817,8 +2816,7 @@
            old_visual_properties.max_size_for_auto_resize !=
                new_visual_properties.max_size_for_auto_resize)) ||
          (!old_visual_properties.auto_resize_enabled &&
-          (old_visual_properties.new_size_device_px !=
-               new_visual_properties.new_size_device_px ||
+          (old_visual_properties.new_size != new_visual_properties.new_size ||
            (old_visual_properties.compositor_viewport_pixel_rect.IsEmpty() &&
             !new_visual_properties.compositor_viewport_pixel_rect.IsEmpty())));
 }
@@ -2834,7 +2832,7 @@
   bool is_acking_applicable =
       g_check_for_pending_visual_properties_ack &&
       !new_visual_properties.auto_resize_enabled &&
-      !new_visual_properties.new_size_device_px.IsEmpty() &&
+      !new_visual_properties.new_size.IsEmpty() &&
       !new_visual_properties.compositor_viewport_pixel_rect.IsEmpty() &&
       new_visual_properties.local_surface_id;
 
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 4740713..d5d80035 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -970,8 +970,7 @@
   view_->SetMockCompositorViewportPixelSize(gfx::Size());
   host_->SynchronizeVisualProperties();
   EXPECT_FALSE(host_->visual_properties_ack_pending_);
-  EXPECT_EQ(original_size.size(),
-            host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(original_size.size(), host_->old_visual_properties_->new_size);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, widget_.ReceivedVisualProperties().size());
 
@@ -982,8 +981,7 @@
   view_->ClearMockCompositorViewportPixelSize();
   host_->SynchronizeVisualProperties();
   EXPECT_TRUE(host_->visual_properties_ack_pending_);
-  EXPECT_EQ(original_size.size(),
-            host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(original_size.size(), host_->old_visual_properties_->new_size);
   cc::RenderFrameMetadata metadata;
   metadata.viewport_size_in_pixels = original_size.size();
   metadata.local_surface_id = std::nullopt;
@@ -1023,8 +1021,7 @@
   static_cast<RenderFrameMetadataProvider::Observer&>(*host_)
       .OnLocalSurfaceIdChanged(metadata);
   EXPECT_TRUE(host_->visual_properties_ack_pending_);
-  EXPECT_EQ(third_size.size(),
-            host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(third_size.size(), host_->old_visual_properties_->new_size);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, widget_.ReceivedVisualProperties().size());
 
@@ -1036,8 +1033,7 @@
   static_cast<RenderFrameMetadataProvider::Observer&>(*host_)
       .OnLocalSurfaceIdChanged(metadata);
   EXPECT_FALSE(host_->visual_properties_ack_pending_);
-  EXPECT_EQ(third_size.size(),
-            host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(third_size.size(), host_->old_visual_properties_->new_size);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0u, widget_.ReceivedVisualProperties().size());
 
@@ -1050,7 +1046,7 @@
   view_->SetBounds(gfx::Rect());
   host_->SynchronizeVisualProperties();
   EXPECT_FALSE(host_->visual_properties_ack_pending_);
-  EXPECT_EQ(gfx::Size(), host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(gfx::Size(), host_->old_visual_properties_->new_size);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, widget_.ReceivedVisualProperties().size());
 
@@ -1060,8 +1056,7 @@
   view_->SetBounds(gfx::Rect(0, 0, 0, 30));
   host_->SynchronizeVisualProperties();
   EXPECT_FALSE(host_->visual_properties_ack_pending_);
-  EXPECT_EQ(gfx::Size(0, 30),
-            host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(gfx::Size(0, 30), host_->old_visual_properties_->new_size);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, widget_.ReceivedVisualProperties().size());
 
@@ -1070,8 +1065,7 @@
   // Set the same size again. It should not be sent again.
   host_->SynchronizeVisualProperties();
   EXPECT_FALSE(host_->visual_properties_ack_pending_);
-  EXPECT_EQ(gfx::Size(0, 30),
-            host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(gfx::Size(0, 30), host_->old_visual_properties_->new_size);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0u, widget_.ReceivedVisualProperties().size());
 
@@ -1081,8 +1075,7 @@
   view_->SetBounds(gfx::Rect(0, 0, 0, 31));
   host_->SynchronizeVisualProperties();
   EXPECT_FALSE(host_->visual_properties_ack_pending_);
-  EXPECT_EQ(gfx::Size(0, 31),
-            host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(gfx::Size(0, 31), host_->old_visual_properties_->new_size);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, widget_.ReceivedVisualProperties().size());
 
@@ -1094,8 +1087,7 @@
   view_->InvalidateLocalSurfaceId();
   host_->SynchronizeVisualProperties();
   EXPECT_FALSE(host_->visual_properties_ack_pending_);
-  EXPECT_EQ(gfx::Size(25, 25),
-            host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(gfx::Size(25, 25), host_->old_visual_properties_->new_size);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, widget_.ReceivedVisualProperties().size());
 }
@@ -1154,31 +1146,6 @@
   EXPECT_FALSE(host_->visual_properties_ack_pending_);
 }
 
-// Test that the reported new_size includes the scale factor.
-TEST_F(RenderWidgetHostTest, NewSizeIncludesScaleFactor) {
-  display::ScreenInfo screen_info;
-  screen_info.rect = gfx::Rect(0, 0, 800, 600);
-  screen_info.available_rect = gfx::Rect(0, 0, 800, 600);
-  screen_info.orientation_type =
-      display::mojom::ScreenOrientation::kPortraitPrimary;
-  screen_info.device_scale_factor = 2.f;
-
-  ClearVisualProperties();
-  view_->SetScreenInfo(screen_info);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(0u, widget_.ReceivedVisualProperties().size());
-  gfx::Rect original_size(0, 0, 101, 100);
-  view_->SetBounds(original_size);
-  EXPECT_TRUE(host_->SynchronizeVisualProperties());
-  // blink::mojom::Widget::UpdateVisualProperties sent to the renderer.
-  base::RunLoop().RunUntilIdle();
-  ASSERT_EQ(1u, widget_.ReceivedVisualProperties().size());
-  auto new_size = widget_.ReceivedVisualProperties()[0].new_size_device_px;
-  EXPECT_EQ(202, new_size.width());
-  EXPECT_EQ(200, new_size.height());
-  EXPECT_TRUE(host_->visual_properties_ack_pending_);
-}
-
 // Ensure VisualProperties continues reporting the size of the current screen,
 // not the viewport, when the frame is fullscreen. See crbug.com/1367416.
 TEST_F(RenderWidgetHostTest, ScreenSizeInFullscreen) {
@@ -1204,7 +1171,7 @@
   blink::VisualProperties props = widget_.ReceivedVisualProperties().at(0);
   EXPECT_EQ(kScreenBounds, props.screen_infos.current().rect);
   EXPECT_EQ(kScreenBounds, props.screen_infos.current().available_rect);
-  EXPECT_EQ(kViewBounds.size(), props.new_size_device_px);
+  EXPECT_EQ(kViewBounds.size(), props.new_size);
 
   // Enter fullscreen and do another VisualProperties sync.
   delegate_->set_is_fullscreen(true);
@@ -1215,7 +1182,7 @@
   props = widget_.ReceivedVisualProperties().at(1);
   EXPECT_EQ(kScreenBounds, props.screen_infos.current().rect);
   EXPECT_EQ(kScreenBounds, props.screen_infos.current().available_rect);
-  EXPECT_EQ(kViewBounds.size(), props.new_size_device_px);
+  EXPECT_EQ(kViewBounds.size(), props.new_size);
 
   // Exit fullscreen and do another VisualProperties sync.
   delegate_->set_is_fullscreen(false);
@@ -1226,7 +1193,7 @@
   props = widget_.ReceivedVisualProperties().at(2);
   EXPECT_EQ(kScreenBounds, props.screen_infos.current().rect);
   EXPECT_EQ(kScreenBounds, props.screen_infos.current().available_rect);
-  EXPECT_EQ(kViewBounds.size(), props.new_size_device_px);
+  EXPECT_EQ(kViewBounds.size(), props.new_size);
 }
 
 TEST_F(RenderWidgetHostTest, RootViewportSegments) {
@@ -2182,7 +2149,7 @@
       compositor_viewport_pixel_rect.size());
 
   blink::VisualProperties visual_properties = host_->GetVisualProperties();
-  EXPECT_EQ(bounds.size(), visual_properties.new_size_device_px);
+  EXPECT_EQ(bounds.size(), visual_properties.new_size);
   EXPECT_EQ(compositor_viewport_pixel_rect,
             visual_properties.compositor_viewport_pixel_rect);
 }
@@ -2273,7 +2240,7 @@
   // SynchronizeVisualProperties calls should not result in new IPC (unless the
   // size has actually changed).
   EXPECT_FALSE(host_->SynchronizeVisualProperties());
-  EXPECT_EQ(initial_size_, host_->old_visual_properties_->new_size_device_px);
+  EXPECT_EQ(initial_size_, host_->old_visual_properties_->new_size);
   EXPECT_TRUE(host_->visual_properties_ack_pending_);
 }
 
@@ -2287,7 +2254,7 @@
   {
     // Size sent to the renderer.
     EXPECT_EQ(gfx::Size(100, 100),
-              widget_.ReceivedVisualProperties().at(0).new_size_device_px);
+              widget_.ReceivedVisualProperties().at(0).new_size);
   }
   // An ack is pending, throttling further updates.
   EXPECT_TRUE(host_->visual_properties_ack_pending_);
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index dd0c7c97..d72c1f4f 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -353,7 +353,7 @@
 
 void RenderWidgetHostViewAndroid::ScreenStateChangeHandler::
     BeginScreenStateChange() {
-  current_screen_state_.visible_viewport_size = rwhva_->view_.GetSizeDIPs();
+  current_screen_state_.visible_viewport_size = rwhva_->view_.GetSize();
   current_screen_state_.physical_backing_size =
       rwhva_->view_.GetPhysicalBackingSize();
   auto screen_info = rwhva_->GetScreenInfo();
@@ -1185,24 +1185,11 @@
   if (!view_.parent())
     return default_bounds_dip_;
 
-  gfx::Size size(view_.GetSizeDIPs());
+  gfx::Size size(view_.GetSize());
+
   return gfx::Rect(size);
 }
 
-gfx::Size RenderWidgetHostViewAndroid::GetRequestedRendererSizeDevicePx() {
-  if (!view_.parent()) {
-    if (default_bounds_dip_.IsEmpty()) {
-      return gfx::Size();
-    }
-
-    const float scale_factor = GetDeviceScaleFactor();
-    return gfx::Size(default_bounds_dip_.width() * scale_factor,
-                     default_bounds_dip_.height() * scale_factor);
-  }
-
-  return view_.GetSizeDevicePx();
-}
-
 gfx::Size RenderWidgetHostViewAndroid::GetVisibleViewportSize() {
   int pinned_bottom_adjust_dps =
       std::max(0, (int)(view_.GetViewportInsetBottom() / view_.GetDipScale()));
@@ -2484,8 +2471,8 @@
     // TODO(yusufo) : Get rid of the below conditions and have a better handling
     // for resizing after crbug.com/628302 is handled.
     bool is_size_initialized = !will_build_tree ||
-                               view_.GetSizeDIPs().width() != 0 ||
-                               view_.GetSizeDIPs().height() != 0;
+                               view_.GetSize().width() != 0 ||
+                               view_.GetSize().height() != 0;
     if (has_view_tree || is_size_initialized)
       resize = true;
     has_view_tree = will_build_tree;
@@ -2609,8 +2596,7 @@
 }
 
 void RenderWidgetHostViewAndroid::OnSizeChanged() {
-  screen_state_change_handler_.OnVisibleViewportSizeChanged(
-      view_.GetSizeDIPs());
+  screen_state_change_handler_.OnVisibleViewportSizeChanged(view_.GetSize());
   // The display feature depends on the view size so we need to recompute it.
   ComputeDisplayFeature();
 }
@@ -2960,7 +2946,7 @@
   }
 
   display_feature_ = std::nullopt;
-  gfx::Size view_size(view_.GetSizeDIPs());
+  gfx::Size view_size(view_.GetSize());
   // On some devices like the Galaxy Fold the display feature has a size of
   // 0 (width or height depending on the orientation). IsEmpty() will fail here.
   if (display_feature_bounds_.size().IsZero() || view_size.IsEmpty()) {
diff --git a/content/browser/renderer_host/render_widget_host_view_android.h b/content/browser/renderer_host/render_widget_host_view_android.h
index f74e5e4..ef7bbda 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.h
+++ b/content/browser/renderer_host/render_widget_host_view_android.h
@@ -155,7 +155,6 @@
   void Hide() override;
   bool IsShowing() override;
   gfx::Rect GetViewBounds() override;
-  gfx::Size GetRequestedRendererSizeDevicePx() override;
   gfx::Size GetVisibleViewportSize() override;
   void SetInsets(const gfx::Insets& insets) override;
   gfx::Size GetCompositorViewportPixelSize() override;
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index d695762a..bdb00be 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -2616,7 +2616,7 @@
 
   view_->SetSize(gfx::Size(100, 100));
 
-  // Device pixel size.
+  // Physical pixel size.
   EXPECT_EQ(gfx::Size(100, 100), view_->GetCompositorViewportPixelSize());
   // Update to the renderer.
   base::RunLoop().RunUntilIdle();
@@ -2624,9 +2624,9 @@
   {
     blink::VisualProperties visual_properties =
         widget_host_->visual_properties().at(0);
-    // Device pixel size.
-    EXPECT_EQ(gfx::Size(100, 100), visual_properties.new_size_device_px);
-    // Device pixel size.
+    // DIP size.
+    EXPECT_EQ(gfx::Size(100, 100), visual_properties.new_size);
+    // Physical pixel size.
     EXPECT_EQ(gfx::Size(100, 100),
               visual_properties.compositor_viewport_pixel_rect.size());
   }
@@ -2641,11 +2641,11 @@
   sink_->ClearMessages();
   widget_host_->ClearVisualProperties();
 
-  // Device scale factor changes to 2, so the device pixel sizes should
+  // Device scale factor changes to 2, so the physical pixel sizes should
   // change, while the DIP sizes do not.
 
   aura_test_helper_->GetTestScreen()->SetDeviceScaleFactor(2.0f);
-  // Device pixel size.
+  // Physical pixel size.
   EXPECT_EQ(gfx::Size(200, 200), view_->GetCompositorViewportPixelSize());
   // Update to the renderer.
   base::RunLoop().RunUntilIdle();
@@ -2653,9 +2653,9 @@
   {
     blink::VisualProperties visual_properties =
         widget_host_->visual_properties().at(0);
-    // Device pixel size.
-    EXPECT_EQ(gfx::Size(200, 200), visual_properties.new_size_device_px);
-    // Device pixel size.
+    // DIP size.
+    EXPECT_EQ(gfx::Size(100, 100), visual_properties.new_size);
+    // Physical pixel size.
     EXPECT_EQ(gfx::Size(200, 200),
               visual_properties.compositor_viewport_pixel_rect.size());
   }
@@ -2671,7 +2671,7 @@
 
   aura_test_helper_->GetTestScreen()->SetDeviceScaleFactor(1.0f);
 
-  // Device pixel size.
+  // Physical pixel size.
   EXPECT_EQ(gfx::Size(100, 100), view_->GetCompositorViewportPixelSize());
   // Update to the renderer.
   base::RunLoop().RunUntilIdle();
@@ -2680,8 +2680,8 @@
     blink::VisualProperties visual_properties =
         widget_host_->visual_properties().at(0);
     // DIP size.
-    EXPECT_EQ(gfx::Size(100, 100), visual_properties.new_size_device_px);
-    // Device pixel size.
+    EXPECT_EQ(gfx::Size(100, 100), visual_properties.new_size);
+    // Physical pixel size.
     EXPECT_EQ(gfx::Size(100, 100),
               visual_properties.compositor_viewport_pixel_rect.size());
   }
@@ -2780,7 +2780,7 @@
   EXPECT_EQ(1u, widget_host_->visual_properties().size());
   const auto& received_property = widget_host_->visual_properties()[0];
   EXPECT_EQ(false, received_property.auto_resize_enabled);
-  EXPECT_EQ(size_after_disabling, received_property.new_size_device_px);
+  EXPECT_EQ(size_after_disabling, received_property.new_size);
 }
 
 // This test verifies that in AutoResize mode a new
@@ -2837,7 +2837,7 @@
     // Auto-resizve limits sent to the renderer.
     EXPECT_EQ(gfx::Size(50, 50), visual_properties.min_size_for_auto_resize);
     EXPECT_EQ(gfx::Size(100, 100), visual_properties.max_size_for_auto_resize);
-    EXPECT_EQ(gfx::Size(120, 120), visual_properties.new_size_device_px);
+    EXPECT_EQ(gfx::Size(120, 120), visual_properties.new_size);
     EXPECT_EQ(1, visual_properties.screen_infos.current().device_scale_factor);
     // A newly generated LocalSurfaceId is sent.
     EXPECT_TRUE(visual_properties.local_surface_id.has_value());
@@ -3098,7 +3098,7 @@
     blink::VisualProperties visual_properties =
         widget_host_->visual_properties().at(0);
     // Empty size is sent.
-    EXPECT_EQ(gfx::Size(), visual_properties.new_size_device_px);
+    EXPECT_EQ(gfx::Size(), visual_properties.new_size);
     // A LocalSurfaceId is sent too.
     ASSERT_TRUE(visual_properties.local_surface_id.has_value());
     EXPECT_TRUE(visual_properties.local_surface_id->is_valid());
@@ -3175,7 +3175,7 @@
   EXPECT_TRUE(widget_host_->visual_properties_ack_pending_for_testing());
   base::RunLoop().RunUntilIdle();
   ASSERT_EQ(1u, widget_host_->visual_properties().size());
-  EXPECT_EQ(size2, widget_host_->visual_properties().at(0).new_size_device_px);
+  EXPECT_EQ(size2, widget_host_->visual_properties().at(0).new_size);
   // Render should send back RenderFrameMetadata with new size.
   {
     cc::RenderFrameMetadata metadata;
@@ -3433,7 +3433,7 @@
   {
     blink::VisualProperties visual_properties =
         widget_host_->visual_properties().at(0);
-    EXPECT_EQ(gfx::Size(100, 100), visual_properties.new_size_device_px);
+    EXPECT_EQ(gfx::Size(100, 100), visual_properties.new_size);
     EXPECT_EQ(gfx::Size(100, 100), visual_properties.visible_viewport_size);
   }
 
@@ -3455,7 +3455,7 @@
   {
     blink::VisualProperties visual_properties =
         widget_host_->visual_properties().at(0);
-    EXPECT_EQ(gfx::Size(100, 100), visual_properties.new_size_device_px);
+    EXPECT_EQ(gfx::Size(100, 100), visual_properties.new_size);
     EXPECT_EQ(gfx::Size(100, 60), visual_properties.visible_viewport_size);
   }
 }
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index 51f8361..2fb637bb 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -140,11 +140,6 @@
   return GetViewBounds().size();
 }
 
-gfx::Size RenderWidgetHostViewBase::GetRequestedRendererSizeDevicePx() {
-  return gfx::ScaleToCeiledSize(GetRequestedRendererSize(),
-                                GetDeviceScaleFactor());
-}
-
 uint32_t RenderWidgetHostViewBase::GetCaptureSequenceNumber() const {
   // TODO(vmpstr): Implement this for overrides other than aura and child frame.
   NOTIMPLEMENTED_LOG_ONCE();
@@ -569,7 +564,7 @@
 void RenderWidgetHostViewBase::ResetGestureDetection() {}
 
 float RenderWidgetHostViewBase::GetDeviceScaleFactor() const {
-  return GetScreenInfos().current().device_scale_factor;
+  return screen_infos_.current().device_scale_factor;
 }
 
 base::WeakPtr<input::RenderWidgetHostViewInput>
diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h
index 2a2edc60..8377a897 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -236,7 +236,6 @@
   // The requested size of the renderer. May differ from GetViewBounds().size()
   // when the view requires additional throttling.
   virtual gfx::Size GetRequestedRendererSize();
-  virtual gfx::Size GetRequestedRendererSizeDevicePx();
 
   // Returns the current capture sequence number.
   virtual uint32_t GetCaptureSequenceNumber() const;
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc b/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc
index da43859..2c733a7 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc
@@ -324,8 +324,7 @@
 
     EXPECT_EQ(compositor_viewport_pixel_rect,
               sent_visual_properties.compositor_viewport_pixel_rect);
-    EXPECT_EQ(rect_in_local_root.size(),
-              sent_visual_properties.new_size_device_px);
+    EXPECT_EQ(rect_in_local_root.size(), sent_visual_properties.new_size);
     EXPECT_EQ(local_surface_id, sent_visual_properties.local_surface_id);
     EXPECT_EQ(123u, sent_visual_properties.capture_sequence_number);
     EXPECT_EQ(1u, sent_visual_properties.root_widget_viewport_segments.size());
diff --git a/content/browser/renderer_host/scroll_into_view_browsertest.cc b/content/browser/renderer_host/scroll_into_view_browsertest.cc
index b9bb46d..51ab2bf 100644
--- a/content/browser/renderer_host/scroll_into_view_browsertest.cc
+++ b/content/browser/renderer_host/scroll_into_view_browsertest.cc
@@ -796,7 +796,7 @@
   RunTest();
 
   // width=device-width must prevent the zooming behavior.
-  EXPECT_LE(kMobileMinimumScale, GetVisualViewport().scale);
+  EXPECT_EQ(kMobileMinimumScale, GetVisualViewport().scale);
 }
 
 // Similar to above, an input in a touch-action region that disables pinch-zoom
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 54f24878..47afc760 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -9427,7 +9427,7 @@
   // Avoid having the root try to handle the following event.
   root_view->set_event_handler(nullptr);
 
-  auto size = root_view->GetSizeDIPs();
+  auto size = root_view->GetSize();
   float x = size.width() / 2;
   float y = size.height() / 2;
   ui::MotionEventAndroid::Pointer pointer0(0, x, y, 0, 0, 0, 0, 0);
diff --git a/content/browser/web_contents/web_contents_android.cc b/content/browser/web_contents/web_contents_android.cc
index 2776494c..fc486e03 100644
--- a/content/browser/web_contents/web_contents_android.cc
+++ b/content/browser/web_contents/web_contents_android.cc
@@ -826,11 +826,11 @@
 }
 
 int WebContentsAndroid::GetWidth(JNIEnv* env) {
-  return web_contents_->GetNativeView()->GetSizeDIPs().width();
+  return web_contents_->GetNativeView()->GetSize().width();
 }
 
 int WebContentsAndroid::GetHeight(JNIEnv* env) {
-  return web_contents_->GetNativeView()->GetSizeDIPs().height();
+  return web_contents_->GetNativeView()->GetSize().height();
 }
 
 ScopedJavaLocalRef<jobject> WebContentsAndroid::GetOrCreateEventForwarder(
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 27c675d..f462c6c 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -4503,6 +4503,18 @@
       base::BindRepeating(
           [](PageVisibilityState page_visibility,
              RenderViewHostImpl* render_view_host) {
+            // If we are a guest frame tree, allow the visibility (ie
+            // display:none) to override the page visibility.
+            if (render_view_host->frame_tree()->is_guest()) {
+              RenderFrameProxyHost* proxy = render_view_host->frame_tree()
+                                                ->root()
+                                                ->render_manager()
+                                                ->GetProxyToOuterDelegate();
+              if (proxy && proxy->cross_process_frame_connector()->IsHidden()) {
+                page_visibility = PageVisibilityState::kHidden;
+              }
+            }
+
             render_view_host->SetFrameTreeVisibility(page_visibility);
           },
           page_visibility);
@@ -10218,7 +10230,7 @@
   return window->bounds().size();
 #elif BUILDFLAG(IS_ANDROID)
   ui::ViewAndroid* view_android = GetNativeView();
-  return view_android->GetSizeDIPs();
+  return view_android->GetSize();
 #elif BUILDFLAG(IS_IOS)
   // TODO(crbug.com/40254930): Implement me.
   NOTREACHED();
diff --git a/content/browser/web_contents/web_contents_view_android.cc b/content/browser/web_contents/web_contents_view_android.cc
index 37b434da..b088103 100644
--- a/content/browser/web_contents/web_contents_view_android.cc
+++ b/content/browser/web_contents/web_contents_view_android.cc
@@ -248,7 +248,7 @@
 }
 
 gfx::Rect WebContentsViewAndroid::GetViewBounds() const {
-  return gfx::Rect(view_.GetSizeDIPs());
+  return gfx::Rect(view_.GetSize());
 }
 
 void WebContentsViewAndroid::CreateView(gfx::NativeView context) {}
diff --git a/content/browser/webui/url_data_manager.h b/content/browser/webui/url_data_manager.h
index 6f84431..8b250ca 100644
--- a/content/browser/webui/url_data_manager.h
+++ b/content/browser/webui/url_data_manager.h
@@ -73,9 +73,10 @@
   friend struct DeleteURLDataSource;
   typedef std::vector<const URLDataSourceImpl*> URLDataSources;
 
-  // If invoked on the UI thread the DataSource is deleted immediatlye,
+  // If invoked on the UI thread the DataSource is deleted immediately,
   // otherwise it is added to |data_sources_| and a task is scheduled to handle
-  // deletion on the UI thread. See note abouve DeleteDataSource for more info.
+  // deletion on the UI thread. See note above the |DeleteURLDataSource| struct
+  // in url_data_source_impl.h for more info.
   static void DeleteDataSource(const URLDataSourceImpl* data_source);
 
   // Returns true if |data_source| is scheduled for deletion (|DeleteDataSource|
diff --git a/content/public/android/java/strings/android_content_strings.grd b/content/public/android/java/strings/android_content_strings.grd
index f7997a2..927fe832 100644
--- a/content/public/android/java/strings/android_content_strings.grd
+++ b/content/public/android/java/strings/android_content_strings.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="values-af/android_content_strings.xml" lang="af" type="android" />
     <output filename="values-am/android_content_strings.xml" lang="am" type="android" />
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc
index 85ce29e..16f4458a 100644
--- a/content/public/test/render_view_test.cc
+++ b/content/public/test/render_view_test.cc
@@ -771,7 +771,7 @@
 void RenderViewTest::Resize(gfx::Size new_size, bool is_fullscreen_granted) {
   blink::VisualProperties visual_properties;
   visual_properties.screen_infos = display::ScreenInfos(display::ScreenInfo());
-  visual_properties.new_size_device_px = new_size;
+  visual_properties.new_size = new_size;
   visual_properties.compositor_viewport_pixel_rect = gfx::Rect(new_size);
   visual_properties.is_fullscreen_granted = is_fullscreen_granted;
   visual_properties.display_mode = blink::mojom::DisplayMode::kBrowser;
@@ -880,7 +880,7 @@
   initial_visual_properties.screen_infos =
       display::ScreenInfos(display::ScreenInfo());
   // Ensure the view has some size so tests involving scrolling bounds work.
-  initial_visual_properties.new_size_device_px = gfx::Size(400, 300);
+  initial_visual_properties.new_size = gfx::Size(400, 300);
   initial_visual_properties.visible_viewport_size = gfx::Size(400, 300);
   return initial_visual_properties;
 }
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 4e886e04..9c31268 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -72,6 +72,8 @@
     "gpu_benchmarking_extension.h",
     "in_process_renderer_thread.cc",
     "in_process_renderer_thread.h",
+    "local_resource_url_loader_factory.cc",
+    "local_resource_url_loader_factory.h",
     "media/audio_decoder.cc",
     "media/audio_decoder.h",
     "media/batching_media_log.cc",
diff --git a/content/renderer/DEPS b/content/renderer/DEPS
index a172720b..8248c11 100644
--- a/content/renderer/DEPS
+++ b/content/renderer/DEPS
@@ -48,6 +48,9 @@
   "render_thread_impl_browsertest\.cc": [
     "+content/app/mojo/mojo_init.h",
   ],
+  "local_resource_url_loader_factory_unittest\.cc": [
+    "+ui/base/resource",
+  ],
   "render_thread_impl_discardable_memory_browsertest\.cc": [
     "+components/discardable_memory/service",
     "+content/browser/browser_main_loop.h",
diff --git a/content/renderer/local_resource_url_loader_factory.cc b/content/renderer/local_resource_url_loader_factory.cc
new file mode 100644
index 0000000..191140b
--- /dev/null
+++ b/content/renderer/local_resource_url_loader_factory.cc
@@ -0,0 +1,231 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/renderer/local_resource_url_loader_factory.h"
+
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#include "base/check.h"
+#include "base/containers/span.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/notimplemented.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/types/expected.h"
+#include "content/common/web_ui_loading_util.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/url_constants.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "net/base/mime_util.h"
+#include "net/socket/socket.h"
+#include "services/network/public/cpp/parsed_headers.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "third_party/blink/public/mojom/loader/local_resource_loader_config.mojom.h"
+#include "ui/base/template_expressions.h"
+#include "url/origin.h"
+
+namespace content {
+
+namespace {
+
+std::map<url::Origin, LocalResourceURLLoaderFactory::Source>
+ConvertConfigToSourcesMap(
+    const blink::mojom::LocalResourceLoaderConfigPtr& config) {
+  std::map<url::Origin, LocalResourceURLLoaderFactory::Source> sources;
+  // TODO(https://crbug.com/384765582) This manual copy is only necessary
+  // because ui::ReplaceTemplateExpressions uses an unconventional map type.
+  // Remove this when that is fixed.
+  for (const auto& source : config->sources) {
+    const url::Origin origin = source.first;
+    const blink::mojom::LocalResourceSourcePtr& mojo_source = source.second;
+    const std::map<const std::string, std::string> replacement_strings(
+        mojo_source->replacement_strings.begin(),
+        mojo_source->replacement_strings.end());
+    LocalResourceURLLoaderFactory::Source local_source(
+        mojo_source.Clone(), std::move(replacement_strings));
+    sources.insert({std::move(origin), std::move(local_source)});
+  }
+  return sources;
+}
+
+}  // namespace
+
+LocalResourceURLLoaderFactory::Source::Source(
+    blink::mojom::LocalResourceSourcePtr source,
+    std::map<const std::string, std::string> replacement_strings)
+    : source(std::move(source)),
+      replacement_strings(std::move(replacement_strings)) {}
+
+LocalResourceURLLoaderFactory::Source::Source(Source&& other) = default;
+LocalResourceURLLoaderFactory::Source&
+LocalResourceURLLoaderFactory::Source::operator=(Source&& other) = default;
+
+LocalResourceURLLoaderFactory::Source::~Source() = default;
+
+LocalResourceURLLoaderFactory::LocalResourceURLLoaderFactory(
+    const blink::mojom::LocalResourceLoaderConfigPtr& config,
+    mojo::PendingRemote<network::mojom::URLLoaderFactory> fallback)
+    : sources_(base::MakeRefCounted<
+               base::RefCountedData<std::map<url::Origin, Source>>>(
+          ConvertConfigToSourcesMap(config))),
+      fallback_(std::move(fallback)) {}
+
+LocalResourceURLLoaderFactory::~LocalResourceURLLoaderFactory() = default;
+
+bool LocalResourceURLLoaderFactory::CanServe(
+    const network::ResourceRequest& request) const {
+  const url::Origin origin = url::Origin::Create(request.url);
+  auto it = sources_->data.find(origin);
+  // The renderer process may not have metadata for the data source. This can
+  // happen if the data source isn't a WebUIDataSource, in which case the
+  // browser process doesn't send metadata for it.
+  // Example: chrome://theme/colors.css
+  if (it == sources_->data.end()) {
+    return false;
+  }
+
+  // Get the resource ID corresponding to the URL path.
+  const blink::mojom::LocalResourceSourcePtr& source = it->second.source;
+  std::string_view path = request.url.path_piece().substr(1);
+  auto resource_it = source->path_to_resource_id_map.find(path);
+  // The path-to-ID map may not have an entry for the given path. This can
+  // happen for resources that are generated on-the-fly in the browser process.
+  // Example: chrome://my-webui/strings.m.js
+  if (resource_it == source->path_to_resource_id_map.end()) {
+    return false;
+  }
+  int resource_id = resource_it->second;
+
+  // Return true if the in-process ResourceBundle has the resource for this ID.
+  return GetContentClient()->HasDataResource(resource_id);
+}
+
+void LocalResourceURLLoaderFactory::CreateLoaderAndStart(
+    mojo::PendingReceiver<network::mojom::URLLoader> loader,
+    int32_t request_id,
+    uint32_t options,
+    const network::ResourceRequest& request,
+    mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
+  if (!CanServe(request)) {
+    fallback_->CreateLoaderAndStart(std::move(loader), request_id, options,
+                                    request, std::move(client),
+                                    traffic_annotation);
+    return;
+  }
+  // Only the "chrome" scheme is supported.
+  CHECK(request.url.scheme() == kChromeUIScheme);
+  // Parallelize calls to GetResourceAndRespond across multiple threads.
+  // Needs to be posted to a SequencedTaskRunner as Mojo requires a
+  // SequencedTaskRunner::CurrentDefaultHandle in scope.
+  base::ThreadPool::CreateSequencedTaskRunner(
+      {base::TaskPriority::USER_BLOCKING, base::MayBlock(),
+       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})
+      ->PostTask(FROM_HERE, base::BindOnce(GetResourceAndRespond, sources_,
+                                           request, std::move(client)));
+}
+
+void LocalResourceURLLoaderFactory::Clone(
+    mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
+// static
+void LocalResourceURLLoaderFactory::GetResourceAndRespond(
+    const scoped_refptr<base::RefCountedData<std::map<url::Origin, Source>>>
+        sources,
+    const network::ResourceRequest& request,
+    mojo::PendingRemote<network::mojom::URLLoaderClient> client) {
+  const url::Origin origin = url::Origin::Create(request.url);
+  auto it = sources->data.find(origin);
+  // CanServe should have been called before this point, which would have
+  // confirmed that there exists a source corresponding to the URL origin.
+  CHECK(it != sources->data.end());
+
+  const blink::mojom::LocalResourceSourcePtr& source = it->second.source;
+  const std::map<const std::string, std::string>& replacement_strings =
+      it->second.replacement_strings;
+
+  // Get resource id.
+  std::string_view path = request.url.path_piece().substr(1);
+  auto resource_it = source->path_to_resource_id_map.find(path);
+  // CanServe should have been called before this point, which would have
+  // confirmed that there exists a resource ID corresponding to the URL path.
+  CHECK(resource_it != source->path_to_resource_id_map.end());
+  int resource_id = resource_it->second;
+
+  // Load bytes.
+  scoped_refptr<base::RefCountedMemory> raw_bytes =
+      GetContentClient()->GetDataResourceBytes(resource_id);
+  // CanServe should have been called before this point, which would have
+  // confirmed that the ResourceBundle will return non-null for the given
+  // resource ID.
+  CHECK(raw_bytes);
+  std::string_view bytes(base::as_string_view(*raw_bytes));
+
+  auto url_response_head = network::mojom::URLResponseHead::New();
+
+  // Mime type.
+  std::string mime_type;
+  if (net::GetMimeTypeFromFile(
+          base::FilePath::FromASCII(request.url.ExtractFileName()),
+          &mime_type)) {
+    url_response_head->mime_type = mime_type;
+  } else {
+    url_response_head->mime_type = "text/html";
+  }
+
+  scoped_refptr<base::RefCountedMemory> bytes_after_replacement = raw_bytes;
+  if (source->replacement_strings.size() > 0 &&
+      (url_response_head->mime_type == "text/html" ||
+       url_response_head->mime_type == "text/css" ||
+       (source->should_replace_i18n_in_js &&
+        url_response_head->mime_type == "text/javascript"))) {
+    std::string replaced_string;
+    if (url_response_head->mime_type == "text/javascript") {
+      CHECK(ui::ReplaceTemplateExpressionsInJS(bytes, replacement_strings,
+                                               &replaced_string));
+    } else {
+      replaced_string =
+          ui::ReplaceTemplateExpressions(bytes, replacement_strings);
+    }
+    bytes_after_replacement = base::MakeRefCounted<base::RefCountedString>(
+        std::move(replaced_string));
+  }
+
+  // Other headers.
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      base::MakeRefCounted<net::HttpResponseHeaders>(source->headers);
+  headers->SetHeader(net::HttpRequestHeaders::kContentType, mime_type);
+  url_response_head->headers = headers;
+  url_response_head->parsed_headers = network::PopulateParsedHeaders(
+      url_response_head->headers.get(), request.url);
+
+  // Handle Range header if request.
+  base::expected<net::HttpByteRange, webui::GetRequestedRangeError>
+      range_or_error = webui::GetRequestedRange(request.headers);
+  // Errors (aside from 'no Range header') should be surfaced to the client.
+  if (!range_or_error.has_value() &&
+      range_or_error.error() != webui::GetRequestedRangeError::kNoRanges) {
+    webui::CallOnError(std::move(client),
+                       net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+    return;
+  }
+  std::optional<net::HttpByteRange> maybe_range =
+      range_or_error.has_value() ? std::make_optional(range_or_error.value())
+                                 : std::nullopt;
+
+  webui::SendData(std::move(url_response_head), std::move(client), maybe_range,
+                  bytes_after_replacement);
+}
+
+}  // namespace content
diff --git a/content/renderer/local_resource_url_loader_factory.h b/content/renderer/local_resource_url_loader_factory.h
new file mode 100644
index 0000000..674cfe9
--- /dev/null
+++ b/content/renderer/local_resource_url_loader_factory.h
@@ -0,0 +1,113 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_RENDERER_LOCAL_RESOURCE_URL_LOADER_FACTORY_H_
+#define CONTENT_RENDERER_LOCAL_RESOURCE_URL_LOADER_FACTORY_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/sequence_bound.h"
+#include "content/common/content_export.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/socket/socket.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "third_party/blink/public/mojom/loader/local_resource_loader_config.mojom.h"
+#include "url/origin.h"
+
+namespace content {
+
+// LocalResourceURLLoaderFactory is a URLLoaderFactory that lives in the
+// renderer process and fetches resources directly from the ResourceBundle,
+// enabling renderers to load bundled resources entirely in-process. This can
+// significantly reduce IPC overhead for WebUIs, whose resources come almost
+// exclusively from the ResourceBundle.
+class CONTENT_EXPORT LocalResourceURLLoaderFactory
+    : public network::mojom::URLLoaderFactory {
+ public:
+  struct Source {
+    Source(blink::mojom::LocalResourceSourcePtr source,
+           std::map<const std::string, std::string> replacement_strings);
+    Source(Source&& other);
+    Source& operator=(Source&& other);
+    ~Source();
+    blink::mojom::LocalResourceSourcePtr source;
+    std::map<const std::string, std::string> replacement_strings;
+  };
+
+  LocalResourceURLLoaderFactory(
+      const blink::mojom::LocalResourceLoaderConfigPtr& config,
+      mojo::PendingRemote<network::mojom::URLLoaderFactory> fallback);
+  ~LocalResourceURLLoaderFactory() override;
+
+  // network::mojom::URLLoaderFactory implementation.
+  void CreateLoaderAndStart(
+      mojo::PendingReceiver<network::mojom::URLLoader> loader,
+      int32_t request_id,
+      uint32_t options,
+      const network::ResourceRequest& request,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
+      override;
+  void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver)
+      override;
+
+ private:
+  // CanServe returns true if and only if all of the following are true:
+  //
+  // 1. The LocalResourceURLLoaderFactory has a resource ID for the given URL
+  //    (which depends on the resource metadata received from the browser
+  //    process), and
+  // 2. The resource ID corresponds to a resource that exists in
+  //    'resources.pak'.
+  //
+  // It should return false in the following cases:
+  //
+  // 1. For dynamic resources that are generated on-the-fly in the browser
+  //    process (e.g. strings.m.js, chrome://theme/colors.css).
+  // 2. For static resources that reside in a pak file that is not
+  //    memory-mapped in the renderer process (e.g. browser_tests.pak).
+  //
+  // CanServe is called internally for every resource request to decide whether
+  // the request can be serviced in-process or if it has to be serviced
+  // out-of-process.
+  bool CanServe(const network::ResourceRequest& request) const;
+
+  // Fetches the resource from the ResourceBundle and sends it to the
+  // URLLoaderClient. This is static because it is posted as a task which may
+  // outlive |this|.
+  static void GetResourceAndRespond(
+      const scoped_refptr<base::RefCountedData<std::map<url::Origin, Source>>>
+          sources,
+      const network::ResourceRequest& request,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> client);
+
+  // Map for resolving origins to their respective source metadata
+  // (path-to-resource-ID mappings and string replacement maps).
+  // It is ref-counted because it needs to be accessed in an async call to
+  // GetResourceAndRespond, which may outlive |this|.
+  const scoped_refptr<base::RefCountedData<std::map<url::Origin, Source>>>
+      sources_;
+
+  // Pipe to fallback factory, which should be the WebUIURLLoaderFactory in the
+  // browser process. This is required because there are certain resources that
+  // cannot be serviced in-process and must be serviced by the browser process.
+  // See the CanServe method for more details.
+  const mojo::Remote<network::mojom::URLLoaderFactory> fallback_;
+
+  // Pipes to callers of the Clone method.
+  mojo::ReceiverSet<network::mojom::URLLoaderFactory> receivers_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_RENDERER_LOCAL_RESOURCE_URL_LOADER_FACTORY_H_
diff --git a/content/renderer/local_resource_url_loader_factory_unittest.cc b/content/renderer/local_resource_url_loader_factory_unittest.cc
new file mode 100644
index 0000000..a76571c
--- /dev/null
+++ b/content/renderer/local_resource_url_loader_factory_unittest.cc
@@ -0,0 +1,351 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/renderer/local_resource_url_loader_factory.h"
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "base/check.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/span.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/notimplemented.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "base/threading/sequence_bound.h"
+#include "content/common/web_ui_loading_util.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/system/data_pipe_utils.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_version.h"
+#include "net/socket/socket.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_url_loader_client.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/loader/local_resource_loader_config.mojom.h"
+#include "ui/base/resource/mock_resource_bundle_delegate.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/resource/resource_scale_factor.h"
+#include "url/origin.h"
+#include "url/url_util.h"
+
+namespace {
+
+// A URLLoaderFactory that always sends the string "out-of-process resource" to
+// the client.
+class FakeURLLoaderFactory : public network::mojom::URLLoaderFactory {
+ public:
+  FakeURLLoaderFactory() : receiver_(this) {}
+  void CreateLoaderAndStart(
+      mojo::PendingReceiver<network::mojom::URLLoader> loader,
+      int32_t request_id,
+      uint32_t options,
+      const network::ResourceRequest& request,
+      mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
+      override {
+    auto headers = network::mojom::URLResponseHead::New();
+    auto bytes =
+        base::MakeRefCounted<base::RefCountedString>("out-of-process resource");
+    content::webui::SendData(std::move(headers), std::move(client),
+                             std::nullopt, std::move(bytes));
+  }
+  void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver)
+      override {
+    // Supports only one receiver at a time.
+    receiver_.reset();
+    receiver_.Bind(std::move(receiver));
+  }
+
+ private:
+  mojo::Receiver<network::mojom::URLLoaderFactory> receiver_;
+};
+
+class LocalResourceURLLoaderFactoryTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    // Swap in mock ResourceBundle.
+    original_resource_bundle_ =
+        ui::ResourceBundle::SwapSharedInstanceForTesting(nullptr);
+    ui::ResourceBundle::InitSharedInstanceWithLocale(
+        "en-US", &resource_bundle_delegate_,
+        ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES);
+
+    source_ = blink::mojom::LocalResourceSource::New();
+    source_->headers =
+        net::HttpResponseHeaders::Builder(net::HttpVersion(1, 1), "200 OK")
+            .Build()
+            ->raw_headers();
+
+    UpdateLoaderFactory();
+  }
+
+  void TearDown() override {
+    ui::ResourceBundle::CleanupSharedInstance();
+    ui::ResourceBundle::SwapSharedInstanceForTesting(original_resource_bundle_);
+  }
+
+ protected:
+  // Used to control the behavior of the mock ResourceBundle.
+  // Marked non-private so that tests can call EXPECT_CALL on it.
+  testing::NiceMock<ui::MockResourceBundleDelegate> resource_bundle_delegate_;
+
+  content::LocalResourceURLLoaderFactory* loader_factory() {
+    return loader_factory_.get();
+  }
+
+  void SetShouldReplaceI18nInJs(bool value) {
+    source_->should_replace_i18n_in_js = value;
+    UpdateLoaderFactory();
+  }
+
+  void AddReplacementString(const std::string& key, const std::string& value) {
+    source_->replacement_strings[key] = value;
+    UpdateLoaderFactory();
+  }
+
+  void AddResourceID(const std::string& path, int id) {
+    source_->path_to_resource_id_map[path] = id;
+    UpdateLoaderFactory();
+  }
+
+  std::string ReadAllData(network::TestURLLoaderClient& client) {
+    std::string result;
+    CHECK(mojo::BlockingCopyToString(client.response_body_release(), &result));
+    return result;
+  }
+
+ private:
+  // Create a config with a |source_| as the single hardcoded entry in the map.
+  void UpdateLoaderFactory() {
+    const url::Origin origin = url::Origin::Create(GURL("chrome://sourcename"));
+    auto config = blink::mojom::LocalResourceLoaderConfig::New();
+    config->sources[origin] = source_.Clone();
+    // Create a pipe and pass receiving end to |fake_fallback_factory_|.
+    mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote;
+    fake_fallback_factory_.Clone(
+        pending_remote.InitWithNewPipeAndPassReceiver());
+    // Pass other end to |loader_factory_|.
+    loader_factory_ = std::make_unique<content::LocalResourceURLLoaderFactory>(
+        config, std::move(pending_remote));
+  }
+
+  std::unique_ptr<content::LocalResourceURLLoaderFactory> loader_factory_;
+
+  FakeURLLoaderFactory fake_fallback_factory_;
+
+  // Intermediate state that is updated by the test and eventually used to
+  // update the loader factory state.
+  blink::mojom::LocalResourceSourcePtr source_;
+
+  // Temporary storage of original ResourceBundle while we swap in the test
+  // mock.
+  raw_ptr<ui::ResourceBundle> original_resource_bundle_;
+
+  // For CreateLoaderAndStart, which posts a task.
+  base::test::TaskEnvironment task_environment_;
+};
+
+struct CanServeTestCase {
+  std::optional<int> resource_id;
+  bool has_resource;
+  bool can_serve;
+};
+
+struct ServeTestCase {
+  std::string path;
+  std::string mime_type;
+  std::string resource_data;
+  std::string response_body;
+};
+
+struct RequestRangeTestCase {
+  std::string request_range;
+  int error_code;
+  std::string resource_data;
+  std::string response_body;
+};
+
+class LocalResourceURLLoaderFactoryCanServeTest
+    : public LocalResourceURLLoaderFactoryTest,
+      public ::testing::WithParamInterface<CanServeTestCase> {};
+class LocalResourceURLLoaderFactoryServeTest
+    : public LocalResourceURLLoaderFactoryTest,
+      public ::testing::WithParamInterface<ServeTestCase> {};
+class LocalResourceURLLoaderFactoryRequestRangeTest
+    : public LocalResourceURLLoaderFactoryTest,
+      public ::testing::WithParamInterface<RequestRangeTestCase> {};
+
+}  // namespace
+
+// Check if loader factory can service a particular request.
+TEST_P(LocalResourceURLLoaderFactoryCanServeTest, CanServe) {
+  const std::string path = "path/to/resource";
+  if (GetParam().resource_id) {
+    AddResourceID(path, *GetParam().resource_id);
+  }
+  const scoped_refptr<base::RefCountedString> resource_data =
+      base::MakeRefCounted<base::RefCountedString>("in-process resource");
+  ON_CALL(resource_bundle_delegate_, HasDataResource)
+      .WillByDefault(testing::Return(GetParam().has_resource));
+  ON_CALL(resource_bundle_delegate_, LoadDataResourceBytes)
+      .WillByDefault(testing::Return(resource_data.get()));
+
+  network::TestURLLoaderClient client;
+  network::ResourceRequest request;
+  // The test fixture hardcodes the origin to 'chrome://sourcename'.
+  request.url = GURL("chrome://sourcename/" + path);
+  mojo::PendingRemote<network::mojom::URLLoader> loader;
+  loader_factory()->CreateLoaderAndStart(
+      loader.InitWithNewPipeAndPassReceiver(), 0, 0, request,
+      client.CreateRemote(), net::MutableNetworkTrafficAnnotationTag());
+  client.RunUntilComplete();
+
+  ASSERT_EQ(net::OK, client.completion_status().error_code);
+  ASSERT_TRUE(client.response_body().is_valid());
+  std::string response_body = ReadAllData(client);
+  if (GetParam().can_serve) {
+    EXPECT_EQ(response_body, "in-process resource");
+  } else {
+    EXPECT_EQ(response_body, "out-of-process resource");
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    LocalResourceURLLoaderFactoryCanServeTest,
+    LocalResourceURLLoaderFactoryCanServeTest,
+    ::testing::Values(
+        // Resource ID exists and ResourceBundle has the resource.
+        CanServeTestCase(std::make_optional(1), true, true),
+        // Resource ID exists but ResourceBundle does not have the resource.
+        CanServeTestCase(std::make_optional(1), false, false),
+        // Resource ID does not exist in mapping.
+        CanServeTestCase(std::nullopt, false, false)));
+
+// Create loader, read bytes from the ResourceBundle and send them to the
+// client.
+TEST_P(LocalResourceURLLoaderFactoryServeTest, Serve) {
+  const int resource_id = 1;
+  const scoped_refptr<base::RefCountedString> resource_data =
+      base::MakeRefCounted<base::RefCountedString>(GetParam().resource_data);
+  AddResourceID(GetParam().path, resource_id);
+  AddReplacementString("foo", "bar");
+  SetShouldReplaceI18nInJs(true);
+  // Bypass the fallback by making CanServe return true.
+  ON_CALL(resource_bundle_delegate_, HasDataResource)
+      .WillByDefault(testing::Return(true));
+  EXPECT_CALL(resource_bundle_delegate_,
+              LoadDataResourceBytes(resource_id,
+                                    ui::ResourceScaleFactor::kScaleFactorNone))
+      .WillOnce(testing::Return(resource_data.get()));
+
+  network::TestURLLoaderClient client;
+  network::ResourceRequest request;
+  // The test fixture hardcodes the origin to 'chrome://sourcename'.
+  request.url = GURL("chrome://sourcename/" + GetParam().path);
+  mojo::PendingRemote<network::mojom::URLLoader> loader;
+  loader_factory()->CreateLoaderAndStart(
+      loader.InitWithNewPipeAndPassReceiver(), 0, 0, request,
+      client.CreateRemote(), net::MutableNetworkTrafficAnnotationTag());
+  client.RunUntilComplete();
+
+  ASSERT_EQ(net::OK, client.completion_status().error_code);
+  EXPECT_EQ(GetParam().mime_type, client.response_head()->mime_type);
+  ASSERT_TRUE(client.response_body().is_valid());
+  std::string response_body = ReadAllData(client);
+  EXPECT_EQ(GetParam().response_body, response_body);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    LocalResourceURLLoaderFactoryServeTest,
+    LocalResourceURLLoaderFactoryServeTest,
+    ::testing::Values(
+        // MIME type is assumed to be text/html. String replacement occurs.
+        ServeTestCase("path/to/resource",
+                      "text/html",
+                      "this is $i18n{foo}",
+                      "this is bar"),
+        // MIME type is text/html. String replacement occurs.
+        ServeTestCase("path/to/resource.html",
+                      "text/html",
+                      "this is $i18n{foo}",
+                      "this is bar"),
+        // MIME type is text/css. String replacement occurs.
+        ServeTestCase("path/to/resource.css",
+                      "text/css",
+                      "this is $i18n{foo}",
+                      "this is bar"),
+        // MIME type is text/javascript. String replacement only occurs within
+        // HTML template section.
+        ServeTestCase("path/to/resource.js",
+                      "text/javascript",
+                      "this is $i18n{foo}",
+                      "this is $i18n{foo}")));
+
+// Request various byte ranges which may or may not be valid.
+TEST_P(LocalResourceURLLoaderFactoryRequestRangeTest, RequestRange) {
+  const std::string path = "path/to/resource";
+  const int resource_id = 1;
+  const scoped_refptr<base::RefCountedString> resource_data =
+      base::MakeRefCounted<base::RefCountedString>(GetParam().resource_data);
+  AddResourceID(path, resource_id);
+  // Bypass the fallback by making CanServe return true.
+  ON_CALL(resource_bundle_delegate_, HasDataResource)
+      .WillByDefault(testing::Return(true));
+  EXPECT_CALL(resource_bundle_delegate_,
+              LoadDataResourceBytes(resource_id,
+                                    ui::ResourceScaleFactor::kScaleFactorNone))
+      .WillOnce(testing::Return(resource_data.get()));
+
+  network::TestURLLoaderClient client;
+  network::ResourceRequest request;
+  // The test fixture hardcodes the origin to 'chrome://sourcename'.
+  request.url = GURL("chrome://sourcename/" + path);
+  request.headers.SetHeader(net::HttpRequestHeaders::kRange,
+                            GetParam().request_range);
+  mojo::PendingRemote<network::mojom::URLLoader> loader;
+  loader_factory()->CreateLoaderAndStart(
+      loader.InitWithNewPipeAndPassReceiver(), 0, 0, request,
+      client.CreateRemote(), net::MutableNetworkTrafficAnnotationTag());
+  client.RunUntilComplete();
+
+  EXPECT_EQ(GetParam().error_code, client.completion_status().error_code);
+  if (GetParam().error_code != net::OK) {
+    return;
+  }
+  ASSERT_TRUE(client.response_body().is_valid());
+  std::string response_body = ReadAllData(client);
+  EXPECT_EQ(GetParam().response_body, response_body);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    LocalResourceURLLoaderFactoryRequestRangeTest,
+    LocalResourceURLLoaderFactoryRequestRangeTest,
+    ::testing::Values(
+        // Valid range.
+        RequestRangeTestCase("bytes=3-10",
+                             net::OK,
+                             "resource data",
+                             "ource da"),
+        // Valid range, but starting byte is greater than resource size.
+        // Error expected.
+        RequestRangeTestCase("bytes=100-101",
+                             net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)));
diff --git a/content/renderer/render_frame_impl_browsertest.cc b/content/renderer/render_frame_impl_browsertest.cc
index 0531485d..d0d72072 100644
--- a/content/renderer/render_frame_impl_browsertest.cc
+++ b/content/renderer/render_frame_impl_browsertest.cc
@@ -122,7 +122,7 @@
     mojom::CreateFrameWidgetParamsPtr widget_params =
         mojom::CreateFrameWidgetParams::New();
     widget_params->routing_id = kSubframeWidgetRouteId;
-    widget_params->visual_properties.new_size_device_px = gfx::Size(100, 100);
+    widget_params->visual_properties.new_size = gfx::Size(100, 100);
     widget_params->visual_properties.screen_infos =
         display::ScreenInfos(display::ScreenInfo());
 
@@ -286,7 +286,7 @@
   visual_properties.screen_infos = display::ScreenInfos(display::ScreenInfo());
   gfx::Size widget_size(400, 200);
   gfx::Size visible_size(350, 170);
-  visual_properties.new_size_device_px = widget_size;
+  visual_properties.new_size = widget_size;
   visual_properties.compositor_viewport_pixel_rect = gfx::Rect(widget_size);
   visual_properties.visible_viewport_size = visible_size;
 
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 140ec647..17bbb44 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -548,11 +548,9 @@
     visual_properties.screen_infos =
         display::ScreenInfos(display::ScreenInfo());
     visual_properties.screen_infos.mutable_current().device_scale_factor = dsf;
-    visual_properties.new_size_device_px =
-        gfx::ScaleToCeiledSize(gfx::Size(100, 100), dsf);
+    visual_properties.new_size = gfx::Size(100, 100);
     visual_properties.compositor_viewport_pixel_rect = gfx::Rect(200, 200);
-    visual_properties.visible_viewport_size =
-        visual_properties.new_size_device_px;
+    visual_properties.visible_viewport_size = visual_properties.new_size;
     visual_properties.auto_resize_enabled = web_view_->AutoResizeMode();
     visual_properties.min_size_for_auto_resize = min_size_for_autoresize_;
     visual_properties.max_size_for_auto_resize = max_size_for_autoresize_;
diff --git a/content/renderer/render_widget_browsertest.cc b/content/renderer/render_widget_browsertest.cc
index 89b9fde..4874ea3f 100644
--- a/content/renderer/render_widget_browsertest.cc
+++ b/content/renderer/render_widget_browsertest.cc
@@ -68,7 +68,7 @@
  protected:
   blink::VisualProperties InitialVisualProperties() override {
     blink::VisualProperties initial_visual_properties;
-    initial_visual_properties.new_size_device_px = initial_size_;
+    initial_visual_properties.new_size = initial_size_;
     initial_visual_properties.compositor_viewport_pixel_rect =
         gfx::Rect(initial_size_);
     initial_visual_properties.local_surface_id =
diff --git a/content/shell/shell_resources.grd b/content/shell/shell_resources.grd
index 8b48f8bb..f2337e8 100644
--- a/content/shell/shell_resources.grd
+++ b/content/shell/shell_resources.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/shell_resources.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index e9478ce..89d48cc 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2900,6 +2900,7 @@
     "../public/test/test_utils_unittest.cc",
     "../renderer/accessibility/annotations/ax_image_stopwords_unittest.cc",
     "../renderer/content_security_policy_util_unittest.cc",
+    "../renderer/local_resource_url_loader_factory_unittest.cc",
     "../renderer/media/batching_media_log_unittest.cc",
     "../renderer/media/inspector_media_event_handler_unittest.cc",
     "../renderer/media/renderer_webaudiodevice_impl_unittest.cc",
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test.py b/content/test/gpu/gpu_tests/gpu_integration_test.py
index 5f82b32..5a9eb2c 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test.py
@@ -505,7 +505,9 @@
         cls._EnsureScreenOn()
         cls._CheckBrowserVersion()
         cls._VerifyBrowserFeaturesMatchExpectedValues()
-        cls._RetrieveAboutGpu()
+        # TODO(crbug.com/376498163): Re-enable this once the impact on Windows
+        # builds has been reduced.
+        # cls._RetrieveAboutGpu()
         return
       except Exception as e:  # pylint: disable=broad-except
         last_exception = e
@@ -864,7 +866,9 @@
       self._HandlePass(test_name, expected_crashes, expected_results)
     finally:
       self.additionalTags[TEST_WAS_SLOW] = json.dumps(self._TestWasSlow())
-      self._ReportAboutGpu(test_name)
+      # TODO(crbug.com/376498163): Re-enable this once the impact on Windows
+      # builds has been reduced.
+      # self._ReportAboutGpu(test_name)
       self._OnAfterTest(args)
 
   def _OnAfterTest(self, args: ct.TestArgs) -> None:
diff --git a/content/test/test_content_client.cc b/content/test/test_content_client.cc
index 99f5e34..fc3c8d65 100644
--- a/content/test/test_content_client.cc
+++ b/content/test/test_content_client.cc
@@ -14,6 +14,10 @@
 
 TestContentClient::~TestContentClient() = default;
 
+bool TestContentClient::HasDataResource(int resource_id) const {
+  return ui::ResourceBundle::GetSharedInstance().HasDataResource(resource_id);
+}
+
 std::string_view TestContentClient::GetDataResource(
     int resource_id,
     ui::ResourceScaleFactor scale_factor) {
diff --git a/content/test/test_content_client.h b/content/test/test_content_client.h
index 6778f07..636ad7e 100644
--- a/content/test/test_content_client.h
+++ b/content/test/test_content_client.h
@@ -21,6 +21,7 @@
   ~TestContentClient() override;
 
   // ContentClient:
+  bool HasDataResource(int resource_id) const override;
   std::string_view GetDataResource(
       int resource_id,
       ui::ResourceScaleFactor scale_factor) override;
diff --git a/content/test/web_ui_mojo_test_resources.grd b/content/test/web_ui_mojo_test_resources.grd
index 9adc490..0686f4e 100644
--- a/content/test/web_ui_mojo_test_resources.grd
+++ b/content/test/web_ui_mojo_test_resources.grd
@@ -2,7 +2,7 @@
 <!--
 This file specifies resources for content_browsertests.
 -->
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/web_ui_mojo_test_resources.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/docs/website b/docs/website
index fb143e8..54467c5 160000
--- a/docs/website
+++ b/docs/website
@@ -1 +1 @@
-Subproject commit fb143e8ce76fcf5f67792fbbd6c219c5d03ec2d9
+Subproject commit 54467c5b22ad5d81a5468007092db3e5d33d7f5b
diff --git a/extensions/browser/extension_registrar.cc b/extensions/browser/extension_registrar.cc
index db1b24e..dde230b 100644
--- a/extensions/browser/extension_registrar.cc
+++ b/extensions/browser/extension_registrar.cc
@@ -331,6 +331,7 @@
     const Extension* source_extension,
     const std::string& extension_id,
     disable_reason::DisableReason disable_reasons) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(disable_reasons == disable_reason::DISABLE_USER_ACTION ||
          disable_reasons == disable_reason::DISABLE_BLOCKED_BY_POLICY);
   if (disable_reasons == disable_reason::DISABLE_BLOCKED_BY_POLICY) {
@@ -505,6 +506,107 @@
   }
 }
 
+void ExtensionRegistrar::OnBlocklistStateRemoved(
+    const std::string& extension_id) {
+  if (blocklist_prefs::IsExtensionBlocklisted(extension_id, extension_prefs_)) {
+    return;
+  }
+
+  // Clear acknowledged state.
+  blocklist_prefs::RemoveAcknowledgedBlocklistState(
+      extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+      extension_prefs_);
+
+  scoped_refptr<const Extension> extension =
+      registry_->blocklisted_extensions().GetByID(extension_id);
+  DCHECK(extension);
+  registry_->RemoveBlocklisted(extension_id);
+  AddExtension(extension.get());
+}
+
+void ExtensionRegistrar::OnBlocklistStateAdded(
+    const std::string& extension_id) {
+  DCHECK(
+      blocklist_prefs::IsExtensionBlocklisted(extension_id, extension_prefs_));
+  // The extension was already acknowledged by the user, it should already be in
+  // the unloaded state.
+  if (blocklist_prefs::HasAcknowledgedBlocklistState(
+          extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+          extension_prefs_)) {
+    DCHECK(base::Contains(registry_->blocklisted_extensions().GetIDs(),
+                          extension_id));
+    return;
+  }
+
+  scoped_refptr<const Extension> extension =
+      registry_->GetInstalledExtension(extension_id);
+  registry_->AddBlocklisted(extension);
+  RemoveExtension(extension_id, UnloadedExtensionReason::BLOCKLIST);
+}
+
+void ExtensionRegistrar::OnGreylistStateRemoved(
+    const std::string& extension_id) {
+  bool is_on_sb_list = (blocklist_prefs::GetSafeBrowsingExtensionBlocklistState(
+                            extension_id, extension_prefs_) !=
+                        BitMapBlocklistState::NOT_BLOCKLISTED);
+  bool is_on_omaha_list =
+      blocklist_prefs::HasAnyOmahaGreylistState(extension_id, extension_prefs_);
+  if (is_on_sb_list || is_on_omaha_list) {
+    return;
+  }
+  // Clear all acknowledged states so the extension will still get disabled if
+  // it is added to the greylist again.
+  blocklist_prefs::ClearAcknowledgedGreylistStates(extension_id,
+                                                   extension_prefs_);
+  RemoveDisableReasonAndMaybeEnable(extension_id,
+                                    disable_reason::DISABLE_GREYLIST);
+}
+
+void ExtensionRegistrar::OnGreylistStateAdded(const std::string& extension_id,
+                                              BitMapBlocklistState new_state) {
+#if DCHECK_IS_ON()
+  bool has_new_state_on_sb_list =
+      (blocklist_prefs::GetSafeBrowsingExtensionBlocklistState(
+           extension_id, extension_prefs_) == new_state);
+  bool has_new_state_on_omaha_list = blocklist_prefs::HasOmahaBlocklistState(
+      extension_id, new_state, extension_prefs_);
+  DCHECK(has_new_state_on_sb_list || has_new_state_on_omaha_list);
+#endif
+  if (blocklist_prefs::HasAcknowledgedBlocklistState(extension_id, new_state,
+                                                     extension_prefs_)) {
+    // If the extension is already acknowledged, don't disable it again
+    // because it can be already re-enabled by the user. This could happen if
+    // the extension is added to the SafeBrowsing blocklist, and then
+    // subsequently marked by Omaha. In this case, we don't want to disable the
+    // extension twice.
+    return;
+  }
+
+  // Set the current greylist states to acknowledge immediately because the
+  // extension is disabled silently. Clear the other acknowledged state because
+  // when the state changes to another greylist state in the future, we'd like
+  // to disable the extension again.
+  blocklist_prefs::UpdateCurrentGreylistStatesAsAcknowledged(extension_id,
+                                                             extension_prefs_);
+  DisableExtension(extension_id, disable_reason::DISABLE_GREYLIST);
+}
+
+void ExtensionRegistrar::BlocklistExtensionForTest(
+    const std::string& extension_id) {
+  blocklist_prefs::SetSafeBrowsingExtensionBlocklistState(
+      extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
+      extension_prefs_);
+  OnBlocklistStateAdded(extension_id);
+}
+
+void ExtensionRegistrar::GreylistExtensionForTest(
+    const std::string& extension_id,
+    const BitMapBlocklistState& state) {
+  blocklist_prefs::SetSafeBrowsingExtensionBlocklistState(extension_id, state,
+                                                          extension_prefs_);
+  OnGreylistStateAdded(extension_id, state);
+}
+
 void ExtensionRegistrar::OnUnpackedExtensionReloadFailed(
     const base::FilePath& path) {
   failed_to_reload_unpacked_extensions_.insert(path);
diff --git a/extensions/browser/extension_registrar.h b/extensions/browser/extension_registrar.h
index 151665e7..fc66656 100644
--- a/extensions/browser/extension_registrar.h
+++ b/extensions/browser/extension_registrar.h
@@ -11,6 +11,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
+#include "extensions/browser/blocklist_state.h"
 #include "extensions/browser/disable_reason.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/process_manager_observer.h"
@@ -174,6 +175,38 @@
   // reloaded. Newly added extensions are no longer automatically blocked.
   void UnblockAllExtensions();
 
+  // Takes Safe Browsing and Omaha malware blocklist states into account and
+  // decides whether to remove the extension from the blocklist and reload it.
+  // Called when a blocklisted extension is removed from the Safe Browsing
+  // malware blocklist or Omaha malware blocklist. Also clears the acknowledged
+  // state if the extension is reloaded.
+  void OnBlocklistStateRemoved(const std::string& extension_id);
+
+  // Takes acknowledged malware blocklist state into account and decides whether
+  // to add the extension to the blocklist and unload it. Called when the
+  // extension is added to the Safe Browsing malware blocklist or the Omaha
+  // malware blocklist.
+  void OnBlocklistStateAdded(const std::string& extension_id);
+
+  // Takes Safe Browsing and Omaha blocklist states into account and decides
+  // whether to remove greylist disabled reason. Called when a greylisted
+  // state is removed from the Safe Browsing blocklist or Omaha blocklist. Also
+  // clears all acknowledged states if the greylist disabled reason is removed.
+  void OnGreylistStateRemoved(const std::string& extension_id);
+
+  // Takes acknowledged blocklist states into account and decides whether to
+  // disable the greylisted extension. Called when a new greylisted state is
+  // added to the Safe Browsing blocklist or Omaha blocklist.
+  void OnGreylistStateAdded(const std::string& extension_id,
+                            BitMapBlocklistState new_state);
+
+  // Simulates an extension being blocklisted for tests.
+  void BlocklistExtensionForTest(const std::string& extension_id);
+
+  // Simulates an extension being greylisted for tests.
+  void GreylistExtensionForTest(const std::string& extension_id,
+                                const BitMapBlocklistState& state);
+
   // Deactivates the extension, adding its id to the list of terminated
   // extensions.
   void TerminateExtension(const ExtensionId& extension_id);
diff --git a/extensions/browser/resources/extensions_browser_resources.grd b/extensions/browser/resources/extensions_browser_resources.grd
index 1c6fa9c..5243a8f 100644
--- a/extensions/browser/resources/extensions_browser_resources.grd
+++ b/extensions/browser/resources/extensions_browser_resources.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/extensions_browser_resources.h" type="rc_header" context="default_100_percent">
       <emit emit_type='prepend'></emit>
diff --git a/extensions/extensions_resources.grd b/extensions/extensions_resources.grd
index 6c9a07c..ea85963 100644
--- a/extensions/extensions_resources.grd
+++ b/extensions/extensions_resources.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/extensions_resources.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/extensions/renderer/resources/extensions_renderer_resources.grd b/extensions/renderer/resources/extensions_renderer_resources.grd
index 54507f9..5981f69e 100644
--- a/extensions/renderer/resources/extensions_renderer_resources.grd
+++ b/extensions/renderer/resources/extensions_renderer_resources.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/extensions_renderer_resources.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/extensions/shell/app_shell_resources.grd b/extensions/shell/app_shell_resources.grd
index 75bd1c8..857ac480 100644
--- a/extensions/shell/app_shell_resources.grd
+++ b/extensions/shell/app_shell_resources.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/app_shell_resources.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/extensions/strings/extensions_strings.grd b/extensions/strings/extensions_strings.grd
index f7e67d7..6a97381 100644
--- a/extensions/strings/extensions_strings.grd
+++ b/extensions/strings/extensions_strings.grd
@@ -7,7 +7,7 @@
 -->
 
 <grit latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="grit/extensions_strings.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/gpu/command_buffer/client/DEPS b/gpu/command_buffer/client/DEPS
index df80219..0e93d2b2 100644
--- a/gpu/command_buffer/client/DEPS
+++ b/gpu/command_buffer/client/DEPS
@@ -6,6 +6,7 @@
   "+components/miracle_parameter",
   "+components/viz/common/resources/shared_image_format.h",
   "+components/viz/common/resources/shared_image_format_utils.h",
+  "+mojo/public/cpp/bindings/pending_remote.h",
 ]
 
 specific_include_rules = {
diff --git a/gpu/command_buffer/client/shared_image_interface.cc b/gpu/command_buffer/client/shared_image_interface.cc
index 8cd91d29..977a241 100644
--- a/gpu/command_buffer/client/shared_image_interface.cc
+++ b/gpu/command_buffer/client/shared_image_interface.cc
@@ -180,6 +180,17 @@
 }
 #endif  // BUILDFLAG(IS_WIN)
 
+void SharedImageInterface::CreateSharedImagePool(
+    const SharedImagePoolId& pool_id,
+    mojo::PendingRemote<mojom::SharedImagePoolClientInterface> client_remote) {
+  NOTREACHED();
+}
+
+void SharedImageInterface::DestroySharedImagePool(
+    const SharedImagePoolId& pool_id) {
+  NOTREACHED();
+}
+
 SharedImageInterface::SharedImageMapping::SharedImageMapping() = default;
 SharedImageInterface::SharedImageMapping::SharedImageMapping(
     SharedImageInterface::SharedImageMapping&& mapped) = default;
diff --git a/gpu/command_buffer/client/shared_image_interface.h b/gpu/command_buffer/client/shared_image_interface.h
index 32e1aa0..5a3f12a 100644
--- a/gpu/command_buffer/client/shared_image_interface.h
+++ b/gpu/command_buffer/client/shared_image_interface.h
@@ -18,7 +18,9 @@
 #include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/command_buffer/common/sync_token.h"
 #include "gpu/gpu_export.h"
+#include "gpu/ipc/common/shared_image_pool_client_interface.mojom.h"
 #include "gpu/ipc/common/surface_handle.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/skia/include/core/SkImageInfo.h"
 #include "third_party/skia/include/gpu/ganesh/GrTypes.h"
 #include "ui/gfx/buffer_types.h"
@@ -411,6 +413,18 @@
 
   void Release() const;
 
+  // Used by client side shared image pool aka SharedImagePool to
+  // create a service side pool. It also creates a new mojo IPC connection
+  // between the client and the service side pool so that service side pool
+  // can communicate with client side pool when needed.
+  virtual void CreateSharedImagePool(
+      const SharedImagePoolId& pool_id,
+      mojo::PendingRemote<mojom::SharedImagePoolClientInterface> client_remote);
+
+  // Called when client side SharedImagePool is destroyed. It will
+  // in turn destroy the corresponding GPU service side SharedImagePool.
+  virtual void DestroySharedImagePool(const SharedImagePoolId& pool_id);
+
  protected:
   friend class base::RefCountedThreadSafe<SharedImageInterface>;
   virtual ~SharedImageInterface();
diff --git a/gpu/ipc/common/BUILD.gn b/gpu/ipc/common/BUILD.gn
index d3f2d2d..2a1f8b0 100644
--- a/gpu/ipc/common/BUILD.gn
+++ b/gpu/ipc/common/BUILD.gn
@@ -125,6 +125,7 @@
     ":gpu_peak_memory",
     ":interfaces_cpp_sources",
     ":memory_stats_sources",
+    ":shared_image_pool_client_interface",
     ":surface_handle_type",
     ":vulkan_ycbcr_info",
     "//ipc",
@@ -306,6 +307,19 @@
   ]
 }
 
+mojom("shared_image_pool_client_interface") {
+  generate_java = true
+  visibility = [
+    "//gpu/*",
+    "//services/*",
+  ]
+  sources = [ "shared_image_pool_client_interface.mojom" ]
+  deps = [ "//mojo/public/mojom/base" ]
+  export_class_attribute = "GPU_EXPORT"
+  export_header = "gpu/gpu_export.h"
+  cpp_configs = [ "//gpu:gpu_implementation" ]
+}
+
 mojom("surface_handle") {
   generate_java = true
   sources = [ "surface_handle.mojom" ]
diff --git a/gpu/ipc/common/shared_image_pool_client_interface.mojom b/gpu/ipc/common/shared_image_pool_client_interface.mojom
new file mode 100644
index 0000000..efbd87c3
--- /dev/null
+++ b/gpu/ipc/common/shared_image_pool_client_interface.mojom
@@ -0,0 +1,12 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module gpu.mojom;
+
+// Interface used by the GPU process to notify the client/renderer side
+// SharedImagePool instance to clear/purge the pool.
+interface SharedImagePoolClientInterface {
+  // Currently empty and methods will be added in future CLs along with its
+  // implementations.
+};
diff --git a/headless/lib/browser/protocol/target_handler.cc b/headless/lib/browser/protocol/target_handler.cc
index b19f063..a5f67a8 100644
--- a/headless/lib/browser/protocol/target_handler.cc
+++ b/headless/lib/browser/protocol/target_handler.cc
@@ -32,7 +32,6 @@
     std::optional<int> top,
     std::optional<int> width,
     std::optional<int> height,
-    std::optional<std::string> window_state,
     std::optional<std::string> context_id,
     std::optional<bool> enable_begin_frame_control,
     std::optional<bool> new_window,
diff --git a/headless/lib/browser/protocol/target_handler.h b/headless/lib/browser/protocol/target_handler.h
index e162d88..33204fb 100644
--- a/headless/lib/browser/protocol/target_handler.h
+++ b/headless/lib/browser/protocol/target_handler.h
@@ -33,7 +33,6 @@
                         std::optional<int> top,
                         std::optional<int> width,
                         std::optional<int> height,
-                        std::optional<std::string> window_state,
                         std::optional<std::string> context_id,
                         std::optional<bool> enable_begin_frame_control,
                         std::optional<bool> new_window,
diff --git a/ios_internal b/ios_internal
index 53efd84..a48abcf 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 53efd8418bb70bf06616f417a7090f9d3b659343
+Subproject commit a48abcfc293d8e695d52adafe5a76954066a0ab8
diff --git a/media/gpu/sandbox/hardware_video_decoding_sandbox_hook_linux.cc b/media/gpu/sandbox/hardware_video_decoding_sandbox_hook_linux.cc
index 170568a..1500055 100644
--- a/media/gpu/sandbox/hardware_video_decoding_sandbox_hook_linux.cc
+++ b/media/gpu/sandbox/hardware_video_decoding_sandbox_hook_linux.cc
@@ -24,10 +24,10 @@
 // to exist only in those configurations so that the presandbox hook is only
 // compiled in those scenarios. As it is now, kHardwareVideoDecoding exists for
 // all ash-chrome builds because
-// chrome/browser/ash/arc/video/gpu_arc_video_service_host.cc depends on it and
-// that file is built for ash-chrome regardless of VA-API/V4L2. That means that
-// bots like linux-chromeos-rel end up compiling this presandbox hook (thus the
-// NOTREACHED()s in some places here).
+// chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.cc depends on
+// it and that file is built for ash-chrome regardless of VA-API/V4L2. That
+// means that bots like linux-chromeos-rel end up compiling this presandbox hook
+// (thus the NOTREACHED()s in some places here).
 
 namespace media {
 namespace {
diff --git a/media/gpu/sandbox/hardware_video_encoding_sandbox_hook_linux.cc b/media/gpu/sandbox/hardware_video_encoding_sandbox_hook_linux.cc
index 2154e19..97269b9 100644
--- a/media/gpu/sandbox/hardware_video_encoding_sandbox_hook_linux.cc
+++ b/media/gpu/sandbox/hardware_video_encoding_sandbox_hook_linux.cc
@@ -107,10 +107,10 @@
   // sandbox type to exist only in those configurations so that the presandbox
   // hook is only reached in those scenarios. As it is now,
   // kHardwareVideoEncoding exists for all ash-chrome builds because
-  // chrome/browser/ash/arc/video/gpu_arc_video_service_host.cc is expected to
-  // depend on it eventually and that file is built for ash-chrome regardless
-  // of VA-API/V4L2. That means that bots like linux-chromeos-rel would end up
-  // reaching this presandbox hook.
+  // chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.cc is
+  // expected to depend on it eventually and that file is built for ash-chrome
+  // regardless of VA-API/V4L2. That means that bots like linux-chromeos-rel
+  // would end up reaching this presandbox hook.
 #if BUILDFLAG(USE_VAAPI)
   VaapiWrapper::PreSandboxInitialization(/*allow_disabling_global_lock=*/true);
 
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index 7f03ff6..5a2318b 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -64,6 +64,7 @@
 #include "base/metrics/field_trial.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/rand_util.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
@@ -71,6 +72,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
+#include "base/timer/elapsed_timer.h"
 #include "net/base/isolation_info.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "net/base/schemeful_site.h"
@@ -722,6 +724,11 @@
     GetCookieListCallback callback) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
+  std::optional<base::ElapsedTimer> timer;
+  if (get_cookie_list_timing_subsampler_.ShouldSample(0.001)) {
+    timer.emplace();
+  }
+
   CookieAccessResultList included_cookies;
   CookieAccessResultList excluded_cookies;
   if (HasCookieableScheme(url)) {
@@ -767,6 +774,12 @@
 
   MaybeRunCookieCallback(std::move(callback), included_cookies,
                          excluded_cookies);
+
+  if (timer) {
+    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
+        "Cookie.GetCookieListWithOptions.Duration", timer->Elapsed(),
+        base::Microseconds(1), base::Milliseconds(128), 100);
+  }
 }
 
 void CookieMonster::DeleteAllCreatedInTimeRange(const TimeRange& creation_range,
diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h
index 5a81a20..54530a6 100644
--- a/net/cookies/cookie_monster.h
+++ b/net/cookies/cookie_monster.h
@@ -24,6 +24,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "base/rand_util.h"
 #include "base/thread_annotations.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
@@ -808,6 +809,8 @@
 
   bool persist_session_cookies_ = false;
 
+  base::MetricsSubSampler get_cookie_list_timing_subsampler_;
+
   THREAD_CHECKER(thread_checker_);
 
   base::WeakPtrFactory<CookieMonster> weak_ptr_factory_{this};
diff --git a/remoting/proto/control.proto b/remoting/proto/control.proto
index e1223fa..242fcd4 100644
--- a/remoting/proto/control.proto
+++ b/remoting/proto/control.proto
@@ -143,6 +143,9 @@
   optional int32 x_dpi = 6;
   optional int32 y_dpi = 7;
 
+  // Display name. This is displayed in the client UI and is informational only.
+  optional string display_name = 9;
+
   // TODO(yuweih): Add bpp to the message.
 }
 
diff --git a/sandbox/policy/linux/bpf_hardware_video_decoding_policy_linux.cc b/sandbox/policy/linux/bpf_hardware_video_decoding_policy_linux.cc
index 8eab2981..e15be33 100644
--- a/sandbox/policy/linux/bpf_hardware_video_decoding_policy_linux.cc
+++ b/sandbox/policy/linux/bpf_hardware_video_decoding_policy_linux.cc
@@ -44,9 +44,9 @@
   // sandbox type to exist only in those configurations so that the
   // HardwareVideoDecodingProcessPolicy is only compiled in those scenarios. As
   // it is now, kHardwareVideoDecoding exists for all ash-chrome builds because
-  // chrome/browser/ash/arc/video/gpu_arc_video_service_host.cc depends on it
-  // and that file is built for ash-chrome regardless of VA-API/V4L2. That means
-  // that bots like linux-chromeos-rel end up compiling this policy.
+  // chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.cc depends on
+  // it and that file is built for ash-chrome regardless of VA-API/V4L2. That
+  // means that bots like linux-chromeos-rel end up compiling this policy.
   NOTREACHED();
 #endif
 }
diff --git a/storage/browser/file_system/file_system_quota_client.cc b/storage/browser/file_system/file_system_quota_client.cc
index d9a8e6de..fa61ce1 100644
--- a/storage/browser/file_system/file_system_quota_client.cc
+++ b/storage/browser/file_system/file_system_quota_client.cc
@@ -58,7 +58,8 @@
     merged.insert(merged.end(), ts.begin(), ts.end());
   }
   base::ranges::sort(merged);
-  merged.erase(base::ranges::unique(merged), merged.end());
+  auto repeated = std::ranges::unique(merged);
+  merged.erase(repeated.begin(), repeated.end());
   return merged;
 }
 
diff --git a/third_party/angle b/third_party/angle
index 4c60a30..b3af2e8 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 4c60a308592af56dc745d8d1ba994e89ebb053db
+Subproject commit b3af2e86f4b75b3c8f8053175fe97c0372926675
diff --git a/third_party/barhopper b/third_party/barhopper
index 865bd06..9230af4 160000
--- a/third_party/barhopper
+++ b/third_party/barhopper
@@ -1 +1 @@
-Subproject commit 865bd06ef4a839b0a15d17e38e25f8911e4cdf9f
+Subproject commit 9230af4dc38c6d2cc9c0841692267762ebfca991
diff --git a/third_party/blink/common/origin_trials/trial_token.cc b/third_party/blink/common/origin_trials/trial_token.cc
index b87ffec..9d4153f 100644
--- a/third_party/blink/common/origin_trials/trial_token.cc
+++ b/third_party/blink/common/origin_trials/trial_token.cc
@@ -192,14 +192,14 @@
     return nullptr;
   }
 
-  std::optional<base::Value> data = base::JSONReader::Read(token_payload);
-  if (!data || !data->is_dict()) {
+  std::optional<base::Value::Dict> data =
+      base::JSONReader::ReadDict(token_payload);
+  if (!data) {
     return nullptr;
   }
-  base::Value::Dict& datadict = data->GetDict();
 
   // Ensure that the origin is a valid (non-opaque) origin URL.
-  std::string* origin_string = datadict.FindString("origin");
+  std::string* origin_string = data->FindString("origin");
   if (!origin_string) {
     return nullptr;
   }
@@ -210,7 +210,7 @@
 
   // The |isSubdomain| flag is optional. If found, ensure it is a valid boolean.
   bool is_subdomain = false;
-  base::Value* is_subdomain_value = datadict.Find("isSubdomain");
+  base::Value* is_subdomain_value = data->Find("isSubdomain");
   if (is_subdomain_value) {
     if (!is_subdomain_value->is_bool()) {
       return nullptr;
@@ -219,13 +219,13 @@
   }
 
   // Ensure that the feature name is a valid string.
-  std::string* feature_name = datadict.FindString("feature");
+  std::string* feature_name = data->FindString("feature");
   if (!feature_name || feature_name->empty()) {
     return nullptr;
   }
 
   // Ensure that the expiry timestamp is a valid (positive) integer.
-  int expiry_timestamp = datadict.FindInt("expiry").value_or(0);
+  int expiry_timestamp = data->FindInt("expiry").value_or(0);
   if (expiry_timestamp <= 0) {
     return nullptr;
   }
@@ -237,7 +237,7 @@
   if (version == kVersion3) {
     // The |isThirdParty| flag is optional. If found, ensure it is a valid
     // boolean.
-    base::Value* is_third_party_value = datadict.Find("isThirdParty");
+    base::Value* is_third_party_value = data->Find("isThirdParty");
     if (is_third_party_value) {
       if (!is_third_party_value->is_bool()) {
         return nullptr;
@@ -247,7 +247,7 @@
 
     // The |usage| field is optional. If found, ensure its value is either empty
     // or "subset".
-    std::string* usage_value = datadict.FindString("usage");
+    std::string* usage_value = data->FindString("usage");
     if (usage_value) {
       if (usage_value->empty()) {
         usage = UsageRestriction::kNone;
diff --git a/third_party/blink/common/widget/visual_properties.cc b/third_party/blink/common/widget/visual_properties.cc
index c9cf3aa1..135a690a 100644
--- a/third_party/blink/common/widget/visual_properties.cc
+++ b/third_party/blink/common/widget/visual_properties.cc
@@ -19,7 +19,7 @@
          auto_resize_enabled == other.auto_resize_enabled &&
          min_size_for_auto_resize == other.min_size_for_auto_resize &&
          max_size_for_auto_resize == other.max_size_for_auto_resize &&
-         new_size_device_px == other.new_size_device_px &&
+         new_size == other.new_size &&
          visible_viewport_size == other.visible_viewport_size &&
          compositor_viewport_pixel_rect ==
              other.compositor_viewport_pixel_rect &&
diff --git a/third_party/blink/common/widget/visual_properties_mojom_traits.cc b/third_party/blink/common/widget/visual_properties_mojom_traits.cc
index 79854c13..5a47c97 100644
--- a/third_party/blink/common/widget/visual_properties_mojom_traits.cc
+++ b/third_party/blink/common/widget/visual_properties_mojom_traits.cc
@@ -18,7 +18,7 @@
   if (!data.ReadScreenInfos(&out->screen_infos) ||
       !data.ReadMinSizeForAutoResize(&out->min_size_for_auto_resize) ||
       !data.ReadMaxSizeForAutoResize(&out->max_size_for_auto_resize) ||
-      !data.ReadNewSize(&out->new_size_device_px) ||
+      !data.ReadNewSize(&out->new_size) ||
       !data.ReadVisibleViewportSize(&out->visible_viewport_size) ||
       !data.ReadCompositorViewportPixelRect(
           &out->compositor_viewport_pixel_rect) ||
diff --git a/third_party/blink/public/common/widget/visual_properties.h b/third_party/blink/public/common/widget/visual_properties.h
index a020546..628ce647 100644
--- a/third_party/blink/public/common/widget/visual_properties.h
+++ b/third_party/blink/public/common/widget/visual_properties.h
@@ -64,8 +64,8 @@
   // The maximum size for Blink if auto-resize is enabled.
   gfx::Size max_size_for_auto_resize;
 
-  // The size for the widget, in device pixels.
-  gfx::Size new_size_device_px;
+  // The size for the widget in DIPs.
+  gfx::Size new_size;
 
   // The size of the area of the widget that is visible to the user, in DIPs.
   // The visible area may be empty if the visible area does not intersect with
@@ -75,17 +75,15 @@
   // as with an on-screen keyboard.
   gfx::Size visible_viewport_size;
 
-  // The rect of compositor's viewport in device pixels. Note that for top level
-  // widgets this is the same as |new_size| (when UseDevicePixelsForWidgetSizing
-  // is on; otherwise different by device pixel ratio) except that on Android
-  // it includes the size of the browser controls. For child frame widgets it
-  // is a pixel-perfect bounds of the visible region of the widget. The size
-  // would be similar to visible_viewport_size, but in device pixels and
-  // computed via very different means. TODO(danakj): It would be super nice to
-  // remove one of |new_size|, |visible_viewport_size| and
-  // |compositor_viewport_pixel_rect|. Their values overlap in purpose,
-  // creating a very confusing situation about which to use for what, and how
-  // they should relate or not.
+  // The rect of compositor's viewport in pixels. Note that for top level
+  // widgets this is roughly the DSF scaled new_size put into a rect. For child
+  // frame widgets it is a pixel-perfect bounds of the visible region of the
+  // widget. The size would be similar to visible_viewport_size, but in physical
+  // pixels and computed via very different means.
+  // TODO(danakj): It would be super nice to remove one of |new_size|,
+  // |visible_viewport_size| and |compositor_viewport_pixel_rect|. Their values
+  // overlap in purpose, creating a very confusing situation about which to use
+  // for what, and how they should relate or not.
   gfx::Rect compositor_viewport_pixel_rect;
 
   // Browser controls params such as top and bottom controls heights, whether
@@ -93,9 +91,8 @@
   cc::BrowserControlsParams browser_controls_params;
 
   // If shown and resizing the renderer, returns the height of the virtual
-  // keyboard in device pixels. Otherwise, returns 0. Always 0 in a
+  // keyboard in physical pixels. Otherwise, returns 0. Always 0 in a
   // non-outermost main frame.
-  // TODO(chrishtr): rename to virtual_keyboard_resize_height_device_px.
   int virtual_keyboard_resize_height_physical_px = 0;
 
   // Whether or not the focused node should be scrolled into view after the
diff --git a/third_party/blink/public/common/widget/visual_properties_mojom_traits.h b/third_party/blink/public/common/widget/visual_properties_mojom_traits.h
index 030373a4..aedd7b0 100644
--- a/third_party/blink/public/common/widget/visual_properties_mojom_traits.h
+++ b/third_party/blink/public/common/widget/visual_properties_mojom_traits.h
@@ -42,7 +42,7 @@
   }
 
   static const gfx::Size& new_size(const blink::VisualProperties& r) {
-    return r.new_size_device_px;
+    return r.new_size;
   }
 
   static const gfx::Size& visible_viewport_size(
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index e10e5495..583cc9fe 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -11174,14 +11174,6 @@
       string host
       integer port
 
-  # The state of the target window.
-  experimental type WindowState extends string
-    enum
-      normal
-      minimized
-      maximized
-      fullscreen
-
   # Activates (focuses) the target.
   command activateTarget
     parameters
@@ -11263,9 +11255,6 @@
       optional integer width
       # Frame height in DIP (requires newWindow to be true or headless shell).
       optional integer height
-      # Frame window state (requires newWindow to be true or headless shell).
-      # Default is normal.
-      optional WindowState windowState
       # The browser context to create the page in.
       experimental optional Browser.BrowserContextID browserContextId
       # Whether BeginFrames for this target will be controlled via DevTools (headless shell only,
diff --git a/third_party/blink/renderer/core/accessibility/ax_object_cache.h b/third_party/blink/renderer/core/accessibility/ax_object_cache.h
index c96abe1..b0104dff 100644
--- a/third_party/blink/renderer/core/accessibility/ax_object_cache.h
+++ b/third_party/blink/renderer/core/accessibility/ax_object_cache.h
@@ -56,6 +56,7 @@
 class HTMLOptionElement;
 class HTMLFrameOwnerElement;
 class HTMLSelectElement;
+class LayoutBlockFlow;
 struct PhysicalRect;
 class WebPluginContainer;
 
@@ -266,6 +267,9 @@
   // Determine if a serialization is in the process or not.
   virtual bool IsSerializationInFlight() const = 0;
 
+  // Clears the cached fragment data associated with `block_flow` if it exists.
+  virtual void ClearBlockFlowCachedData(const LayoutBlockFlow* block_flow) = 0;
+
  protected:
   friend class ScopedBlinkAXEventIntent;
   FRIEND_TEST_ALL_PREFIXES(ScopedBlinkAXEventIntentTest, SingleIntent);
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index a2608eb..038e786 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -926,7 +926,7 @@
   widget_base_->LayerTreeHost()->SetExternalPageScaleFactor(
       combined_scale_factor, visual_properties.is_pinch_gesture_active);
 
-  Resize(visual_properties.new_size_device_px);
+  Resize(widget_base_->DIPsToCeiledBlinkSpace(visual_properties.new_size));
 }
 
 gfx::Rect WebPagePopupImpl::ViewportVisibleRect() {
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 59722f2..9b2d6b0f 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -6298,7 +6298,7 @@
 
   blink::VisualProperties visual_properties;
   visual_properties.screen_infos = display::ScreenInfos(display::ScreenInfo());
-  visual_properties.new_size_device_px = gfx::Size(400, 300);
+  visual_properties.new_size = gfx::Size(400, 300);
   visual_properties.visible_viewport_size = gfx::Size(400, 300);
   visual_properties.screen_infos.mutable_current().rect = gfx::Rect(800, 600);
 
diff --git a/third_party/blink/renderer/core/frame/screen_metrics_emulator.cc b/third_party/blink/renderer/core/frame/screen_metrics_emulator.cc
index 7077759..31625e4 100644
--- a/third_party/blink/renderer/core/frame/screen_metrics_emulator.cc
+++ b/third_party/blink/renderer/core/frame/screen_metrics_emulator.cc
@@ -178,7 +178,7 @@
   DCHECK(!frame_widget_->AutoResizeMode());
 
   original_screen_infos_ = visual_properties.screen_infos;
-  original_widget_size_ = visual_properties.new_size_device_px;
+  original_widget_size_ = visual_properties.new_size;
   original_visible_viewport_size_ = visual_properties.visible_viewport_size;
   original_root_viewport_segments_ =
       visual_properties.root_widget_viewport_segments;
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index 4edbb7e..abc5319 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -1976,20 +1976,9 @@
     const VisualProperties& visual_properties) {
   gfx::Rect new_compositor_viewport_pixel_rect =
       visual_properties.compositor_viewport_pixel_rect;
-  gfx::Size new_size;
-  new_size = visual_properties.new_size_device_px;
-  if (device_scale_factor_for_testing_) {
-    DCHECK(non_testing_device_scale_factor_);
-    new_size =
-        gfx::ScaleToFlooredSize(new_size, device_scale_factor_for_testing_ /
-                                              non_testing_device_scale_factor_);
-    new_compositor_viewport_pixel_rect = gfx::ScaleToEnclosedRect(
-        new_compositor_viewport_pixel_rect,
-        device_scale_factor_for_testing_ / non_testing_device_scale_factor_);
-  }
-
   if (ForMainFrame()) {
-    if (size_ != new_size) {
+    if (size_ !=
+        widget_base_->DIPsToCeiledBlinkSpace(visual_properties.new_size)) {
       // Only hide popups when the size changes. Eg https://crbug.com/761908.
       View()->CancelPagePopup();
     }
@@ -2024,7 +2013,7 @@
 
   if (ForMainFrame()) {
     if (!AutoResizeMode()) {
-      size_ = new_size;
+      size_ = widget_base_->DIPsToCeiledBlinkSpace(visual_properties.new_size);
 
       View()->ResizeWithBrowserControls(
           size_.value(),
@@ -2041,18 +2030,17 @@
   } else {
     // Widgets in a WebView's frame tree without a local main frame
     // set the size of the WebView to be the |visible_viewport_size|, in order
-    // to limit compositing in (out of process) child frames to what is
-    // visible.
+    // to limit compositing in (out of process) child frames to what is visible.
     //
     // Note that child frames in the same process/WebView frame tree as the
-    // main frame do not do this in order to not clobber the source of truth
-    // in the main frame.
+    // main frame do not do this in order to not clobber the source of truth in
+    // the main frame.
     if (!View()->MainFrameImpl()) {
       View()->Resize(widget_base_->DIPsToCeiledBlinkSpace(
           widget_base_->VisibleViewportSizeInDIPs()));
     }
 
-    Resize(new_size);
+    Resize(widget_base_->DIPsToCeiledBlinkSpace(visual_properties.new_size));
   }
 }
 
@@ -5111,11 +5099,6 @@
   DCHECK(ForMainFrame());
   DCHECK_GE(factor, 0.f);
 
-  if (!device_scale_factor_for_testing_) {
-    non_testing_device_scale_factor_ =
-        widget_base_->GetOriginalDeviceScaleFactor();
-  }
-
   // Stash the window size before we adjust the scale factor, as subsequent
   // calls to convert will use the new scale factor.
   gfx::Size size_in_dips = widget_base_->BlinkSpaceToFlooredDIPs(Size());
@@ -5123,10 +5106,8 @@
 
   // Receiving a 0 is used to reset between tests, it removes the override in
   // order to listen to the browser for the next test.
-  if (!factor) {
-    non_testing_device_scale_factor_ = 0;
+  if (!factor)
     return;
-  }
 
   // We are changing the device scale factor from the renderer, so allocate a
   // new viz::LocalSurfaceId to avoid surface invariants violations in tests.
@@ -5140,7 +5121,7 @@
   if (!AutoResizeMode()) {
     // This picks up the new device scale factor as
     // `UpdateCompositorViewportAndScreenInfo()` has applied a new value.
-    Resize(size_with_dsf);
+    Resize(widget_base_->DIPsToCeiledBlinkSpace(size_in_dips));
   }
 }
 
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index 3dd204c..69febe3 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -666,8 +666,8 @@
       bool enabled,
       const blink::DeviceEmulationParams& params);
   void SetScreenInfoAndSize(const display::ScreenInfos& screen_infos,
-                            const gfx::Size& widget_size_in_dips,
-                            const gfx::Size& visible_viewport_size_in_dips);
+                            const gfx::Size& widget_size,
+                            const gfx::Size& visible_viewport_size);
 
   // Update the surface allocation information, compositor viewport rect and
   // screen info on the widget.
@@ -1212,11 +1212,6 @@
   // frame widgets.
   float device_scale_factor_for_testing_ = 0;
 
-  // When device_scale_factor_for_testing_ is set (i.e. nonzero), this
-  // stores the device scale factor before the testing override was set.
-  // Otherwise it is set to zero.
-  float non_testing_device_scale_factor_ = 0;
-
   // This struct contains data that is only valid for main frame widgets.
   // You should use `main_data()` to access it.
   struct MainFrameData {
diff --git a/third_party/blink/renderer/core/html/forms/option_list.cc b/third_party/blink/renderer/core/html/forms/option_list.cc
index 83fa3b0..2bb8c5b 100644
--- a/third_party/blink/renderer/core/html/forms/option_list.cc
+++ b/third_party/blink/renderer/core/html/forms/option_list.cc
@@ -71,7 +71,9 @@
     }
 
     if (RuntimeEnabledFeatures::SelectParserRelaxationEnabled()) {
-      if (IsA<HTMLSelectElement>(current)) {
+      if (current == select_) {
+        current = nullptr;
+      } else if (IsA<HTMLSelectElement>(current)) {
         current = ElementTraversal::PreviousAbsoluteSibling(*next, &select_);
       } else {
         current = ElementTraversal::Previous(*current, &select_);
diff --git a/third_party/blink/renderer/core/html/forms/option_list_test.cc b/third_party/blink/renderer/core/html/forms/option_list_test.cc
index 8a5110d..9289f45 100644
--- a/third_party/blink/renderer/core/html/forms/option_list_test.cc
+++ b/third_party/blink/renderer/core/html/forms/option_list_test.cc
@@ -95,4 +95,13 @@
   EXPECT_EQ("gg11", Id(*iter2)) << "Nested OPTGROUP should be included.";
 }
 
+TEST_F(OptionListTest, RetreatBeforeBeginning) {
+  Select().setInnerHTML("<button>button</button><option id=o1>option</option>");
+  OptionList list = Select().GetOptionList();
+  OptionList::Iterator it = list.begin();
+  EXPECT_EQ("o1", Id(*it));
+  --it;
+  EXPECT_EQ(nullptr, *it);
+}
+
 }  // naemespace blink
diff --git a/third_party/blink/renderer/core/html/forms/resources/list_picker.js b/third_party/blink/renderer/core/html/forms/resources/list_picker.js
index ca121f5..7824f42c 100644
--- a/third_party/blink/renderer/core/html/forms/resources/list_picker.js
+++ b/third_party/blink/renderer/core/html/forms/resources/list_picker.js
@@ -334,6 +334,13 @@
     this.selectElement_.style.fontVariant = this.config_.baseStyle.fontVariant;
     if (this.config_.baseStyle.textAlign)
       this.selectElement_.style.textAlign = this.config_.baseStyle.textAlign;
+
+    // updateChildren_ takes longer when there are existing elements, so remove
+    // them to make it faster.
+    // TODO(crbug.com/388557894): Remove this after improving the performance
+    // of updateChildren_.
+    this.selectElement_.innerHTML = '';
+
     this.updateChildren_(this.selectElement_, this.config_);
     this.setMenuListOptionsBoundsInAXTree_();
   }
@@ -360,6 +367,8 @@
   }
 
   /**
+   * TODO(crbug.com/388557894): Make this faster in the case that `parent` has
+   * a large number of children.
    * @param {!Element} parent Select element or optgroup element.
    * @param {!Object} config
    */
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc
index 795ec912..6dffb46 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element.cc
@@ -3913,11 +3913,14 @@
     StartPlaybackProgressTimer();
     playing_ = true;
   } else {  // Should not be playing right now
-    if (is_playing) {
+    // Always tell WMP about the pause since it may need to clear a pending
+    // automatic playback resumption.
+    if (web_media_player_ && ready_state_ >= kHaveMetadata) {
       web_media_player_->Pause();
-
-      if (pause_speech && ::features::IsTextBasedAudioDescriptionEnabled())
-        SpeechSynthesis()->Pause();
+    }
+    if (is_playing && pause_speech &&
+        ::features::IsTextBasedAudioDescriptionEnabled()) {
+      SpeechSynthesis()->Pause();
     }
 
     playback_progress_timer_.Stop();
diff --git a/third_party/blink/renderer/core/html/media/html_media_element_test.cc b/third_party/blink/renderer/core/html/media/html_media_element_test.cc
index 5b56a767..ddf1a8b 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element_test.cc
@@ -84,6 +84,7 @@
 
 class MockWebMediaPlayer : public EmptyWebMediaPlayer {
  public:
+  MOCK_METHOD0(Pause, void());
   MOCK_METHOD0(OnTimeUpdate, void());
   MOCK_CONST_METHOD0(Seekable, WebTimeRanges());
   MOCK_METHOD0(OnFrozen, void());
@@ -1185,6 +1186,27 @@
   Media()->Play();
 }
 
+// Test ensures that WebMediaPlayer is always told about pause events,
+// even if already paused.
+TEST_P(HTMLMediaElementTest, WebMediaPlayerIsPaused) {
+  // Prepare the player.
+  Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
+  test::RunPendingTasks();
+
+  // Prior to HaveMetadat, Play/Pause won't be delivered to the player.
+  EXPECT_CALL(*MockMediaPlayer(), Pause()).Times(0);
+  Media()->Play();
+  Media()->pause();
+  testing::Mock::VerifyAndClearExpectations(MockMediaPlayer());
+
+  // After metadata pause should be delivered even if already paused.
+  SetReadyState(HTMLMediaElement::kHaveMetadata);
+  EXPECT_CALL(*MockMediaPlayer(), Pause()).Times(2);
+  Media()->pause();
+  Media()->pause();
+  testing::Mock::VerifyAndClearExpectations(MockMediaPlayer());
+}
+
 TEST_P(HTMLMediaElementTest, OnTimeUpdate_ReadyState) {
   // Prepare the player.
   Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
diff --git a/third_party/blink/renderer/core/inspector/dev_tools_host.cc b/third_party/blink/renderer/core/inspector/dev_tools_host.cc
index b1051d2..e9ce363f 100644
--- a/third_party/blink/renderer/core/inspector/dev_tools_host.cc
+++ b/third_party/blink/renderer/core/inspector/dev_tools_host.cc
@@ -171,19 +171,18 @@
 void DevToolsHost::sendMessageToEmbedder(const String& message) {
   if (client_) {
     // Strictly convert, as we expect message to be serialized JSON.
-    auto value =
-        base::JSONReader::Read(message.Utf8(WTF::Utf8ConversionMode::kStrict));
-    if (!value || !value->is_dict()) {
+    auto value = base::JSONReader::ReadDict(
+        message.Utf8(WTF::Utf8ConversionMode::kStrict));
+    if (!value) {
       ScriptState* script_state = ToScriptStateForMainWorld(frontend_frame_);
       if (!script_state)
         return;
       V8ThrowException::ThrowTypeError(
           script_state->GetIsolate(),
-          value ? "Message to embedder must deserialize to a dictionary value"
-                : "Message to embedder couldn't be JSON-deserialized");
+          "Message to embedder couldn't be deserialized as a JSON object");
       return;
     }
-    client_->SendMessageToEmbedder(std::move(*value).TakeDict());
+    client_->SendMessageToEmbedder(std::move(*value));
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/inline/fragment_items.cc b/third_party/blink/renderer/core/layout/inline/fragment_items.cc
index 8e26fc7..57e011d 100644
--- a/third_party/blink/renderer/core/layout/inline/fragment_items.cc
+++ b/third_party/blink/renderer/core/layout/inline/fragment_items.cc
@@ -60,7 +60,7 @@
     // |FragmentItem|.
     if (auto* layout_text =
             DynamicTo<LayoutText>(other_item.GetMutableLayoutObject()))
-      layout_text->DetachAbstractInlineTextBoxesIfNeeded();
+      layout_text->DetachAxHooksIfNeeded();
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_object_child_list.cc b/third_party/blink/renderer/core/layout/layout_object_child_list.cc
index 213d0b8..18927212 100644
--- a/third_party/blink/renderer/core/layout/layout_object_child_list.cc
+++ b/third_party/blink/renderer/core/layout/layout_object_child_list.cc
@@ -64,7 +64,7 @@
   // be prohibited when moved to different parent as if it were destroyed.
   if (object->FirstInlineFragmentItemIndex()) {
     if (auto* text = DynamicTo<LayoutText>(object))
-      text->DetachAbstractInlineTextBoxesIfNeeded();
+      text->DetachAxHooksIfNeeded();
     FragmentItems::LayoutObjectWillBeMoved(*object);
   }
   object->SetIsInLayoutNGInlineFormattingContext(false);
diff --git a/third_party/blink/renderer/core/layout/layout_text.cc b/third_party/blink/renderer/core/layout/layout_text.cc
index 2a5f9e5..ef7fe3b 100644
--- a/third_party/blink/renderer/core/layout/layout_text.cc
+++ b/third_party/blink/renderer/core/layout/layout_text.cc
@@ -260,12 +260,12 @@
       Parent()->DirtyLinesFromChangedChild(this);
     }
     if (FirstInlineFragmentItemIndex()) {
-      DetachAbstractInlineTextBoxesIfNeeded();
+      DetachAxHooksIfNeeded();
       FragmentItems::LayoutObjectWillBeDestroyed(*this);
       ClearFirstInlineFragmentItemIndex();
     }
   } else if (FirstInlineFragmentItemIndex()) {
-    DetachAbstractInlineTextBoxesIfNeeded();
+    DetachAxHooksIfNeeded();
     ClearFirstInlineFragmentItemIndex();
   }
   DeleteTextBoxes();
@@ -297,10 +297,10 @@
 
 void LayoutText::DeleteTextBoxes() {
   NOT_DESTROYED();
-  DetachAbstractInlineTextBoxesIfNeeded();
+  DetachAxHooksIfNeeded();
 }
 
-void LayoutText::DetachAbstractInlineTextBoxes() {
+void LayoutText::DetachAxHooks() {
   NOT_DESTROYED();
   // TODO(layout-dev): Because We should call |WillDestroy()| once for
   // associated fragments, when you reuse fragments, you should construct
@@ -310,14 +310,21 @@
   // TODO(yosin): Make sure we call this function within valid containg block
   // of |this|.
   InlineCursor cursor;
-  for (cursor.MoveTo(*this); cursor; cursor.MoveToNextForSameLayoutObject())
+  for (cursor.MoveTo(*this); cursor; cursor.MoveToNextForSameLayoutObject()) {
     AbstractInlineTextBox::WillDestroy(cursor);
+  }
+}
+
+void LayoutText::ClearBlockFlowCachedData(const LayoutBlockFlow* block_flow) {
+  if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) {
+    cache->ClearBlockFlowCachedData(FragmentItemsContainer());
+  }
 }
 
 void LayoutText::ClearFirstInlineFragmentItemIndex() {
   NOT_DESTROYED();
   CHECK(IsInLayoutNGInlineFormattingContext()) << *this;
-  DetachAbstractInlineTextBoxesIfNeeded();
+  DetachAxHooksIfNeeded();
   first_fragment_item_index_ = 0u;
 }
 
@@ -326,7 +333,7 @@
   CHECK(IsInLayoutNGInlineFormattingContext());
   // TODO(yosin): Call |AbstractInlineTextBox::WillDestroy()|.
   DCHECK_NE(index, 0u);
-  DetachAbstractInlineTextBoxesIfNeeded();
+  DetachAxHooksIfNeeded();
   // Changing the first fragment item index causes
   // LayoutText::FirstAbstractInlineTextBox to return a box,
   // so notify the AX object for this LayoutText that it might need to
diff --git a/third_party/blink/renderer/core/layout/layout_text.h b/third_party/blink/renderer/core/layout/layout_text.h
index 63e4aba..8db2c13 100644
--- a/third_party/blink/renderer/core/layout/layout_text.h
+++ b/third_party/blink/renderer/core/layout/layout_text.h
@@ -332,7 +332,7 @@
 
   void InvalidateSubtreeLayoutForFontUpdates() override;
 
-  void DetachAbstractInlineTextBoxesIfNeeded();
+  void DetachAxHooksIfNeeded();
 
   // Returns the logical location of the first line box, and the logical height
   // of the LayoutText.
@@ -438,7 +438,8 @@
 
  private:
   ContentCaptureManager* GetOrResetContentCaptureManager();
-  void DetachAbstractInlineTextBoxes();
+  void DetachAxHooks();
+  void ClearBlockFlowCachedData(const LayoutBlockFlow* block_flow);
 
   virtual unsigned NonCollapsedCaretMaxOffset() const;
 
@@ -470,10 +471,15 @@
   return first_fragment_item_index_;
 }
 
-inline void LayoutText::DetachAbstractInlineTextBoxesIfNeeded() {
+inline void LayoutText::DetachAxHooksIfNeeded() {
   if (has_abstract_inline_text_box_) [[unlikely]] {
-    DetachAbstractInlineTextBoxes();
+    DetachAxHooks();
   }
+  if (!IsInLayoutNGInlineFormattingContext()) {
+    return;
+  }
+
+  ClearBlockFlowCachedData(FragmentItemsContainer());
 }
 
 template <>
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index 21aa8f4..53258335 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -5555,7 +5555,8 @@
     if (::features::IsAccessibilityBlockFlowIteratorEnabled()) {
       DCHECK(it.Next()) << "Failed to advance the BlockFlow Iterator while "
                            "processing AxInlineTextBox children of "
-                        << this;
+                        << this << " which has layout " << GetLayoutObject()
+                        << "\n and the AITB produced " << box->GetText();
 
       WTF::String fragment_text = it.GetText();
       WTF::String abstract_inline_text = box->GetText();
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index af0f071..4da4c97a 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -122,6 +122,7 @@
 #include "third_party/blink/renderer/platform/graphics/dom_node_id.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "ui/accessibility/accessibility_features.h"
 #include "ui/accessibility/ax_common.h"
 #include "ui/accessibility/ax_enums.mojom-blink.h"
 #include "ui/accessibility/ax_event.h"
@@ -2170,6 +2171,17 @@
   MarkAXObjectDirty(ax_object);
 }
 
+void AXObjectCacheImpl::ClearBlockFlowCachedData(
+    const LayoutBlockFlow* block_flow) {
+  if (!::features::IsAccessibilityBlockFlowIteratorEnabled()) {
+    return;
+  }
+  auto it = block_flow_data_cache_.find(block_flow);
+  if (it != block_flow_data_cache_.end()) {
+    block_flow_data_cache_.erase(it);
+  }
+}
+
 void AXObjectCacheImpl::CSSAnchorChanged(const LayoutObject* positioned_obj) {
   if (Node* node = positioned_obj->GetNode()) {
     DeferTreeUpdate(TreeUpdateReason::kCSSAnchorChanged, node);
@@ -3416,14 +3428,19 @@
 
 const AXBlockFlowData* AXObjectCacheImpl::GetBlockFlowData(
     const AXObject* object) {
-  // TODO: Assumption that we are only really working on one paragraph at a
-  // time turned out to be incorrect. Ideally, we can come up with a strategy
-  // to make this work in order to avoid memory bloat. For now just compute the
-  // AxBlockFlowData every time as depending on a cached version may cause
-  // problems.
   LayoutBlockFlow* block_flow =
       object->GetLayoutObject()->FragmentItemsContainer();
-  return MakeGarbageCollected<AXBlockFlowData>(block_flow);
+  if (!block_flow) {
+    return nullptr;
+  }
+  auto it = block_flow_data_cache_.find(block_flow);
+  if (it != block_flow_data_cache_.end()) {
+    return it->value;
+  }
+  auto result = block_flow_data_cache_.insert(
+      block_flow, MakeGarbageCollected<AXBlockFlowData>(block_flow));
+  CHECK(result.is_new_entry);
+  return result.stored_value->value;
 }
 
 bool AXObjectCacheImpl::IsParsingMainDocument() const {
@@ -6121,6 +6138,7 @@
   visitor->Trace(next_on_line_map_);
   visitor->Trace(processed_blocks_);
   visitor->Trace(previous_on_line_map_);
+  visitor->Trace(block_flow_data_cache_);
 
   visitor->Trace(tree_update_callback_queue_main_);
   visitor->Trace(tree_update_callback_queue_popup_);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
index 3d57cad..e0d2feb 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
@@ -274,6 +274,9 @@
   void TextChanged(const LayoutObject*) override;
   void TextChangedWithCleanLayout(Node* optional_node, AXObject*);
 
+  // Called when fragments in the LayoutBlockFlow changed.
+  void ClearBlockFlowCachedData(const LayoutBlockFlow* block_flow) override;
+
   void DocumentTitleChanged() override;
 
   // Returns true if we can immediately process tree updates for this node.
@@ -1269,6 +1272,9 @@
       previous_on_line_map_;
   HeapHashSet<Member<const LayoutBlockFlow>> processed_blocks_;
 
+  HeapHashMap<Member<const LayoutBlockFlow>, Member<AXBlockFlowData>>
+      block_flow_data_cache_;
+
   FRIEND_TEST_ALL_PREFIXES(AccessibilityTest, PauseUpdatesAfterMaxNumberQueued);
   FRIEND_TEST_ALL_PREFIXES(AccessibilityTest,
                            UpdateAXForAllDocumentsAfterPausedUpdates);
diff --git a/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.idl b/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.idl
index 4a2e4f8..95fd1660 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.idl
+++ b/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.idl
@@ -34,7 +34,7 @@
 
     // image smoothing
     [NoAllocDirectCall=Setter] attribute boolean imageSmoothingEnabled; // (default True)
-    [RuntimeEnabled=CanvasImageSmoothing, MeasureAs=Canvas2DImageSmoothingQuality2] attribute ImageSmoothingQuality imageSmoothingQuality; // (default "low")
+    [MeasureAs=Canvas2DImageSmoothingQuality2] attribute ImageSmoothingQuality imageSmoothingQuality; // (default "low")
 
     // colors and styles (see also the CanvasDrawingStyles interface)
     [HighEntropy, SetterCallWith=Isolate, RaisesException=Setter, GetterCallWith=ScriptState] attribute any strokeStyle; // (default black)
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc b/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
index e789251..2ebdb9de 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
@@ -93,16 +93,18 @@
 }
 
 std::optional<media::AudioCodec> TryGetPcmCodec(const String& codec) {
-  if (codec == "ulaw") {
+  String codecs_str = codec.LowerASCII();
+  if (codecs_str == "ulaw") {
     return media::AudioCodec::kPCM_MULAW;
   }
 
-  if (codec == "alaw") {
+  if (codecs_str == "alaw") {
     return media::AudioCodec::kPCM_ALAW;
   }
 
-  if (codec == "pcm-u8" || codec == "pcm-s16" || codec == "pcm-s24" ||
-      codec == "pcm-s32" || codec == "pcm-f32") {
+  if (codecs_str == "pcm-u8" || codecs_str == "pcm-s16" ||
+      codecs_str == "pcm-s24" || codecs_str == "pcm-s32" ||
+      codecs_str == "pcm-f32") {
     return media::AudioCodec::kPCM;
   }
 
@@ -110,29 +112,33 @@
 }
 
 media::SampleFormat PcmCodecToSampleFormat(const String& codec) {
-  CHECK(codec.StartsWith("pcm"));
+  String codecs_str = codec.LowerASCII();
 
-  if (codec == "pcm-u8") {
+  if (codecs_str == "pcm-u8") {
     return media::SampleFormat::kSampleFormatU8;
   }
 
-  if (codec == "pcm-s16") {
+  if (codecs_str == "pcm-s16") {
     return media::SampleFormat::kSampleFormatS16;
   }
 
-  if (codec == "pcm-s24") {
+  if (codecs_str == "pcm-s24") {
     return media::SampleFormat::kSampleFormatS24;
   }
 
-  if (codec == "pcm-s32") {
+  if (codecs_str == "pcm-s32") {
     return media::SampleFormat::kSampleFormatS32;
   }
 
-  if (codec == "pcm-f32") {
+  if (codecs_str == "pcm-f32") {
     return media::SampleFormat::kSampleFormatF32;
   }
 
-  return media::SampleFormat::kUnknownSampleFormat;
+  // We want to default to F32Planar for unexpected states, as well as the case
+  // of the codec being "1", which is a valid PCM codec for WAV (seen in
+  // media/base/mime_util_internal.cc). This return catches both unexpected
+  // cases and this desired case.
+  return media::SampleFormat::kSampleFormatPlanarF32;
 }
 
 // static
diff --git a/third_party/blink/renderer/platform/graphics/web_graphics_context_3d_video_frame_pool.cc b/third_party/blink/renderer/platform/graphics/web_graphics_context_3d_video_frame_pool.cc
index 63cdab1..996ca62 100644
--- a/third_party/blink/renderer/platform/graphics/web_graphics_context_3d_video_frame_pool.cc
+++ b/third_party/blink/renderer/platform/graphics/web_graphics_context_3d_video_frame_pool.cc
@@ -369,8 +369,7 @@
 
 BASE_FEATURE(kGpuMemoryBufferReadbackFromTexture,
              "GpuMemoryBufferReadbackFromTexture",
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || \
-    BUILDFLAG(IS_LINUX)
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
              base::FEATURE_ENABLED_BY_DEFAULT
 #else
              base::FEATURE_DISABLED_BY_DEFAULT
diff --git a/third_party/blink/renderer/platform/media/web_media_player_impl.cc b/third_party/blink/renderer/platform/media/web_media_player_impl.cc
index c454ccb..b34ac49 100644
--- a/third_party/blink/renderer/platform/media/web_media_player_impl.cc
+++ b/third_party/blink/renderer/platform/media/web_media_player_impl.cc
@@ -3833,6 +3833,9 @@
   // we should plumb the pause reason from here all the way through to
   // `WebMediaPlayerImpl::Pause`, where the reset is done.
   client_->PausePlayback(pause_reason);
+
+  // NOTE: The reason MUST be set AFTER `PausePlayback()` is called, since it
+  // will call `Pause()` which clears the `visibility_pause_reason_`.
   visibility_pause_reason_ = pause_reason;
 }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 1caf2ce..329afc27 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -729,10 +729,6 @@
       status: "experimental",
     },
     {
-      name: "CanvasImageSmoothing",
-      status: "experimental",
-    },
-    {
       // https://crbug.com/377325952
       name: "CanvasInterventions",
       browser_process_read_write_access: true,
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index 1a89255..25ba6c2 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -2222,11 +2222,15 @@
 
 void MainThreadSchedulerImpl::OnPageFrozen(
     base::MemoryReductionTaskContext called_from) {
+  main_thread_only().renderer_frozen_metadata.emplace(
+      "MainThreadSchedulerImpl.RendererFrozen", /* is_frozen */ 1,
+      base::SampleMetadataScope::kProcess);
   memory_purge_manager_.OnPageFrozen(called_from);
   UpdatePolicy();
 }
 
 void MainThreadSchedulerImpl::OnPageResumed() {
+  main_thread_only().renderer_frozen_metadata.reset();
   memory_purge_manager_.OnPageResumed();
   UpdatePolicy();
 }
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index 42b07c2c..3e1a1bd 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -705,6 +705,7 @@
 
     bool renderer_hidden = false;
     std::optional<base::ScopedSampleMetadata> renderer_hidden_metadata;
+    std::optional<base::ScopedSampleMetadata> renderer_frozen_metadata;
     bool renderer_backgrounded = kLaunchingProcessIsBackgrounded;
     TraceableState<bool, TracingCategory::kDefault>
         blocking_input_expected_soon;
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index a3ace153..e558cd72 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -504,6 +504,9 @@
   // Web tests can override the device scale factor in the renderer.
   if (auto scale_factor = client_->GetTestingDeviceScaleFactorOverride()) {
     screen_info.device_scale_factor = scale_factor;
+    visual_properties.compositor_viewport_pixel_rect =
+        gfx::Rect(gfx::ScaleToCeiledSize(visual_properties.new_size,
+                                         screen_info.device_scale_factor));
   }
 
   // Inform the rendering thread of the color space indicating the presence of
diff --git a/third_party/blink/renderer/platform/widget/widget_base.h b/third_party/blink/renderer/platform/widget/widget_base.h
index 37bdfbd..dbd53ba9 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.h
+++ b/third_party/blink/renderer/platform/widget/widget_base.h
@@ -405,9 +405,6 @@
 
   void OnDevToolsSessionConnectionChanged(bool attached);
 
-  // Helper to get the non-emulated device scale factor.
-  float GetOriginalDeviceScaleFactor() const;
-
  private:
   static void AssertAreCompatible(const WidgetBase& a, const WidgetBase& b);
 
@@ -434,6 +431,9 @@
   // Called after the delay given in `RequestAnimationAfterDelay()`.
   void RequestAnimationAfterDelayTimerFired(TimerBase*);
 
+  // Helper to get the non-emulated device scale factor.
+  float GetOriginalDeviceScaleFactor() const;
+
   // Finishes the call to RequestNewLayerTreeFrameSink() once the
   // |gpu_channel_host| is available.
   // TODO(crbug.com/1278147): Clean up these parameters using either a struct or
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index f131e6b..cb1f356 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2694,8 +2694,6 @@
 crbug.com/388345823 [ Win ] external/wpt/fetch/api/credentials/cookies.any.sharedworker.html [ Crash ]
 crbug.com/388312947 [ Mac14 ] virtual/composite-clip-path-animation/external/wpt/css/css-masking/clip-path/animations/clip-path-transition.html [ Crash ]
 crbug.com/388312947 [ Win ] virtual/composite-clip-path-animation/external/wpt/css/css-masking/clip-path/animations/clip-path-transition.html [ Crash ]
-crbug.com/388376333 [ Mac12 ] virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-inside-top-layer.tentative.html [ Crash ]
-crbug.com/388376333 [ Win ] virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-inside-top-layer.tentative.html [ Crash ]
 crbug.com/388326073 [ Mac14 ] virtual/fedcm-authz/external/wpt/fedcm/fedcm-authz/fedcm-disclosure-text-shown.https.html [ Crash ]
 crbug.com/388326073 [ Win ] virtual/fedcm-authz/external/wpt/fedcm/fedcm-authz/fedcm-disclosure-text-shown.https.html [ Crash ]
 [ Linux ] virtual/fenced-frame-mparch-internal/wpt_internal/fenced_frame/revoke-manual-report-event-beacons.https.html [ Crash ]
@@ -9085,6 +9083,9 @@
 crbug.com/366530634 external/wpt/storage-access-api/storage-access-headers.tentative.https.sub.window.html [ Failure Pass Timeout ]
 crbug.com/366530634 external/wpt/storage-access-api/storage-access-permission.sub.https.window.html [ Failure Pass Timeout ]
 
+crbug.com/40653832 external/wpt/css/css-position/sticky/position-sticky-rtl.html [ Failure ]
+crbug.com/40653832 virtual/fractional-scroll-offsets/external/wpt/css/css-position/sticky/position-sticky-rtl.html [ Failure ]
+
 # Gardener 2024-12-20
 crbug.com/379003901 [ Mac11 ] virtual/gpu-rasterization/external/wpt/css/css-images/object-view-box-fit-cover-video.html [ Failure Pass ]
 
@@ -9109,3 +9110,6 @@
 crbug.com/385337582 external/wpt/paint-timing/fcp-only/fcp-document-opacity-image.html [ Failure Pass ]
 crbug.com/383627707 [ Mac ] external/wpt/element-timing/image-carousel.html [ Failure Pass ]
 crbug.com/379474211 external/wpt/paint-timing/fcp-only/fcp-document-opacity-text.html [ Failure Pass ]
+
+crbug.com/389559091 [ Linux ] external/wpt/html/semantics/forms/the-select-element/customizable-select/select-home-end-pagedown-pageup.tentative.html [ Pass Timeout ]
+crbug.com/389559091 [ Mac ] external/wpt/html/semantics/forms/the-select-element/customizable-select/select-home-end-pagedown-pageup.tentative.html [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-computed.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-computed.html
index 89854a6..abe20d5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-computed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-computed.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8">
 <title>CSS Masonry: masonry-slack getComputedStyle()</title>
-<link rel="help" href="https://tabatkins.github.io/specs/css-masonry/">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
 <link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
 <meta name="assert" content="masonry-slack computed value is as specified.">
 <script src="/resources/testharness.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-invalid.html
index feb90fd..75bae04 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-invalid.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8">
 <title>CSS Masonry: masonry-slack parsing</title>
-<link rel="help" href="https://tabatkins.github.io/specs/css-masonry/">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
 <link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
 <meta name="assert" content="masonry-slack supports only the grammar 'normal | <length-percentage>'.">
 <meta name="assert" content="masonry-slack rejects negative <length-percentage>.">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-valid.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-valid.html
index 9447b9b..55044a9 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-slack-valid.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8">
 <title>CSS Masonry: masonry-slack parsing</title>
-<link rel="help" href="https://tabatkins.github.io/specs/css-masonry/">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
 <link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
 <meta name="assert" content="masonry-slack supports the full grammar 'normal | <length-percentage>'.">
 <script src="/resources/testharness.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-template-tracks-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-template-tracks-invalid.html
index df69085..7fed500c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-template-tracks-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-template-tracks-invalid.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>CSS Masonry: Parsing masonry-template-tracks with invalid values</title>
 <link rel="author" title="Ethan Jimenez" href="mailto:ethavar@microsoft.com">
-<link rel="help" href="https://tabatkins.github.io/specs/css-masonry/#template-tracks">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3/#propdef-masonry-template-tracks">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/parsing-testcommon.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-template-tracks-valid.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-template-tracks-valid.html
index 7397760..45550cb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-template-tracks-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-template-tracks-valid.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>CSS Masonry: Parsing masonry-template-tracks with valid values</title>
 <link rel="author" title="Ethan Jimenez" href="mailto:ethavar@microsoft.com">
-<link rel="help" href="https://tabatkins.github.io/specs/css-masonry/#template-tracks">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3/#propdef-masonry-template-tracks">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/parsing-testcommon.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-computed.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-computed.html
index a8239bf2..771626c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-computed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-computed.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>CSS Masonry: masonry-track-* getComputedStyle()</title>
 <link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
-<link rel="help" href="https://tabatkins.github.io/specs/css-masonry/">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/computed-testcommon.js"></script>
@@ -44,4 +44,4 @@
   test_computed_value("masonry-track", "span first", "span first");
   test_computed_value("masonry-track", "span 2 first / auto", "span 2 first");
   test_computed_value("masonry-track", "span 2 first", "span 2 first");
-</script>
\ No newline at end of file
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-invalid.html
index 65887008..ba03c84 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-invalid.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>CSS Masonry: masonry-track-* parsing</title>
 <link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
-<link rel="help" href="https://tabatkins.github.io/specs/css-masonry/">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/parsing-testcommon.js"></script>
@@ -53,4 +53,4 @@
   test_invalid_value("masonry-track", "first span 1 / last");
   test_invalid_value("masonry-track", "3 first / 2 span last");
   test_invalid_value("masonry-track", "3 / 1 span 2");
-</script>
\ No newline at end of file
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-valid.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-valid.html
index 5f98422..2625c262 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-track-valid.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>CSS Masonry: masonry-track-* parsing</title>
 <link rel="author" title="Sam Davis Omekara Jr." href="mailto:samomekarajr@microsoft.com">
-<link rel="help" href="https://tabatkins.github.io/specs/css-masonry/">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/parsing-testcommon.js"></script>
@@ -95,4 +95,4 @@
       "masonry-track-start": "last",
       "masonry-track-end": "last"
   });
-</script>
\ No newline at end of file
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.high-ref.html b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.high-ref.html
new file mode 100644
index 0000000..4a1a62b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.high-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<canvas id="canvas" width="300" height="300"></canvas>
+<script>
+  const canvas = document.getElementById('canvas');
+  const ctx = canvas.getContext('2d');
+  ctx.imageSmoothingQuality = 'high';
+  ctx.fillStyle = 'green';
+  ctx.fillRect(0, 0, 300, 300);
+  const image = new Image();
+  image.src = './resources/html5.png';
+  image.onload = () => {
+    ctx.drawImage(image, 0, 0, 200, 200);
+  };
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.high.https.html b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.high.https.html
new file mode 100644
index 0000000..9e4494e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.high.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="help" href="https://drafts.css-houdini.org/css-paint-api/">
+<link rel="match" href="paint2d-imageSmoothingQuality.high-ref.html">
+<style>
+#output {
+    width: 300px;
+    height: 300px;
+    background-image: paint(image);
+    border-image: url(./resources/html5.png);
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+<div id="output"></div>
+
+<script id="code" type="text/worklet">
+    registerPaint('image', class {
+        static get inputProperties() { return [ 'border-image-source' ]; };
+        paint(ctx, geom, styleMap) {
+            ctx.imageSmoothingQuality = 'high';
+            ctx.fillStyle = 'green';
+            ctx.fillRect(0, 0, geom.width, geom.height);
+            ctx.drawImage(styleMap.get('border-image-source'), 0, 0, 200, 200);
+        }
+    });
+</script>
+
+<script>
+    importWorkletAndTerminateTestAfterAsyncPaint(
+        CSS.paintWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.low-ref.html b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.low-ref.html
new file mode 100644
index 0000000..511da3f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.low-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<canvas id="canvas" width="300" height="300"></canvas>
+<script>
+  const canvas = document.getElementById('canvas');
+  const ctx = canvas.getContext('2d');
+  ctx.imageSmoothingQuality = 'low';
+  ctx.fillStyle = 'green';
+  ctx.fillRect(0, 0, 300, 300);
+  const image = new Image();
+  image.src = './resources/html5.png';
+  image.onload = () => {
+    ctx.drawImage(image, 0, 0, 200, 200);
+  };
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.low.https.html b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.low.https.html
new file mode 100644
index 0000000..85338ae
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.low.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="help" href="https://drafts.css-houdini.org/css-paint-api/">
+<link rel="match" href="paint2d-imageSmoothingQuality.low-ref.html">
+<style>
+#output {
+    width: 300px;
+    height: 300px;
+    background-image: paint(image);
+    border-image: url(./resources/html5.png);
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+<div id="output"></div>
+
+<script id="code" type="text/worklet">
+    registerPaint('image', class {
+        static get inputProperties() { return [ 'border-image-source' ]; };
+        paint(ctx, geom, styleMap) {
+            ctx.imageSmoothingQuality = 'low';
+            ctx.fillStyle = 'green';
+            ctx.fillRect(0, 0, geom.width, geom.height);
+            ctx.drawImage(styleMap.get('border-image-source'), 0, 0, 200, 200);
+        }
+    });
+</script>
+
+<script>
+    importWorkletAndTerminateTestAfterAsyncPaint(
+        CSS.paintWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.med-ref.html b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.med-ref.html
new file mode 100644
index 0000000..801c512
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.med-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<canvas id="canvas" width="300" height="300"></canvas>
+<script>
+  const canvas = document.getElementById('canvas');
+  const ctx = canvas.getContext('2d');
+  ctx.imageSmoothingQuality = 'medium';
+  ctx.fillStyle = 'green';
+  ctx.fillRect(0, 0, 300, 300);
+  const image = new Image();
+  image.src = './resources/html5.png';
+  image.onload = () => {
+    ctx.drawImage(image, 0, 0, 200, 200);
+  };
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.med.https.html b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.med.https.html
new file mode 100644
index 0000000..4d5008a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-paint-api/paint2d-imageSmoothingQuality.med.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="help" href="https://drafts.css-houdini.org/css-paint-api/">
+<link rel="match" href="paint2d-imageSmoothingQuality.med-ref.html">
+<style>
+#output {
+    width: 300px;
+    height: 300px;
+    background-image: paint(image);
+    border-image: url(./resources/html5.png);
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+<div id="output"></div>
+
+<script id="code" type="text/worklet">
+    registerPaint('image', class {
+        static get inputProperties() { return [ 'border-image-source' ]; };
+        paint(ctx, geom, styleMap) {
+            ctx.imageSmoothingQuality = 'medium';
+            ctx.fillStyle = 'green';
+            ctx.fillRect(0, 0, geom.width, geom.height);
+            ctx.drawImage(styleMap.get('border-image-source'), 0, 0, 200, 200);
+        }
+    });
+</script>
+
+<script>
+    importWorkletAndTerminateTestAfterAsyncPaint(
+        CSS.paintWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-rtl-ref.html b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-rtl-ref.html
new file mode 100644
index 0000000..b114a3a3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-rtl-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<style>
+.container {
+  display: flex;
+  overflow: auto;
+  width: 500px;
+  position: relative;
+}
+
+.container {
+  scrollbar-width: none;
+}
+
+.child {
+  width: 1900px;
+  height: 100px;
+  flex-shrink: 0;
+  background: lightblue;
+}
+.sticky {
+  position: absolute;
+  left: 0;
+  width: 100px;
+  height: 100px;
+  flex-shrink: 0;
+  background: lightgreen;
+}
+</style>
+<div class="container" dir="rtl">
+  <div class="child">1</div>
+  <div class="sticky">20</div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-rtl.html b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-rtl.html
new file mode 100644
index 0000000..df1a636
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-rtl.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
+<meta name="assert" content="Position sticky works correctly with rtl" />
+<link rel="match" href="position-sticky-rtl-ref.html" />
+<style>
+.container {
+  display: flex;
+  overflow: auto;
+  width: 500px;
+  position: relative;
+}
+
+.container {
+  scrollbar-width: none;
+}
+
+.child {
+  width: 1900px;
+  height: 100px;
+  flex-shrink: 0;
+  background: lightblue;
+}
+.sticky {
+  position: sticky;
+  left: 0;
+  width: 100px;
+  height: 100px;
+  flex-shrink: 0;
+  background: lightgreen;
+}
+</style>
+<div class="container" dir="rtl">
+  <div class="child">1</div>
+  <div class="sticky">20</div>
+</div>
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
index a15ae65..4dbfd0e 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
@@ -187,6 +187,7 @@
 CONSOLE MESSAGE:     getter globalAlpha
 CONSOLE MESSAGE:     getter globalCompositeOperation
 CONSOLE MESSAGE:     getter imageSmoothingEnabled
+CONSOLE MESSAGE:     getter imageSmoothingQuality
 CONSOLE MESSAGE:     getter lineCap
 CONSOLE MESSAGE:     getter lineDashOffset
 CONSOLE MESSAGE:     getter lineJoin
@@ -239,6 +240,7 @@
 CONSOLE MESSAGE:     setter globalAlpha
 CONSOLE MESSAGE:     setter globalCompositeOperation
 CONSOLE MESSAGE:     setter imageSmoothingEnabled
+CONSOLE MESSAGE:     setter imageSmoothingQuality
 CONSOLE MESSAGE:     setter lineCap
 CONSOLE MESSAGE:     setter lineDashOffset
 CONSOLE MESSAGE:     setter lineJoin
@@ -586,6 +588,7 @@
 CONSOLE MESSAGE:     getter globalAlpha
 CONSOLE MESSAGE:     getter globalCompositeOperation
 CONSOLE MESSAGE:     getter imageSmoothingEnabled
+CONSOLE MESSAGE:     getter imageSmoothingQuality
 CONSOLE MESSAGE:     getter lineCap
 CONSOLE MESSAGE:     getter lineDashOffset
 CONSOLE MESSAGE:     getter lineJoin
@@ -638,6 +641,7 @@
 CONSOLE MESSAGE:     setter globalAlpha
 CONSOLE MESSAGE:     setter globalCompositeOperation
 CONSOLE MESSAGE:     setter imageSmoothingEnabled
+CONSOLE MESSAGE:     setter imageSmoothingQuality
 CONSOLE MESSAGE:     setter lineCap
 CONSOLE MESSAGE:     setter lineDashOffset
 CONSOLE MESSAGE:     setter lineJoin
diff --git a/third_party/boringssl/src b/third_party/boringssl/src
index 7db3433..bf4cf69 160000
--- a/third_party/boringssl/src
+++ b/third_party/boringssl/src
@@ -1 +1 @@
-Subproject commit 7db3433bd4466b20ade77494cd3bb03396441aef
+Subproject commit bf4cf6938a77f1aca83ef529dce96681efd1e6c5
diff --git a/third_party/catapult b/third_party/catapult
index 580dbb7..d25caed 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 580dbb72d8984e972a02874d06ace7b13295798a
+Subproject commit d25caed4b98fea8a30ecf85a9c2b056cb139809e
diff --git a/third_party/dawn b/third_party/dawn
index 041b072..60a78b1 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 041b072241d75850d18f441b53bacf2d317e9818
+Subproject commit 60a78b1ae59d6724d658e1eb3d8e2813afb29cb9
diff --git a/third_party/fuzztest/src b/third_party/fuzztest/src
index ae6208f..b86e98f 160000
--- a/third_party/fuzztest/src
+++ b/third_party/fuzztest/src
@@ -1 +1 @@
-Subproject commit ae6208fc45a09da94d9c0925e26cd9bbca92154b
+Subproject commit b86e98ff1149313e43333d1017c3a87baa6721c5
diff --git a/third_party/glslang/src b/third_party/glslang/src
index e435148..c16e6a3 160000
--- a/third_party/glslang/src
+++ b/third_party/glslang/src
@@ -1 +1 @@
-Subproject commit e43514866f7e0f8265c677039d2fe773c892d44b
+Subproject commit c16e6a34b72de51d07c121a1c202806bac0be9dc
diff --git a/third_party/libavif/LICENSE b/third_party/libavif/LICENSE
deleted file mode 100644
index 5b188d7..0000000
--- a/third_party/libavif/LICENSE
+++ /dev/null
@@ -1,46 +0,0 @@
-Copyright 2019 Joe Drago. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright notice, this
-list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright notice,
-this list of conditions and the following disclaimer in the documentation
-and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-------------------------------------------------------------------------------
-
-Files: tests/cJSON.*
-
-Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/third_party/libavif/README.chromium b/third_party/libavif/README.chromium
index a12b1e3..24344f5 100644
--- a/third_party/libavif/README.chromium
+++ b/third_party/libavif/README.chromium
@@ -3,8 +3,8 @@
 URL: https://github.com/AOMediaCodec/libavif
 Version: N/A
 Revision: DEPS
-License: BSD-2-Clause, MIT
-License File: LICENSE
+License: BSD-2-Clause, IJG, Apache-2.0, BSD-3-Clause
+License File: src/LICENSE
 Security Critical: yes
 Shipped: yes
 
diff --git a/third_party/libc++abi/src b/third_party/libc++abi/src
index 7681005..cbada99 160000
--- a/third_party/libc++abi/src
+++ b/third_party/libc++abi/src
@@ -1 +1 @@
-Subproject commit 7681005c6233e8a21b97e24c1a3c5c6979927d5a
+Subproject commit cbada99a33f015cb8333d63a88ff0c10cbbc6f38
diff --git a/third_party/lit/v3_0/BUILD.gn b/third_party/lit/v3_0/BUILD.gn
index f279486e..c7de797 100644
--- a/third_party/lit/v3_0/BUILD.gn
+++ b/third_party/lit/v3_0/BUILD.gn
@@ -64,6 +64,7 @@
     "//chrome/test/data/pdf:build_ts",
     "//chrome/test/data/webui/app_home:build_ts",
     "//chrome/test/data/webui/cr_components:build_ts",
+    "//chrome/test/data/webui/cr_components/cr_shortcut_input:build_ts",
     "//chrome/test/data/webui/cr_components/help_bubble:build_ts",
     "//chrome/test/data/webui/cr_components/history_clusters:build_ts",
     "//chrome/test/data/webui/cr_elements:build_ts",
@@ -85,6 +86,7 @@
     "//content/browser/resources/service_worker:build_ts",
     "//content/browser/resources/traces_internals:build_ts",
     "//ui/webui/resources/cr_components/certificate_manager:build_ts",
+    "//ui/webui/resources/cr_components/cr_shortcut_input:build_ts",
     "//ui/webui/resources/cr_components/customize_color_scheme_mode:build_ts",
     "//ui/webui/resources/cr_components/help_bubble:build_ts",
     "//ui/webui/resources/cr_components/history_clusters:build_ts",
diff --git a/third_party/perfetto b/third_party/perfetto
index 38f6220..2eab41ff 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 38f6220c882b08ed8bd8cbb47cff50cfa790e41b
+Subproject commit 2eab41ff134151a618b4f54d4b8913d770c4ac22
diff --git a/third_party/skia b/third_party/skia
index 539c311..c24b41e 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 539c311a3a7fa76bf50a9c83822815abdb825a18
+Subproject commit c24b41eebcc69f4f8156716a411de04a80b6c3bc
diff --git a/third_party/spirv-tools/src b/third_party/spirv-tools/src
index 9064fe8..995922d 160000
--- a/third_party/spirv-tools/src
+++ b/third_party/spirv-tools/src
@@ -1 +1 @@
-Subproject commit 9064fe8637daf9f694c8a2e035a34da94022f2be
+Subproject commit 995922d48149384766cc646159a9e28701f01f0c
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index ee1a15d..5b43f94 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit ee1a15d510c031acaff02138a922ccdb6f85c2a7
+Subproject commit 5b43f9496300bdac77c67501dc3b3738cb3d21c5
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src
index 5cceb78..cddf237 160000
--- a/third_party/vulkan-validation-layers/src
+++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@
-Subproject commit 5cceb78082833556789a64f3237b04df7a826d93
+Subproject commit cddf2371ee3ef9c31deea06ce14df558c20ece04
diff --git a/third_party/webrtc b/third_party/webrtc
index da8a535a..2911627 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit da8a535ad4e41fbb587b38346d324a2892ba4183
+Subproject commit 2911627ab3cb5e2549d7e65d176a01ed398b874a
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f2d9c4c..a03f1c6 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -17370,6 +17370,7 @@
   <int value="-1239515260" label="OmniboxDisableCGIParamMatching:disabled"/>
   <int value="-1239262870" label="TextFragmentAnchor:enabled"/>
   <int value="-1238992816" label="ShelfDimming:enabled"/>
+  <int value="-1238769868" label="TabStripGroupDragDropAndroid:disabled"/>
   <int value="-1237921078" label="SyncUSSNigori:enabled"/>
   <int value="-1237821073" label="SharedHighlightingUseBlocklist:enabled"/>
   <int value="-1237621246" label="WebXRGamepadSupport:disabled"/>
@@ -22097,6 +22098,7 @@
   <int value="653795860" label="HelpAppReleaseNotes:disabled"/>
   <int value="654199907" label="AllowSyncXHRInPageDismissal:disabled"/>
   <int value="654879464" label="WalletRequiresFirstSyncSetupComplete:enabled"/>
+  <int value="655364027" label="TabStripGroupDragDropAndroid:enabled"/>
   <int value="656779919" label="OsFeedbackJelly:enabled"/>
   <int value="656864700" label="FillOnAccountSelectHttp:disabled"/>
   <int value="657832926" label="SwipeToMoveCursor:enabled"/>
diff --git a/tools/metrics/histograms/metadata/apps/histograms.xml b/tools/metrics/histograms/metadata/apps/histograms.xml
index 9e489f5..6c039b6 100644
--- a/tools/metrics/histograms/metadata/apps/histograms.xml
+++ b/tools/metrics/histograms/metadata/apps/histograms.xml
@@ -908,7 +908,7 @@
 </histogram>
 
 <histogram name="Apps.AppList.NumberOfNonSystemFolders" units="count"
-    expires_after="2025-02-22">
+    expires_after="2025-08-22">
   <owner>tbarzic@chromium.org</owner>
   <owner>jamescook@chromium.org</owner>
   <owner>chromeos-launcher@google.com</owner>
@@ -1951,7 +1951,7 @@
 </histogram>
 
 <histogram name="Apps.AppListFolder.ShowHide.AnimationSmoothness" units="%"
-    expires_after="2025-02-22">
+    expires_after="2025-08-22">
   <owner>tbarzic@chromium.org</owner>
   <owner>jamescook@chromium.org</owner>
   <owner>chromeos-launcher@google.com</owner>
@@ -2930,7 +2930,7 @@
 </histogram>
 
 <histogram name="Apps.TimeBetweenAppInstallAndLaunch{TabletOrClamshell}"
-    units="ms" expires_after="2025-02-22">
+    units="ms" expires_after="2025-08-22">
   <owner>tbarzic@chromium.org</owner>
   <owner>jamescook@chromium.org</owner>
   <owner>chromeos-launcher@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/cookie/histograms.xml b/tools/metrics/histograms/metadata/cookie/histograms.xml
index 84c0bf9..81c7aff7 100644
--- a/tools/metrics/histograms/metadata/cookie/histograms.xml
+++ b/tools/metrics/histograms/metadata/cookie/histograms.xml
@@ -583,6 +583,18 @@
   </summary>
 </histogram>
 
+<histogram name="Cookie.GetCookieListWithOptions.Duration" units="microseconds"
+    expires_after="2025-07-13">
+  <owner>anthonyvd@chromium.org</owner>
+  <owner>chrome-catan@google.com</owner>
+  <summary>
+    Records the time spent executing CookieMonster::GetCookieListWithOptions,
+    plus its callback if the callback is invoked synchronously.
+
+    Subsampled to 1/1000. Not reported for clients with low-resolution clocks.
+  </summary>
+</histogram>
+
 <histogram name="Cookie.HasNonASCII.{CookieField}" enum="Boolean"
     expires_after="2025-05-18">
   <owner>bingler@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/search/histograms.xml b/tools/metrics/histograms/metadata/search/histograms.xml
index b7b7bde1..ac8f2ea 100644
--- a/tools/metrics/histograms/metadata/search/histograms.xml
+++ b/tools/metrics/histograms/metadata/search/histograms.xml
@@ -54,7 +54,7 @@
 </histogram>
 
 <histogram name="Search.AuxiliarySearch.DeleteTime{AuxiliarySearchDataType}"
-    units="ms" expires_after="M140">
+    units="ms" expires_after="2026-02-10">
   <owner>gangwu@chromium.org</owner>
   <owner>chrome-mobile-search@google.com</owner>
   <summary>Time taken for deleting contents from the auxiliary search.</summary>
@@ -63,7 +63,7 @@
 
 <histogram
     name="Search.AuxiliarySearch.DeletionFailure{AuxiliarySearchDataType}"
-    units="count" expires_after="M140">
+    units="count" expires_after="2026-02-10">
   <owner>gangwu@chromium.org</owner>
   <owner>chrome-mobile-search@google.com</owner>
   <summary>
@@ -74,7 +74,7 @@
 
 <histogram
     name="Search.AuxiliarySearch.DeletionRequestStatus{AuxiliarySearchDataType}"
-    enum="AuxiliarySearchRequestStatus" expires_after="M140">
+    enum="AuxiliarySearchRequestStatus" expires_after="2026-02-10">
   <owner>gangwu@chromium.org</owner>
   <owner>chrome-mobile-search@google.com</owner>
   <summary>
@@ -94,7 +94,7 @@
 
 <histogram
     name="Search.AuxiliarySearch.DonationFailure{AuxiliarySearchDataType}"
-    units="count" expires_after="M140">
+    units="count" expires_after="2026-02-10">
   <owner>gangwu@chromium.org</owner>
   <owner>chrome-mobile-search@google.com</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 87cc92a..c8c0a9a5 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": "81ac9b9e0950715b1cd7bfea1230606e5ef4357f",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/38f6220c882b08ed8bd8cbb47cff50cfa790e41b/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/2eab41ff134151a618b4f54d4b8913d770c4ac22/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "a15d8362d80cfd7cd8d785cf6afc22586de688cd",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v49.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "24fb6186c9dcbb4c77c7200803988100907b9730",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/38f6220c882b08ed8bd8cbb47cff50cfa790e41b/trace_processor_shell"
+            "hash": "f2ce431c4ccb7c9de644e63d3c43bdb7e43a6e9f",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/2eab41ff134151a618b4f54d4b8913d770c4ac22/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/typescript/path_mappings.py b/tools/typescript/path_mappings.py
index a18d58b..f0ea406c 100644
--- a/tools/typescript/path_mappings.py
+++ b/tools/typescript/path_mappings.py
@@ -23,6 +23,7 @@
       "cr_components/certificate_manager",
       "cr_components/color_change_listener",
       "cr_components/commerce",
+      "cr_components/cr_shortcut_input",
       "cr_components/customize_color_scheme_mode",
       "cr_components/customize_themes",
       "cr_components/help_bubble",
diff --git a/ui/accessibility/extensions/strings/accessibility_extensions_strings.grd b/ui/accessibility/extensions/strings/accessibility_extensions_strings.grd
index 982072d..e9d975a4 100644
--- a/ui/accessibility/extensions/strings/accessibility_extensions_strings.grd
+++ b/ui/accessibility/extensions/strings/accessibility_extensions_strings.grd
@@ -4,7 +4,7 @@
      name of the extension as a prefix, e.g. IDS_ALT_APP_NAME. -->
 
 <grit base_dir="." latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" enc_check="möl" source_lang_id="en">
+      enc_check="möl" source_lang_id="en">
   <outputs>
     <output filename="_locales/am/messages.json" type="chrome_messages_json" lang="am"/>
     <output filename="_locales/ar/messages.json" type="chrome_messages_json" lang="ar"/>
diff --git a/ui/android/event_forwarder.cc b/ui/android/event_forwarder.cc
index a980682..34c80f23 100644
--- a/ui/android/event_forwarder.cc
+++ b/ui/android/event_forwarder.cc
@@ -209,7 +209,7 @@
                                         jlong time_ms,
                                         jfloat scale) {
   float dip_scale = view_->GetDipScale();
-  auto size = view_->GetSizeDIPs();
+  auto size = view_->GetSize();
   float x = size.width() / 2;
   float y = size.height() / 2;
   gfx::PointF root_location =
@@ -226,7 +226,7 @@
     const JavaParamRef<jobject>& motion_event,
     jlong event_time_ns,
     jlong down_time_ms) {
-  auto size = view_->GetSizeDIPs();
+  auto size = view_->GetSize();
   float x = size.width() / 2;
   float y = size.height() / 2;
   ui::MotionEventAndroid::Pointer pointer0(0, x, y, 0, 0, 0, 0, 0);
diff --git a/ui/android/java/src/org/chromium/ui/InsetObserver.java b/ui/android/java/src/org/chromium/ui/InsetObserver.java
index cf26c5e..105b91be 100644
--- a/ui/android/java/src/org/chromium/ui/InsetObserver.java
+++ b/ui/android/java/src/org/chromium/ui/InsetObserver.java
@@ -100,7 +100,7 @@
             InsetConsumerSource.APP_HEADER_COORDINATOR_CAPTION,
             InsetConsumerSource.EDGE_TO_EDGE_CONTROLLER_IMPL,
             InsetConsumerSource.EDGE_TO_EDGE_LAYOUT_COORDINATOR,
-            InsetConsumerSource.APP_HEADER_COORDINATOR_IME,
+            InsetConsumerSource.APP_HEADER_COORDINATOR_BOTTOM,
             InsetConsumerSource.COUNT
         })
         @Retention(RetentionPolicy.SOURCE)
@@ -112,7 +112,7 @@
             int APP_HEADER_COORDINATOR_CAPTION = 2;
             int EDGE_TO_EDGE_CONTROLLER_IMPL = 3;
             int EDGE_TO_EDGE_LAYOUT_COORDINATOR = 4;
-            int APP_HEADER_COORDINATOR_IME = 5;
+            int APP_HEADER_COORDINATOR_BOTTOM = 5;
 
             // Update this whenever a consumer source is added or removed.
             int COUNT = 6;
diff --git a/ui/android/java/strings/android_ui_strings.grd b/ui/android/java/strings/android_ui_strings.grd
index 20b6af0..b664d29 100644
--- a/ui/android/java/strings/android_ui_strings.grd
+++ b/ui/android/java/strings/android_ui_strings.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="values-af/android_ui_strings.xml" lang="af" type="android" />
     <output filename="values-am/android_ui_strings.xml" lang="am" type="android" />
diff --git a/ui/android/junit/src/org/chromium/ui/InsetObserverTest.java b/ui/android/junit/src/org/chromium/ui/InsetObserverTest.java
index eaf7eba4d..bff3a105 100644
--- a/ui/android/junit/src/org/chromium/ui/InsetObserverTest.java
+++ b/ui/android/junit/src/org/chromium/ui/InsetObserverTest.java
@@ -144,7 +144,7 @@
     public void applyInsets_withMultipleInsetConsumers() {
         // Add consumers in reverse order of priority.
         mInsetObserver.addInsetsConsumer(
-                mInsetsConsumer1, InsetConsumerSource.APP_HEADER_COORDINATOR_IME);
+                mInsetsConsumer1, InsetConsumerSource.APP_HEADER_COORDINATOR_BOTTOM);
         mInsetObserver.addInsetsConsumer(
                 mInsetsConsumer2,
                 InsetConsumerSource.DEFERRED_IME_WINDOW_INSET_APPLICATION_CALLBACK);
diff --git a/ui/android/view_android.cc b/ui/android/view_android.cc
index a955092..ab9d83f 100644
--- a/ui/android/view_android.cc
+++ b/ui/android/view_android.cc
@@ -151,9 +151,9 @@
 
   // Empty view size also need not propagating down in order to prevent
   // spurious events with empty size from being sent down.
-  if (child->match_parent() && !bounds_device_px_.IsEmpty() &&
-      child->GetSizeDevicePx() != bounds_device_px_.size()) {
-    child->OnSizeChangedInternal(bounds_device_px_.size());
+  if (child->match_parent() && !bounds_.IsEmpty() &&
+      child->GetSize() != bounds_.size()) {
+    child->OnSizeChangedInternal(bounds_.size());
     child->DispatchOnSizeChanged();
   }
 
@@ -493,32 +493,25 @@
   // Match-parent view must not receive size events.
   DCHECK(!match_parent());
 
-  gfx::Size size_device_px(width, height);
-
-  if (bounds_device_px_.size() == size_device_px) {
+  float scale = GetDipScale();
+  gfx::Size size(std::ceil(width / scale), std::ceil(height / scale));
+  if (bounds_.size() == size)
     return;
-  }
 
-  OnSizeChangedInternal(size_device_px);
+  OnSizeChangedInternal(size);
 
   // Signal resize event after all the views in the tree get the updated size.
   DispatchOnSizeChanged();
 }
 
-void ViewAndroid::OnSizeChangedInternal(const gfx::Size& size_device_px) {
-  if (bounds_device_px_.size() == size_device_px) {
+void ViewAndroid::OnSizeChangedInternal(const gfx::Size& size) {
+  if (bounds_.size() == size)
     return;
-  }
 
-  bounds_device_px_.set_size(size_device_px);
-
-  float scale = GetDipScale();
-  bounds_dips_.set_size(gfx::Size(std::ceil(size_device_px.width() / scale),
-                                  std::ceil(size_device_px.height() / scale)));
-
+  bounds_.set_size(size);
   for (ViewAndroid* child : children_) {
     if (child->match_parent())
-      child->OnSizeChangedInternal(size_device_px);
+      child->OnSizeChangedInternal(size);
   }
 }
 
@@ -561,12 +554,8 @@
   return physical_size_;
 }
 
-gfx::Size ViewAndroid::GetSizeDIPs() const {
-  return bounds_dips_.size();
-}
-
-gfx::Size ViewAndroid::GetSizeDevicePx() const {
-  return bounds_device_px_.size();
+gfx::Size ViewAndroid::GetSize() const {
+  return bounds_.size();
 }
 
 bool ViewAndroid::OnDragEvent(const DragEventAndroid& event) {
@@ -696,7 +685,7 @@
                           const E& event,
                           const gfx::PointF& point) {
   if (event_handler_) {
-    if (bounds_dips_.origin().IsOrigin()) {  // (x, y) == (0, 0)
+    if (bounds_.origin().IsOrigin()) {  // (x, y) == (0, 0)
       if (handler_callback.Run(event_handler_.get(), event))
         return true;
     } else {
@@ -708,14 +697,14 @@
 
   if (!children_.empty()) {
     gfx::PointF offset_point(point);
-    offset_point.Offset(-bounds_dips_.x(), -bounds_dips_.y());
+    offset_point.Offset(-bounds_.x(), -bounds_.y());
     gfx::Point int_point = gfx::ToFlooredPoint(offset_point);
 
     // Match from back to front for hit testing.
     for (ViewAndroid* child : base::Reversed(children_)) {
       bool matched = child->match_parent();
       if (!matched)
-        matched = child->bounds_dips_.Contains(int_point);
+        matched = child->bounds_.Contains(int_point);
       if (matched && child->HitTest(handler_callback, event, offset_point))
         return true;
     }
@@ -724,8 +713,7 @@
 }
 
 void ViewAndroid::SetLayoutForTesting(int x, int y, int width, int height) {
-  bounds_dips_.SetRect(x, y, width, height);
-  bounds_device_px_.SetRect(x, y, width, height);
+  bounds_.SetRect(x, y, width, height);
 }
 
 size_t ViewAndroid::GetChildrenCountForTesting() const {
diff --git a/ui/android/view_android.h b/ui/android/view_android.h
index 314f89e..78876253 100644
--- a/ui/android/view_android.h
+++ b/ui/android/view_android.h
@@ -168,10 +168,8 @@
                         jint drag_obj_rect_height);
 
   gfx::Size GetPhysicalBackingSize() const;
-  gfx::Size GetSizeDIPs() const;
-  gfx::Size GetSizeDevicePx() const;
+  gfx::Size GetSize() const;
 
-  // |width| and |height| are in device pixels.
   void OnSizeChanged(int width, int height);
   // |deadline_override| if not nullopt will be used as the cc::DeadlinePolicy
   // timeout for this resize.
@@ -304,7 +302,7 @@
   // each leaf of subtree.
   static bool SubtreeHasEventForwarder(ViewAndroid* view);
 
-  void OnSizeChangedInternal(const gfx::Size& size_device_px);
+  void OnSizeChangedInternal(const gfx::Size& size);
   void DispatchOnSizeChanged();
 
   // Returns the Java delegate for this view. This is used to delegate work
@@ -322,11 +320,7 @@
 
   // Basic view layout information. Used to do hit testing deciding whether
   // the passed events should be processed by the view. Unit in DIP.
-  gfx::Rect bounds_dips_;
-
-  // Same as above, but before dividing by the device scale factor.
-  gfx::Rect bounds_device_px_;
-
+  gfx::Rect bounds_;
   const LayoutType layout_type_;
 
   // In physical pixel.
diff --git a/ui/android/view_android_unittest.cc b/ui/android/view_android_unittest.cc
index e3d3202..bbe3a4e 100644
--- a/ui/android/view_android_unittest.cc
+++ b/ui/android/view_android_unittest.cc
@@ -214,7 +214,7 @@
 
   Reset();
 
-  // Match-parent view should not receive size events in the first place.
+  // Match-parent view should not receivee size events in the first place.
   EXPECT_DCHECK_DEATH(viewm_.OnSizeChanged(100, 200));
   EXPECT_FALSE(handlerm_.OnSizeCalled());
   EXPECT_FALSE(handler3_.OnSizeCalled());
diff --git a/ui/base/test/ui_base_test_resources.grd b/ui/base/test/ui_base_test_resources.grd
index 8d8763f..3a9f6e2b 100644
--- a/ui/base/test/ui_base_test_resources.grd
+++ b/ui/base/test/ui_base_test_resources.grd
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <grit base_dir="." latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="grit/ui_base_test_resources.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/ui/resources/ui_resources.grd b/ui/resources/ui_resources.grd
index 50ff7c1a..6138436 100644
--- a/ui/resources/ui_resources.grd
+++ b/ui/resources/ui_resources.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/ui_resources.h" type="rc_header" context="default_100_percent">
       <emit emit_type='prepend'></emit>
diff --git a/ui/resources/ui_unscaled_resources.grd b/ui/resources/ui_unscaled_resources.grd
index e1b99717..1674c35 100644
--- a/ui/resources/ui_unscaled_resources.grd
+++ b/ui/resources/ui_unscaled_resources.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/ui_unscaled_resources.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/ui/views/controls/table/table_view.cc b/ui/views/controls/table/table_view.cc
index d37d41c..c8497dffd 100644
--- a/ui/views/controls/table/table_view.cc
+++ b/ui/views/controls/table/table_view.cc
@@ -550,6 +550,13 @@
   return ax::mojom::SortDirection::kDescending;
 }
 
+void TableView::SetMouseHoveringEnabled(bool enabled) {
+  if (hovering_enabled_ != enabled) {
+    hovering_enabled_ = enabled;
+    SchedulePaint();
+  }
+}
+
 void TableView::Layout(PassKey) {
   // When the scrollview's width changes we force recalculating column sizes.
   ScrollView* scroll_view = ScrollView::GetScrollViewForContents(this);
@@ -766,7 +773,7 @@
 }
 
 void TableView::OnMouseMoved(const ui::MouseEvent& event) {
-  if (!HasFocus()) {
+  if (!hovering_enabled_ || !HasFocus()) {
     return;
   }
 
@@ -781,8 +788,10 @@
 }
 
 void TableView::OnMouseExited(const ui::MouseEvent& event) {
-  OnHoverChanged(hovered_row_, std::nullopt);
-  hovered_row_ = std::nullopt;
+  if (hovering_enabled_) {
+    OnHoverChanged(hovered_row_, std::nullopt);
+    hovered_row_ = std::nullopt;
+  }
 }
 
 void TableView::OnGestureEvent(ui::GestureEvent* event) {
@@ -1101,7 +1110,7 @@
         hovered_row_.has_value() && hovered_row_.value() == i;
     if (is_selected) {
       canvas->FillRect(GetRowBounds(i), selected_bg_color);
-    } else if (is_hovered) {
+    } else if (hovering_enabled_ && is_hovered) {
       canvas->FillRect(GetRowBounds(i), hovered_bg_color);
     } else if (alternate_bg_color != default_bg_color && (i % 2)) {
       canvas->FillRect(GetRowBounds(i), alternate_bg_color);
@@ -1456,6 +1465,10 @@
 
 void TableView::OnHoverChanged(std::optional<size_t> previous_hovered_row,
                                std::optional<size_t> new_hovered_row) {
+  if (!hovering_enabled_) {
+    return;
+  }
+
   const auto maybe_schedule_paint = [this](std::optional<size_t> row) {
     if (row.has_value() && row.value() < GetRowCount()) {
       SchedulePaintInRect(GetRowBounds(row.value()));
diff --git a/ui/views/controls/table/table_view.h b/ui/views/controls/table/table_view.h
index 5e8709f..e103346 100644
--- a/ui/views/controls/table/table_view.h
+++ b/ui/views/controls/table/table_view.h
@@ -238,6 +238,10 @@
   bool GetSortOnPaint() const;
   void SetSortOnPaint(bool sort_on_paint);
 
+  // If enabled, hovering over a row causes the row's background color to
+  // change.
+  void SetMouseHoveringEnabled(bool enabled);
+
   // Returns the proper ax sort direction.
   ax::mojom::SortDirection GetFirstSortDescriptorDirection() const;
 
@@ -592,6 +596,10 @@
   // Customization for the header. Includes options such as padding.
   TableHeaderStyle header_style_;
 
+  // TODO(crbug.com/388086397): Enable by mouse hovering by default when color
+  // tokens are refined on all platforms.
+  bool hovering_enabled_ = false;
+
   // Weak pointer factory, enables using PostTask safely.
   base::WeakPtrFactory<TableView> weak_factory_;
 };
diff --git a/ui/views/examples/views_examples_resources.grd b/ui/views/examples/views_examples_resources.grd
index 2acaba0b..88f06dfe 100644
--- a/ui/views/examples/views_examples_resources.grd
+++ b/ui/views/examples/views_examples_resources.grd
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <grit base_dir="." latest_public_release="0" current_release="1"
-      output_all_resource_defines="false" source_lang_id="en" enc_check="möl">
+      source_lang_id="en" enc_check="möl">
   <outputs>
     <output filename="grit/views_examples_resources.h" type="rc_header">
       <emit emit_type='prepend'></emit>
diff --git a/ui/views/resources/views_resources.grd b/ui/views/resources/views_resources.grd
index f1429ef..fb1f9e5 100644
--- a/ui/views/resources/views_resources.grd
+++ b/ui/views/resources/views_resources.grd
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+<grit latest_public_release="0" current_release="1">
   <outputs>
     <output filename="grit/views_resources.h" type="rc_header" context="default_100_percent">
       <emit emit_type='prepend'></emit>
diff --git a/ui/views/view.cc b/ui/views/view.cc
index 6f9417d6..c31e311 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -1791,15 +1791,13 @@
 
   // Providing we are attached to a Widget and registered with a focus manager,
   // we should de-register from that focus manager now.
-  if (GetWidget() && accelerator_focus_manager_) {
-    accelerator_focus_manager_->UnregisterAccelerator(accelerator, this);
+  if (auto* focus_manager = GetFocusManager()) {
+    focus_manager->UnregisterAccelerator(accelerator, this);
   }
 }
 
 void View::ResetAccelerators() {
-  if (accelerators_) {
-    UnregisterAccelerators(false);
-  }
+  UnregisterAccelerators(false);
 }
 
 bool View::AcceleratorPressed(const ui::Accelerator& accelerator) {
@@ -2257,15 +2255,12 @@
 
 void View::VisibilityChanged(View* starting_from, bool is_visible) {}
 
-void View::NativeViewHierarchyChanged() {
-  FocusManager* focus_manager = GetFocusManager();
-  if (accelerator_focus_manager_ != focus_manager) {
-    UnregisterAccelerators(true);
+void View::NativeViewHierarchyWillChange() {
+  UnregisterAccelerators(true);
+}
 
-    if (focus_manager) {
-      RegisterPendingAccelerators();
-    }
-  }
+void View::NativeViewHierarchyChanged() {
+  RegisterPendingAccelerators();
 }
 
 void View::AddedToWidget() {}
@@ -3136,9 +3131,7 @@
   // their parents. This allows children to override accelerators registered by
   // their parents as accelerators registered later take priority over those
   // registered earlier.
-  if (GetFocusManager()) {
-    RegisterPendingAccelerators();
-  }
+  RegisterPendingAccelerators();
 
   {
     internal::ScopedChildrenLock lock(this);
@@ -3155,6 +3148,16 @@
   }
 }
 
+void View::PropagateNativeViewHierarchyWillChange() {
+  {
+    internal::ScopedChildrenLock lock(this);
+    for (views::View* child : children_) {
+      child->PropagateNativeViewHierarchyWillChange();
+    }
+  }
+  NativeViewHierarchyWillChange();
+}
+
 void View::PropagateNativeViewHierarchyChanged() {
   {
     internal::ScopedChildrenLock lock(this);
@@ -3621,13 +3624,16 @@
     return;
   }
 
-  accelerator_focus_manager_ = GetFocusManager();
-  CHECK(accelerator_focus_manager_);
+  auto* focus_manager = GetFocusManager();
+  if (!focus_manager) {
+    return;
+  }
+
   for (std::vector<ui::Accelerator>::const_iterator i =
            accelerators_->begin() +
            static_cast<ptrdiff_t>(registered_accelerator_count_);
        i != accelerators_->end(); ++i) {
-    accelerator_focus_manager_->RegisterAccelerator(
+    focus_manager->RegisterAccelerator(
         *i, ui::AcceleratorManager::kNormalPriority, this);
   }
   registered_accelerator_count_ = accelerators_->size();
@@ -3639,9 +3645,8 @@
   }
 
   if (GetWidget()) {
-    if (accelerator_focus_manager_) {
-      accelerator_focus_manager_->UnregisterAccelerators(this);
-      accelerator_focus_manager_ = nullptr;
+    if (auto* focus_manager = GetFocusManager()) {
+      focus_manager->UnregisterAccelerators(this);
     }
     if (!leave_data_intact) {
       accelerators_->clear();
diff --git a/ui/views/view.h b/ui/views/view.h
index 61f50d0..c03ba19 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -1783,6 +1783,12 @@
   virtual void VisibilityChanged(View* starting_from, bool is_visible);
 
   // This method is invoked when the parent NativeView of the widget that the
+  // view is attached to is about to change, but the view hierarchy will not
+  // change. This is followed by `NativeViewHierarchyChanged()` below after the
+  // change has occurred.
+  virtual void NativeViewHierarchyWillChange();
+
+  // This method is invoked when the parent NativeView of the widget that the
   // view is attached to has changed and the view hierarchy has not changed.
   // ViewHierarchyChanged() is called when the parent NativeView of the widget
   // that the view is attached to is changed as a result of changing the view
@@ -2058,6 +2064,10 @@
   void PropagateAddNotifications(const ViewHierarchyChangedDetails& details,
                                  bool is_added_to_widget);
 
+  // Propagates NativeViewHierarchyWillChange() notification through all the
+  // children.
+  void PropagateNativeViewHierarchyWillChange();
+
   // Propagates NativeViewHierarchyChanged() notification through all the
   // children.
   void PropagateNativeViewHierarchyChanged();
@@ -2475,9 +2485,6 @@
 
   // Accelerators --------------------------------------------------------------
 
-  // Focus manager accelerators registered on.
-  raw_ptr<FocusManager> accelerator_focus_manager_ = nullptr;
-
   // The list of accelerators. List elements in the range
   // [0, registered_accelerator_count_) are already registered to FocusManager,
   // and the rest are not yet.
diff --git a/ui/views/widget/root_view.cc b/ui/views/widget/root_view.cc
index e8246ae..d26dd03b 100644
--- a/ui/views/widget/root_view.cc
+++ b/ui/views/widget/root_view.cc
@@ -355,6 +355,10 @@
   return children().empty() ? nullptr : children().front();
 }
 
+void RootView::NotifyNativeViewHierarchyWillChange() {
+  PropagateNativeViewHierarchyWillChange();
+}
+
 void RootView::NotifyNativeViewHierarchyChanged() {
   PropagateNativeViewHierarchyChanged();
 }
diff --git a/ui/views/widget/root_view.h b/ui/views/widget/root_view.h
index 4dc9b9e..f056dd5 100644
--- a/ui/views/widget/root_view.h
+++ b/ui/views/widget/root_view.h
@@ -70,6 +70,9 @@
   void SetContentsView(View* contents_view);
   View* GetContentsView();
 
+  // Called when parent of the host is about to change.
+  void NotifyNativeViewHierarchyWillChange();
+
   // Called when parent of the host changed.
   void NotifyNativeViewHierarchyChanged();
 
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 3136f99..8b11480 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -673,6 +673,7 @@
   // that may have been in focus.
   ClearFocusFromWidget();
   native_widget_->OnNativeViewHierarchyWillChange();
+  root_view_->NotifyNativeViewHierarchyWillChange();
 }
 
 void Widget::NotifyNativeViewHierarchyChanged() {
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index 4942f3f..4572513 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -52,6 +52,7 @@
   if (!is_android && !is_ios) {
     public_deps += [
       "cr_components/app_management:build_grdp",
+      "cr_components/cr_shortcut_input:build_grdp",
       "cr_components/customize_color_scheme_mode:build_grdp",
       "cr_components/help_bubble:build_grdp",
       "cr_components/localized_link:build_grdp",
@@ -67,6 +68,7 @@
       "$root_gen_dir/third_party/polymer/v3_0/polymer_3_0_resources.grdp",
       "$target_gen_dir/cr_components/app_management/resources.grdp",
       "$target_gen_dir/cr_components/theme_color_picker/resources.grdp",
+      "$target_gen_dir/cr_components/cr_shortcut_input/resources.grdp",
       "$target_gen_dir/cr_components/customize_color_scheme_mode/resources.grdp",
       "$target_gen_dir/cr_components/help_bubble/resources.grdp",
       "$target_gen_dir/cr_components/localized_link/resources.grdp",
diff --git a/ui/webui/resources/cr_components/cr_shortcut_input/BUILD.gn b/ui/webui/resources/cr_components/cr_shortcut_input/BUILD.gn
new file mode 100644
index 0000000..f2d8bfd
--- /dev/null
+++ b/ui/webui/resources/cr_components/cr_shortcut_input/BUILD.gn
@@ -0,0 +1,32 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//ui/webui/resources/tools/build_webui.gni")
+
+assert(!is_android && !is_ios)
+
+build_webui("build") {
+  grd_prefix = "cr_components_cr_shortcut_input"
+
+  css_files = [ "cr_shortcut_input.css" ]
+
+  non_web_component_files = [
+    "cr_shortcut_input.ts",
+    "cr_shortcut_input.html.ts",
+    "cr_shortcut_util.ts",
+  ]
+
+  ts_out_dir =
+      "$root_gen_dir/ui/webui/resources/tsc/cr_components/cr_shortcut_input"
+  ts_composite = true
+  generate_grdp = true
+
+  ts_deps = [
+    "//third_party/lit/v3_0:build_ts",
+    "//ui/webui/resources/cr_elements:build_ts",
+    "//ui/webui/resources/js:build_ts",
+  ]
+
+  grd_resource_path_prefix = rebase_path(".", "//ui/webui/resources")
+}
diff --git a/chrome/browser/resources/extensions/shortcut_input.css b/ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.css
similarity index 78%
rename from chrome/browser/resources/extensions/shortcut_input.css
rename to ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.css
index 820bf65..53cdb55d 100644
--- a/chrome/browser/resources/extensions/shortcut_input.css
+++ b/ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.css
@@ -4,8 +4,9 @@
 
 /* #css_wrapper_metadata_start
  * #type=style-lit
- * #import=chrome://resources/cr_elements/cr_icons_lit.css.js
- * #import=chrome://resources/cr_elements/cr_hidden_style_lit.css.js
+ * #import=//resources/cr_elements/cr_icons_lit.css.js
+ * #import=//resources/cr_elements/cr_hidden_style_lit.css.js
+ * #scheme=relative
  * #include=cr-icons-lit cr-hidden-style-lit
  * #css_wrapper_metadata_end */
 
diff --git a/chrome/browser/resources/extensions/shortcut_input.html.ts b/ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.html.ts
similarity index 76%
rename from chrome/browser/resources/extensions/shortcut_input.html.ts
rename to ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.html.ts
index dbde97f5..52c3fa6 100644
--- a/chrome/browser/resources/extensions/shortcut_input.html.ts
+++ b/ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.html.ts
@@ -4,20 +4,20 @@
 
 import {html} from '//resources/lit/v3_0/lit.rollup.js';
 
-import type {ShortcutInputElement} from './shortcut_input.js';
+import type {CrShortcutInputElement} from './cr_shortcut_input.js';
 
-export function getHtml(this: ShortcutInputElement) {
+export function getHtml(this: CrShortcutInputElement) {
   // clang-format off
   return html`<!--_html_template_start_-->
 <div id="main">
   <cr-input id="input" ?readonly="${this.readonly_}"
-      aria-label="${this.computeInputAriaLabel_()}"
+      aria-label="${this.inputAriaLabel}"
       .placeholder="${this.computePlaceholder_()}"
       ?invalid="${this.getIsInvalid_()}"
       .errorMessage="${this.getErrorString_()}"
       .value="${this.computeText_()}">
     <cr-icon-button id="edit" title="$i18n{edit}"
-        aria-label="${this.computeEditButtonAriaLabel_()}"
+        aria-label="${this.editButtonAriaLabel}"
         slot="suffix" class="icon-edit no-overlap"
         @click="${this.onEditClick_}">
     </cr-icon-button>
diff --git a/chrome/browser/resources/extensions/shortcut_input.ts b/ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.ts
similarity index 63%
rename from chrome/browser/resources/extensions/shortcut_input.ts
rename to ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.ts
index 3518094..eeaeadf 100644
--- a/chrome/browser/resources/extensions/shortcut_input.ts
+++ b/ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.ts
@@ -2,21 +2,18 @@
 // 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_icon_button/cr_icon_button.js';
-import 'chrome://resources/cr_elements/cr_input/cr_input.js';
+import '//resources/cr_elements/cr_icon_button/cr_icon_button.js';
+import '//resources/cr_elements/cr_input/cr_input.js';
 
-import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
-import type {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
-import {I18nMixinLit} from 'chrome://resources/cr_elements/i18n_mixin_lit.js';
-import {assert} from 'chrome://resources/js/assert.js';
-import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
+import {getInstance as getAnnouncerInstance} from '//resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
+import type {CrInputElement} from '//resources/cr_elements/cr_input/cr_input.js';
+import {I18nMixinLit} from '//resources/cr_elements/i18n_mixin_lit.js';
+import {assert} from '//resources/js/assert.js';
+import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
-import {createDummyExtensionInfo} from './item_util.js';
-import type {KeyboardShortcutDelegate} from './keyboard_shortcut_delegate.js';
-import {createDummyKeyboardShortcutDelegate} from './keyboard_shortcut_delegate.js';
-import {getCss} from './shortcut_input.css.js';
-import {getHtml} from './shortcut_input.html.js';
-import {formatShortcutText, hasValidModifiers, isValidKeyCode, Key, keystrokeToString} from './shortcut_util.js';
+import {getCss} from './cr_shortcut_input.css.js';
+import {getHtml} from './cr_shortcut_input.html.js';
+import {formatShortcutText, hasValidModifiers, isValidKeyCode, Key, keystrokeToString} from './cr_shortcut_util.js';
 
 enum ShortcutError {
   NO_ERROR = 0,
@@ -25,21 +22,20 @@
   NEED_CHARACTER = 3,
 }
 
-// The UI to display and manage keyboard shortcuts set for extension commands.
+// The UI to display and manage keyboard shortcuts.
 
-export interface ExtensionsShortcutInputElement {
+export interface CrShortcutInputElement {
   $: {
     input: CrInputElement,
     edit: HTMLElement,
   };
 }
 
-const ExtensionsShortcutInputElementBase = I18nMixinLit(CrLitElement);
+const CrShortcutInputElementBase = I18nMixinLit(CrLitElement);
 
-export class ExtensionsShortcutInputElement extends
-    ExtensionsShortcutInputElementBase {
+export class CrShortcutInputElement extends CrShortcutInputElementBase {
   static get is() {
-    return 'extensions-shortcut-input';
+    return 'cr-shortcut-input';
   }
 
   static override get styles() {
@@ -52,10 +48,9 @@
 
   static override get properties() {
     return {
-      delegate: {type: Object},
-      item: {type: Object},
-      command: {type: Object},
       shortcut: {type: String},
+      inputAriaLabel: {type: String},
+      editButtonAriaLabel: {type: String},
       error_: {type: Number},
 
       readonly_: {
@@ -65,17 +60,9 @@
     };
   }
 
-  delegate: KeyboardShortcutDelegate = createDummyKeyboardShortcutDelegate();
-  item: chrome.developerPrivate.ExtensionInfo = createDummyExtensionInfo();
-  command: chrome.developerPrivate.Command = {
-    description: '',
-    keybinding: '',
-    name: '',
-    isActive: false,
-    scope: chrome.developerPrivate.CommandScope.CHROME,
-    isExtensionAction: false,
-  };
   shortcut: string = '';
+  inputAriaLabel: string = '';
+  editButtonAriaLabel: string = '';
   protected readonly_: boolean = true;
   private capturing_: boolean = false;
   private error_: ShortcutError = ShortcutError.NO_ERROR;
@@ -90,15 +77,16 @@
     node.addEventListener('keyup', this.onKeyUp_.bind(this));
   }
 
-  private startCapture_() {
+  private async startCapture_() {
     if (this.capturing_ || this.readonly_) {
       return;
     }
     this.capturing_ = true;
-    this.delegate.setShortcutHandlingSuspended(true);
+    await this.updateComplete;
+    this.fire('input-capture-change', true);
   }
 
-  private endCapture_() {
+  private async endCapture_() {
     if (!this.capturing_) {
       return;
     }
@@ -106,15 +94,15 @@
     this.capturing_ = false;
     this.$.input.blur();
     this.error_ = ShortcutError.NO_ERROR;
-    this.delegate.setShortcutHandlingSuspended(false);
     this.readonly_ = true;
+    await this.updateComplete;
+    this.fire('input-capture-change', false);
   }
 
   private clearShortcut_() {
     this.pendingShortcut_ = '';
     this.shortcut = '';
-    // We commit the empty shortcut in order to clear the current shortcut
-    // for the extension.
+    // Commit the empty shortcut in order to clear the current shortcut.
     this.commitPending_();
     this.endCapture_();
   }
@@ -130,7 +118,7 @@
 
     if (e.keyCode === Key.ESCAPE) {
       if (!this.capturing_) {
-        // If we're not currently capturing, allow escape to propagate.
+        // If not currently capturing, allow escape to propagate.
         return;
       }
       // Otherwise, escape cancels capturing.
@@ -186,13 +174,13 @@
   }
 
   private handleKey_(e: KeyboardEvent) {
-    // While capturing, we prevent all events from bubbling, to prevent
+    // While capturing, prevent all events from bubbling, to prevent
     // shortcuts lacking the right modifier (F3 for example) from activating
     // and ending capture prematurely.
     e.preventDefault();
     e.stopPropagation();
 
-    // We don't allow both Ctrl and Alt in the same keybinding.
+    // Don't allow both Ctrl and Alt in the same keybinding.
     // TODO(devlin): This really should go in hasValidModifiers,
     // but that requires updating the existing page as well.
     if (e.ctrlKey && e.altKey) {
@@ -219,20 +207,10 @@
     this.endCapture_();
   }
 
-  private commitPending_() {
+  private async commitPending_() {
     this.shortcut = this.pendingShortcut_;
-    this.delegate.updateExtensionCommandKeybinding(
-        this.item.id, this.command.name, this.shortcut);
-  }
-
-  protected computeInputAriaLabel_(): string {
-    return this.i18n(
-        'editShortcutInputLabel', this.command.description, this.item.name);
-  }
-
-  protected computeEditButtonAriaLabel_(): string {
-    return this.i18n(
-        'editShortcutButtonLabel', this.command.description, this.item.name);
+    await this.updateComplete;
+    this.fire('shortcut-updated', this.shortcut);
   }
 
   protected computePlaceholder_(): string {
@@ -257,22 +235,17 @@
   protected onEditClick_() {
     // TODO(ghazale): The clearing functionality should be improved.
     // Instead of clicking the edit button, and then clicking elsewhere to
-    // commit the "empty" shortcut, we want to introduce a separate clear
-    // button.
+    // commit the "empty" shortcut, introduce a separate clear button.
     this.clearShortcut_();
     this.readonly_ = false;
     this.$.input.focus();
   }
 }
 
-// Exported to be used in the autogenerated Lit template file
-export type ShortcutInputElement = ExtensionsShortcutInputElement;
-
 declare global {
   interface HTMLElementTagNameMap {
-    'extensions-shortcut-input': ExtensionsShortcutInputElement;
+    'cr-shortcut-input': CrShortcutInputElement;
   }
 }
 
-customElements.define(
-    ExtensionsShortcutInputElement.is, ExtensionsShortcutInputElement);
+customElements.define(CrShortcutInputElement.is, CrShortcutInputElement);
diff --git a/chrome/browser/resources/extensions/shortcut_util.ts b/ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_util.ts
similarity index 92%
rename from chrome/browser/resources/extensions/shortcut_util.ts
rename to ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_util.ts
index 74ce2bf..3c537d80 100644
--- a/chrome/browser/resources/extensions/shortcut_util.ts
+++ b/ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_util.ts
@@ -2,9 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {assertNotReached} from 'chrome://resources/js/assert.js';
-import {isChromeOS, isMac} from 'chrome://resources/js/platform.js';
-
+import {assertNotReached} from '//resources/js/assert.js';
+import {isChromeOS, isMac} from '//resources/js/platform.js';
 
 export enum Key {
   COMMA = 188,
@@ -68,7 +67,7 @@
 }
 
 /**
- * Checks whether the passed in |keyCode| is a valid extension command key.
+ * Checks whether the passed in |keyCode| is a valid command key.
  * @return Whether the key is valid.
  */
 export function isValidKeyCode(keyCode: number): boolean {
@@ -85,8 +84,7 @@
 }
 
 /**
- * Converts a keystroke event to string form, ignoring invalid extension
- * commands.
+ * Converts a keystroke event to string form, ignoring invalid commands.
  */
 export function keystrokeToString(e: KeyboardEvent): string {
   const output = [];
@@ -178,7 +176,7 @@
 /**
  * Returns true if the event has valid modifiers.
  * @param e The keyboard event to consider.
- * @return Wether the event is valid.
+ * @return Whether the event is valid.
  */
 export function hasValidModifiers(e: KeyboardEvent): boolean {
   switch (getModifierPolicy(e.keyCode)) {
diff --git a/v8 b/v8
index fc91f65..897d8f2 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit fc91f65bbd2f5dfa85e65a007ec01a71a10a45b4
+Subproject commit 897d8f21c242c88045314d105862e4c88e78b859