diff --git a/DEPS b/DEPS
index b9e75de..03ecc72 100644
--- a/DEPS
+++ b/DEPS
@@ -276,11 +276,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '7cf9e93279ad7acb591d761935900a49457bbd70',
+  'src_internal_revision': '3ce4702c33df16bb3cc2619173d8f80639872707',
   # 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': 'dba7f46122ba3b1cdb8890eec92aa7b3534781b6',
+  'skia_revision': '1398cbd6b7f9af9eca3b0b5277fece4cb33a45ff',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -288,7 +288,7 @@
   # 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': 'c10f5e3fbf61e024ed298700dbc0b5faf888ec5f',
+  'angle_revision': 'c289b30f332d55bf0156d7122dac00f1aebe56e0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -352,7 +352,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling chromium_variations
   # and whatever else without interference from each other.
-  'chromium_variations_revision': '9be189a60865bf5fed52f2d6dd76ceb54e231ddb',
+  'chromium_variations_revision': '7b74300faa5f3675b06c4f10bfaef33c760918e9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # 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': '032f0bdd8c0a3800eb49131d212142a61df81b0c',
+  'fuzztest_revision': 'c99c121225fcc175bdc084d83c30f3c806b75afd',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling domato
   # and whatever else without interference from each other.
@@ -372,7 +372,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'ed19c1e8985293025be2e812b86ea7619185fcfd',
+  'devtools_frontend_revision': '8245ed152847c99e3313079a696637bca1d5bdd7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -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': '07cbbfbf05a3b1e9f22b3d0ac3203a5a70c15689',
+  'dawn_revision': 'ab9f198d52730b69f4a208c5afd39abb0236f76a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -496,7 +496,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling llvm-libc
   # and whatever else without interference from each other.
-  'llvm_libc_revision':    '9ee890194fe9d4f39b1d5114c6e291b72e6062dd',
+  'llvm_libc_revision':    'cf32ae379c8968df8be7b8b9b1d69115402bccc4',
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
@@ -1439,7 +1439,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'de102b699470b4d92be37fc98f29f6c668e419f2',
+    'dbee4805fa8597c96cfcf0e4f44e77ae9f50901c',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1468,7 +1468,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '0ac0dfe5adf41700ef61e3bb0a1ece2362757433',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '5c9ba055eef03a043b7cf5191de54e1197fee86a',
       'condition': 'checkout_ios',
   },
 
@@ -1702,7 +1702,7 @@
       'packages': [
           {
                'package': 'chromium/third_party/android_build_tools/manifest_merger',
-               'version': 'T3B_dWqgDISstbC0L7CrQOOf9xe-27KUYK8UCTq6trgC',
+               'version': 'UGF3GC1qR9jeALurBm6PNVfQT1TNc-yqDjT4pEpuiYsC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -2436,7 +2436,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '0893e2af69caf8592f6e38f34ccdd4ad6615de9d',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e324242074e2e64a65e90a2933afd3ca4413554f',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -2800,13 +2800,13 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '450cceb587613ac1469c5a131fac15935c99e0e7',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '18742954f642e134d9080840bdb2a884aad09776',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '973068171048a6ab4c9d0762d5efdae8c2c1c8c4',
 
   'src/third_party/webpagereplay':
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '2c96934699b1f75572f1dd6f508e85ab0c96a356',
+    Var('webrtc_git') + '/src.git' + '@' + '35b67572f28b865e81bdddfc370214c329e2f285',
 
   # 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.
@@ -4634,7 +4634,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        'fa89054d303701d661169f5d7cf4e8473dd3fe98',
+        '5d2fd8e76328068efba5dcac5421f3390b7aa9e7',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index 7ba4f5b..5be231a 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -27,6 +27,7 @@
 #include "ash/public/cpp/accelerator_actions.h"
 #include "ash/public/cpp/accelerators.h"
 #include "ash/public/cpp/debug_delegate.h"
+#include "ash/public/mojom/input_device_settings.mojom-shared.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/system/input_device_settings/input_device_settings_notification_controller.h"
@@ -70,6 +71,8 @@
 using ::base::UserMetricsAction;
 using ::chromeos::WindowStateType;
 using input_method::InputMethodManager;
+using OverviewBasedScreenshotKeyboardType =
+    AcceleratorControllerImpl::OverviewBasedScreenshotKeyboardType;
 
 static_assert(AcceleratorAction::kDesksActivate0 ==
                       AcceleratorAction::kDesksActivate1 - 1 &&
@@ -139,6 +142,49 @@
       GetEncodedShortcut(accelerator.modifiers(), accelerator.key_code()));
 }
 
+void RecordOverviewBasedScreenshotUmaHistogram(
+    AcceleratorAction action,
+    const ui::Accelerator& accelerator) {
+  // Only interested in tracking screenshot related actions.
+  switch (action) {
+    case ash::AcceleratorAction::kTakeScreenshot:
+    case ash::AcceleratorAction::kTakePartialScreenshot:
+    case ash::AcceleratorAction::kTakeWindowScreenshot:
+      break;
+    default:
+      return;
+  }
+
+  // Only interested in triggers via the overview key.
+  if (accelerator.key_code() != ui::VKEY_MEDIA_LAUNCH_APP1) {
+    return;
+  }
+
+  const OverviewBasedScreenshotKeyboardType keyboard_type = [&]() {
+    auto* keyboard_capability = Shell::Get()->keyboard_capability();
+    CHECK(keyboard_capability);
+
+    if (!keyboard_capability->IsChromeOSKeyboard(
+            accelerator.source_device_id())) {
+      return OverviewBasedScreenshotKeyboardType::kNonChromeOSKeyboard;
+    }
+
+    if (keyboard_capability->HasTopRowActionKey(
+            accelerator.source_device_id(), ui::TopRowActionKey::kScreenshot)) {
+      return OverviewBasedScreenshotKeyboardType::
+          kChromeOSKeyboardWithScreenshot;
+    } else {
+      return OverviewBasedScreenshotKeyboardType::
+          kChromeOSKeyboardWithoutScreenshot;
+    }
+  }();
+
+  base::UmaHistogramEnumeration(
+      base::StrCat({"Ash.Accelerators.OverviewBasedScreenshot.",
+                    GetAcceleratorActionName(action)}),
+      keyboard_type);
+}
+
 void RecordImeSwitchByAccelerator() {
   UMA_HISTOGRAM_ENUMERATION("InputMethod.ImeSwitch",
                             ImeSwitchType::kAccelerator);
@@ -1707,6 +1753,7 @@
   }
 
   RecordActionUmaHistogram(action, accelerator);
+  RecordOverviewBasedScreenshotUmaHistogram(action, accelerator);
   NotifyActionPerformed(action);
 
   // Reset any in progress composition.
diff --git a/ash/accelerators/accelerator_controller_impl.h b/ash/accelerators/accelerator_controller_impl.h
index 34652b0..cbea8a5 100644
--- a/ash/accelerators/accelerator_controller_impl.h
+++ b/ash/accelerators/accelerator_controller_impl.h
@@ -56,6 +56,16 @@
       public AshAcceleratorConfiguration::Observer,
       public AcceleratorPrefs::Observer {
  public:
+  // Used to record the keyboard type which triggers a screenshot action via the
+  // overview key. Do not reorder values of this enum.
+  enum class OverviewBasedScreenshotKeyboardType {
+    kNonChromeOSKeyboard,
+    kChromeOSKeyboardWithScreenshot,
+    kChromeOSKeyboardWithoutScreenshot,
+    kMinValue = kNonChromeOSKeyboard,
+    kMaxValue = kChromeOSKeyboardWithoutScreenshot,
+  };
+
   // TestApi is used for tests to get internal implementation details.
   class TestApi {
    public:
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index 27c529c..0bbb840 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -105,7 +105,9 @@
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/screen.h"
 #include "ui/display/test/display_manager_test_api.h"
+#include "ui/events/ash/keyboard_capability.h"
 #include "ui/events/devices/device_data_manager_test_api.h"
+#include "ui/events/devices/input_device.h"
 #include "ui/events/devices/keyboard_device.h"
 #include "ui/events/event.h"
 #include "ui/events/event_constants.h"
@@ -2646,6 +2648,77 @@
   ASSERT_FALSE(message_center()->IsQuietMode());
 }
 
+TEST_F(AcceleratorControllerTest, OverviewBasedScreenshotMetric) {
+  const ui::KeyboardDevice kChromeOSKeyboardWithScreenshot(
+      5, ui::INPUT_DEVICE_INTERNAL, "ChromeOSKeyboardWithScreenshot");
+  const ui::KeyboardDevice kChromeOSKeyboardWithoutScreenshot(
+      10, ui::INPUT_DEVICE_BLUETOOTH, "ChromeOSKeyboardWithoutScreenshot");
+  const ui::KeyboardDevice kNonChromeOSKeyboard(15, ui::INPUT_DEVICE_USB,
+                                                "NonChromeOSKeyboard");
+
+  ui::DeviceDataManagerTestApi().SetKeyboardDevices(
+      {kChromeOSKeyboardWithoutScreenshot, kChromeOSKeyboardWithScreenshot,
+       kNonChromeOSKeyboard});
+
+  Shell::Get()->keyboard_capability()->SetKeyboardInfoForTesting(
+      kChromeOSKeyboardWithScreenshot,
+      {ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
+       ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom,
+       /*top_row_scan_codes=*/{555},
+       /*top_row_action_keys=*/{ui::TopRowActionKey::kScreenshot}});
+  Shell::Get()->keyboard_capability()->SetKeyboardInfoForTesting(
+      kChromeOSKeyboardWithoutScreenshot,
+      {ui::KeyboardCapability::DeviceType::kDeviceExternalChromeOsKeyboard,
+       ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom,
+       /*top_row_scan_codes=*/{555},
+       /*top_row_action_keys=*/{ui::TopRowActionKey::kOverview}});
+  Shell::Get()->keyboard_capability()->SetKeyboardInfoForTesting(
+      kNonChromeOSKeyboard,
+      {ui::KeyboardCapability::DeviceType::kDeviceExternalGenericKeyboard,
+       ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout1,
+       /*top_row_scan_codes=*/{},
+       /*top_row_action_keys=*/{ui::TopRowActionKey::kOverview}});
+
+  ui::KeyEvent partial_screenshot_event(
+      ui::EventType::kKeyPressed, ui::VKEY_MEDIA_LAUNCH_APP1,
+      ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
+
+  {
+    base::HistogramTester histogram_tester;
+    partial_screenshot_event.set_source_device_id(
+        kChromeOSKeyboardWithScreenshot.id);
+    controller_->Process(ui::Accelerator(partial_screenshot_event));
+    histogram_tester.ExpectUniqueSample(
+        "Ash.Accelerators.OverviewBasedScreenshot.TakePartialScreenshot",
+        AcceleratorControllerImpl::OverviewBasedScreenshotKeyboardType::
+            kChromeOSKeyboardWithScreenshot,
+        1);
+  }
+
+  {
+    base::HistogramTester histogram_tester;
+    partial_screenshot_event.set_source_device_id(
+        kChromeOSKeyboardWithoutScreenshot.id);
+    controller_->Process(ui::Accelerator(partial_screenshot_event));
+    histogram_tester.ExpectUniqueSample(
+        "Ash.Accelerators.OverviewBasedScreenshot.TakePartialScreenshot",
+        AcceleratorControllerImpl::OverviewBasedScreenshotKeyboardType::
+            kChromeOSKeyboardWithoutScreenshot,
+        1);
+  }
+
+  {
+    base::HistogramTester histogram_tester;
+    partial_screenshot_event.set_source_device_id(kNonChromeOSKeyboard.id);
+    controller_->Process(ui::Accelerator(partial_screenshot_event));
+    histogram_tester.ExpectUniqueSample(
+        "Ash.Accelerators.OverviewBasedScreenshot.TakePartialScreenshot",
+        AcceleratorControllerImpl::OverviewBasedScreenshotKeyboardType::
+            kNonChromeOSKeyboard,
+        1);
+  }
+}
+
 class SystemShortcutBehaviorTest : public AcceleratorControllerTest {
   void SetUp() override {
     AcceleratorControllerTest::SetUp();
diff --git a/ash/app_list/views/app_list_folder_view.cc b/ash/app_list/views/app_list_folder_view.cc
index 095bd53f..d5aafe2 100644
--- a/ash/app_list/views/app_list_folder_view.cc
+++ b/ash/app_list/views/app_list_folder_view.cc
@@ -832,8 +832,6 @@
   shown_ = show;
   UpdateExpandedCollapsedAccessibleState();
   if (show) {
-    // TODO(crbug.com/325137417): Investigate whether this line is necessary. It
-    // probably isn't.
     GetViewAccessibility().SetName(
         folder_item_view_->GetViewAccessibility().GetCachedName(),
         ax::mojom::NameFrom::kAttribute);
diff --git a/ash/app_list/views/apps_grid_view_unittest.cc b/ash/app_list/views/apps_grid_view_unittest.cc
index dd8a5b2b..65d54fa 100644
--- a/ash/app_list/views/apps_grid_view_unittest.cc
+++ b/ash/app_list/views/apps_grid_view_unittest.cc
@@ -5178,6 +5178,110 @@
   MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/false);
 }
 
+TEST_P(AppsGridViewDragTest, DragPinnedItemToShelf) {
+  GetTestModel()->PopulateApps(3);
+  UpdateLayout();
+
+  auto* const shelf_model = ShelfModel::Get();
+  shelf_model->AddAndPinAppWithFactoryConstructedDelegate("Item 1");
+  shelf_model->AddAndPinAppWithFactoryConstructedDelegate("Item 2");
+  ASSERT_EQ(0, shelf_model->ItemIndexByAppID("Item 1"));
+
+  AppListItemView* const item_view =
+      GetItemViewInCurrentPageAt(0, 1, apps_grid_view_);
+  StartDragForViewAndFireTimer(AppsGridView::MOUSE, item_view);
+
+  std::list<base::OnceClosure> tasks;
+  tasks.push_back(base::BindLambdaForTesting([&]() {
+    CheckHaptickEventsCount(1);
+    // Verify that item drag has started.
+    ASSERT_TRUE(apps_grid_view_->drag_item());
+    ASSERT_TRUE(apps_grid_view_->IsDragging());
+    ASSERT_EQ(item_view->item(), apps_grid_view_->drag_item());
+  }));
+  tasks.push_back(base::BindLambdaForTesting([&]() {
+    // Shelf should start handling the drag if it moves within its bounds.
+    auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
+    UpdateDragInScreen(
+        AppsGridView::MOUSE,
+        shelf_view->GetBoundsInScreen().left_center() + gfx::Vector2d(5, 5),
+        /*steps=*/1);
+
+    EXPECT_EQ("Item 1", shelf_view->drag_and_drop_shelf_id().app_id);
+  }));
+  tasks.push_back(base::BindLambdaForTesting([&]() {
+    // Move the item towards the end of the shelf, so it becomes the last shelf
+    // item.
+    auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
+    const auto& shelf_bounds = shelf_view->GetBoundsInScreen();
+    UpdateDragInScreen(AppsGridView::MOUSE,
+                       is_rtl_
+                           ? shelf_bounds.left_center() + gfx::Vector2d(6, 6)
+                           : shelf_bounds.right_center() - gfx::Vector2d(5, 5),
+                       /*steps=*/2);
+
+    EXPECT_EQ("Item 1", shelf_view->drag_and_drop_shelf_id().app_id);
+  }));
+  tasks.push_back(base::BindLambdaForTesting([&]() { EndDrag(); }));
+  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/false);
+
+  // Releasing drag over shelf should pin the dragged app.
+  EXPECT_TRUE(shelf_model->IsAppPinned("Item 1"));
+  EXPECT_EQ(1, shelf_model->ItemIndexByAppID("Item 1"));
+  EXPECT_EQ(0, shelf_model->ItemIndexByAppID("Item 2"));
+  CheckHaptickEventsCount(1);
+}
+
+TEST_P(AppsGridViewDragTest, UnpinDraggedItemDuringDragToShelf) {
+  GetTestModel()->PopulateApps(3);
+  UpdateLayout();
+
+  auto* const shelf_model = ShelfModel::Get();
+  shelf_model->AddAndPinAppWithFactoryConstructedDelegate("Item 1");
+  shelf_model->AddAndPinAppWithFactoryConstructedDelegate("Item 2");
+  ASSERT_EQ(0, shelf_model->ItemIndexByAppID("Item 1"));
+
+  AppListItemView* const item_view =
+      GetItemViewInCurrentPageAt(0, 1, apps_grid_view_);
+  StartDragForViewAndFireTimer(AppsGridView::MOUSE, item_view);
+
+  std::list<base::OnceClosure> tasks;
+  tasks.push_back(base::BindLambdaForTesting([&]() {
+    CheckHaptickEventsCount(1);
+    // Verify that item drag has started.
+    ASSERT_TRUE(apps_grid_view_->drag_item());
+    ASSERT_TRUE(apps_grid_view_->IsDragging());
+    ASSERT_EQ(item_view->item(), apps_grid_view_->drag_item());
+  }));
+  tasks.push_back(base::BindLambdaForTesting([&]() {
+    // Shelf should start handling the drag if it moves within its bounds.
+    auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
+    UpdateDragInScreen(
+        AppsGridView::MOUSE,
+        shelf_view->GetBoundsInScreen().left_center() + gfx::Vector2d(5, 5),
+        /*steps=*/1);
+
+    EXPECT_EQ("Item 1", shelf_view->drag_and_drop_shelf_id().app_id);
+  }));
+  tasks.push_back(base::BindLambdaForTesting([&]() {
+    shelf_model->UnpinAppWithID("Item 1");
+    auto* shelf_view = GetPrimaryShelf()->GetShelfViewForTesting();
+    UpdateDragInScreen(
+        AppsGridView::MOUSE,
+        shelf_view->GetBoundsInScreen().left_center() + gfx::Vector2d(10, 5),
+        /*steps=*/2);
+
+    EXPECT_EQ("", shelf_view->drag_and_drop_shelf_id().app_id);
+    EXPECT_FALSE(shelf_model->IsAppPinned("Item 1"));
+  }));
+  tasks.push_back(base::BindLambdaForTesting([&]() { EndDrag(); }));
+  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/false);
+
+  EXPECT_FALSE(shelf_model->IsAppPinned("Item 1"));
+  EXPECT_EQ(0, shelf_model->ItemIndexByAppID("Item 2"));
+  CheckHaptickEventsCount(1);
+}
+
 TEST_P(AppsGridViewDragTest, MousePointerIsGrabbingDuringDrag) {
   auto* cursor_manager = Shell::Get()->cursor_manager();
   auto previous_cursor_type = cursor_manager->GetCursor().type();
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index b5f487f..5eaca25 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -1128,12 +1128,13 @@
   if (!ShouldHandleDrag(app_id, location_in_screen))
     return false;
 
+  // If the AppsGridView (which was dispatching this event) was opened by our
+  // button, ShelfView dragging operations are locked and we have to unlock.
+  CancelDrag();
+
   DCHECK(!is_active_drag_and_drop_host_);
   is_active_drag_and_drop_host_ = true;
 
-  // If the AppsGridView (which was dispatching this event) was opened by our
-  // button, ShelfView dragging operations are locked and we have to unlock.
-  CancelDrag(std::nullopt);
   drag_and_drop_item_pinned_ = false;
   drag_and_drop_shelf_id_ = ShelfID(app_id);
   // Check if the application is pinned - if not, we have to pin it so
@@ -1142,9 +1143,7 @@
   if (!model_->IsAppPinned(app_id)) {
     ShelfModel::ScopedUserTriggeredMutation user_triggered(model_);
 
-    if (model_->ItemIndexByAppID(app_id) >= 0) {
-      model_->PinExistingItemWithID(app_id);
-    } else {
+    if (model_->ItemIndexByAppID(app_id) < 0) {
       model_->AddAndPinAppWithFactoryConstructedDelegate(app_id);
       drag_and_drop_item_pinned_ = true;
     }
@@ -1157,7 +1156,7 @@
   // Since there is already an icon presented by the caller, we hide this item
   // for now. That has to be done by reducing the size since the visibility will
   // change once a regrouping animation is performed.
-  pre_drag_and_drop_size_ = drag_and_drop_view->size();
+  // The size will be restored to ideal bounds in `EndDrag()`.
   drag_and_drop_view->SetSize(gfx::Size());
 
   // First we have to center the mouse cursor over the item.
@@ -1185,8 +1184,8 @@
 
   drag_icon_bounds_in_screen_ = drag_icon_bounds_in_screen;
   gfx::Point pt = location_in_screen;
-  views::View* drag_and_drop_view =
-      view_model_->view_at(model_->ItemIndexByID(drag_and_drop_shelf_id_));
+  const int item_index = model_->ItemIndexByID(drag_and_drop_shelf_id_);
+  views::View* drag_and_drop_view = view_model_->view_at(item_index);
   ConvertPointFromScreen(drag_and_drop_view, &pt);
   gfx::Point point_in_root = location_in_screen;
   wm::ConvertPointFromScreen(window_util::GetRootWindowAt(location_in_screen),
@@ -1198,24 +1197,23 @@
 }
 
 void ShelfView::EndDrag(bool cancel) {
-  drag_scroll_dir_ = 0;
-  scrolling_timer_.Stop();
-  speed_up_drag_scrolling_.Stop();
-
   if (drag_and_drop_shelf_id_.IsNull()) {
-    is_active_drag_and_drop_host_ = false;
+    ClearDragState();
     return;
   }
 
+  // `PointerReleasedOnButton()` clears drag state, so cache parts of the state
+  // needed later on.
+  const auto item_id = drag_and_drop_shelf_id_;
   views::View* drag_and_drop_view =
-      view_model_->view_at(model_->ItemIndexByID(drag_and_drop_shelf_id_));
+      view_model_->view_at(model_->ItemIndexByID(item_id));
   PointerReleasedOnButton(drag_and_drop_view, DRAG_AND_DROP, cancel);
 
-  // Either destroy the temporarily created item - or - make the item visible.
-  if (drag_and_drop_item_pinned_ && cancel) {
-    ShelfModel::ScopedUserTriggeredMutation user_triggered(model_);
-    model_->UnpinAppWithID(drag_and_drop_shelf_id_.app_id);
-  } else if (drag_and_drop_view) {
+  // Animate drag and drop view to visible if it hasn't been removed by
+  // `PointerReleasedOnButton()`.
+  const int item_index = model_->ItemIndexByID(item_id);
+  if (item_index >= 0) {
+    drag_and_drop_view = view_model_->view_at(item_index);
     std::unique_ptr<gfx::AnimationDelegate> animation_delegate;
 
     // Resets the dragged view's opacity at the end of drag. Otherwise, if
@@ -1237,12 +1235,12 @@
       }
 
     } else {
-      drag_and_drop_view->SetSize(pre_drag_and_drop_size_);
+      // Restore drag and drop view size, which was cleared in `StartDrag` to
+      // hide the item view.
+      const int button_size = GetButtonSize();
+      drag_and_drop_view->SetSize(gfx::Size(button_size, button_size));
     }
   }
-  drag_icon_bounds_in_screen_ = gfx::Rect();
-  drag_and_drop_shelf_id_ = ShelfID();
-  is_active_drag_and_drop_host_ = false;
 }
 
 void ShelfView::SwapButtons(views::View* button_to_swap, bool with_next) {
@@ -1319,31 +1317,31 @@
 void ShelfView::PointerReleasedOnButton(const views::View* view,
                                         Pointer pointer,
                                         bool canceled) {
-  drag_scroll_dir_ = 0;
-  scrolling_timer_.Stop();
-  speed_up_drag_scrolling_.Stop();
-
   is_repost_event_on_same_item_ = false;
-
   if (canceled) {
-    CancelDrag(std::nullopt);
+    CancelDrag();
   } else if (drag_pointer_ == pointer) {
+    // `dragged_off_shelf_` gets reset in in `FinalizeRipOffDrag()` - cache the
+    // value so it can be used to decide whether to update dragged view pin
+    // status.
+    const bool was_dragged_off = dragged_off_shelf_;
     FinalizeRipOffDrag(false);
     drag_pointer_ = NONE;
 
     // Check if the pin status of |drag_view_| should be changed when
-    // |drag_view_| is dragged over the separator. Do nothing if |drag_view_| is
-    // already handled in FinalizedRipOffDrag.
-    if (drag_view_) {
-      if (ShouldUpdateDraggedViewPinStatus(
-              view_model_->GetIndexOfView(view).value())) {
-        const std::string drag_app_id = ShelfItemForView(drag_view_)->id.app_id;
-        ShelfModel::ScopedUserTriggeredMutation user_triggered(model_);
-        if (model_->IsAppPinned(drag_app_id)) {
-          model_->UnpinAppWithID(drag_app_id);
-        } else {
-          model_->PinExistingItemWithID(drag_app_id);
-        }
+    // |drag_view_| is dragged over the separator. Keep the current pin state
+    // if the view has been dragged off the shelf - `FinalizeRipOffDrag()`
+    // should have already updated the item pin state appropriately in this
+    // case.
+    if (drag_view_ && !was_dragged_off &&
+        ShouldUpdateDraggedViewPinStatus(
+            view_model_->GetIndexOfView(view).value())) {
+      const std::string drag_app_id = ShelfItemForView(drag_view_)->id.app_id;
+      ShelfModel::ScopedUserTriggeredMutation user_triggered(model_);
+      if (model_->IsAppPinned(drag_app_id)) {
+        model_->UnpinAppWithID(drag_app_id);
+      } else {
+        model_->PinExistingItemWithID(drag_app_id);
       }
     }
     AnimateToIdealBounds();
@@ -1372,9 +1370,7 @@
 
   // If the drag pointer is NONE, no drag operation is going on and the
   // |drag_view_| can be released.
-  drag_view_ = nullptr;
-  drag_view_relative_to_ideal_bounds_ = RelativePosition::kNotAvailable;
-  RemoveGhostView();
+  ClearDragState();
 }
 
 void ShelfView::AnimateDragImageLayer(
@@ -1573,10 +1569,9 @@
   DCHECK(drag_view_);
   drag_pointer_ = pointer;
   start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
-  drag_scroll_dir_ = 0;
 
   if (!start_drag_index_.has_value()) {
-    CancelDrag(std::nullopt);
+    CancelDrag();
     return;
   }
 
@@ -1638,9 +1633,6 @@
     HandleRipOffDrag(event);
     // Check if the item got ripped off the shelf - if it did we are done.
     if (dragged_off_shelf_) {
-      drag_scroll_dir_ = 0;
-      scrolling_timer_.Stop();
-      speed_up_drag_scrolling_.Stop();
       if (!dragged_off_shelf_before)
         model_->OnItemRippedOff();
       return;
@@ -1857,27 +1849,31 @@
       drag_view_->SetVisible(false);
       ShelfModel::ScopedUserTriggeredMutation user_triggered(model_);
       model_->UnpinAppWithID(model_->items()[current_index.value()].id.app_id);
+      // Show the view if unpinning the app does not remove it from shelf (e.g.
+      // if the app ripped of shelf has an open instance).
+      if (drag_view_) {
+        drag_view_->SetVisible(true);
+      }
     }
   }
+
   if (cancel || snap_back) {
-    if (!cancelling_drag_model_changed_) {
-      // Only do something if the change did not come through a model change.
-      gfx::Rect drag_bounds = drag_icon_proxy_->GetBoundsInScreen();
-      gfx::Point relative_to = GetBoundsInScreen().origin();
-      gfx::Rect target(
-          gfx::PointAtOffsetFromOrigin(drag_bounds.origin() - relative_to),
-          drag_bounds.size());
-      drag_view_->SetBoundsRect(target);
-      // Hide the status from the active item since we snap it back now. Upon
-      // animation end the flag gets cleared if |snap_back_from_rip_off_view_|
-      // is set.
-      snap_back_from_rip_off_view_ = drag_view_;
-      drag_view_->AddState(ShelfAppButton::STATE_HIDDEN);
-      // When a canceling drag model is happening, the view model is diverged
-      // from the menu model and movements / animations should not be done.
-      model_->Move(current_index.value(), start_drag_index_.value());
-      AnimateToIdealBounds();
-    }
+    // Only do something if the change did not come through a model change.
+    gfx::Rect drag_bounds = drag_icon_proxy_->GetBoundsInScreen();
+    gfx::Point relative_to = GetBoundsInScreen().origin();
+    gfx::Rect target(
+        gfx::PointAtOffsetFromOrigin(drag_bounds.origin() - relative_to),
+        drag_bounds.size());
+    drag_view_->SetBoundsRect(target);
+    // Hide the status from the active item since we snap it back now. Upon
+    // animation end the flag gets cleared if |snap_back_from_rip_off_view_|
+    // is set.
+    snap_back_from_rip_off_view_ = drag_view_;
+    drag_view_->AddState(ShelfAppButton::STATE_HIDDEN);
+    // When a canceling drag model is happening, the view model is diverged
+    // from the menu model and movements / animations should not be done.
+    model_->Move(current_index.value(), start_drag_index_.value());
+    AnimateToIdealBounds();
     drag_view_->layer()->SetOpacity(1.0f);
     model_->OnItemReturnedFromRipOff(model_->item_count() - 1);
   }
@@ -1980,11 +1976,8 @@
   // Only unpinned running apps on shelf can be dragged across the separator to
   // pin.
   bool can_change_pin_state = ShelfItemForView(drag_view)->type == TYPE_APP;
-
-  // Note that |drag_and_drop_shelf_id_| is set only when the current drag view
-  // is from app list, which can not be dragged to the unpinned app side.
   return !ShelfItemForView(drag_view)->IsPinStateForced() &&
-         drag_and_drop_shelf_id_ == ShelfID() && can_change_pin_state;
+         can_change_pin_state;
 }
 
 void ShelfView::OnFadeInAnimationEnded() {
@@ -2129,45 +2122,42 @@
   return bounds;
 }
 
-std::optional<size_t> ShelfView::CancelDrag(
-    std::optional<size_t> modified_index) {
-  drag_scroll_dir_ = 0;
-  scrolling_timer_.Stop();
-  speed_up_drag_scrolling_.Stop();
-
+void ShelfView::CancelDrag() {
   FinalizeRipOffDrag(true);
 
-  delegate_->CancelScrollForItemDrag();
-  drag_icon_proxy_.reset();
-  drag_image_layer_.reset();
+  if (drag_view_) {
+    auto drag_view_index = view_model_->GetIndexOfView(drag_view_);
+    drag_view_ = nullptr;
 
-  if (!drag_view_)
-    return modified_index;
-  bool was_dragging = dragging();
-  auto drag_view_index = view_model_->GetIndexOfView(drag_view_);
+    if (drag_and_drop_item_pinned_) {
+      ShelfModel::ScopedUserTriggeredMutation user_triggered(model_);
+      model_->UnpinAppWithID(drag_and_drop_shelf_id_.app_id);
+    } else {
+      model_->Move(drag_view_index.value(), start_drag_index_.value());
+    }
+  }
+
+  ClearDragState();
+}
+
+void ShelfView::ClearDragState() {
   drag_pointer_ = NONE;
   drag_view_ = nullptr;
-  if (drag_view_index == modified_index) {
-    // The view that was being dragged is being modified. Don't do anything.
-    return modified_index;
-  }
-  if (!was_dragging)
-    return modified_index;
+  start_drag_index_.reset();
+  drag_view_relative_to_ideal_bounds_ = RelativePosition::kNotAvailable;
+  drag_icon_bounds_in_screen_ = gfx::Rect();
 
-  // Restore previous position, tracking the position of the modified view.
-  bool at_end = modified_index == view_model_->view_size();
-  views::View* modified_view =
-      (modified_index.has_value() && !at_end)
-          ? view_model_->view_at(modified_index.value())
-          : nullptr;
-  model_->Move(drag_view_index.value(), start_drag_index_.value());
+  drag_icon_proxy_.reset();
+  drag_image_layer_.reset();
+  dragged_off_shelf_ = false;
 
-  // If the modified view will be at the end of the list, return the new end of
-  // the list.
-  if (at_end)
-    return view_model_->view_size();
-  return modified_view ? view_model_->GetIndexOfView(modified_view)
-                       : std::nullopt;
+  delegate_->CancelScrollForItemDrag();
+
+  drag_and_drop_shelf_id_ = ShelfID();
+  drag_and_drop_item_pinned_ = false;
+  is_active_drag_and_drop_host_ = false;
+
+  RemoveGhostView();
 }
 
 void ShelfView::OnGestureEvent(ui::GestureEvent* event) {
@@ -2220,11 +2210,10 @@
   const ShelfItem& item(model_->items()[model_index]);
   views::View* view = CreateViewForItem(item);
 
-  {
-    base::AutoReset<bool> cancelling_drag(&cancelling_drag_model_changed_,
-                                          true);
-    model_index = static_cast<int>(CancelDrag(model_index).value());
+  if (start_drag_index_ >= 0 && model_index <= start_drag_index_) {
+    start_drag_index_ = *start_drag_index_ + 1;
   }
+
   view_model_->Add(view, static_cast<size_t>(model_index));
 
   // If |item| is pinned and the mutation is user-triggered, report the pinning
@@ -2316,7 +2305,6 @@
   // If std::move is not called on |view|, |view| will be deleted once out of
   // scope.
   std::unique_ptr<views::View> view(view_model_->view_at(model_index));
-
   shelf_button_delegate_->OnButtonWillBeRemoved();
 
   if (old_item.is_promise_app) {
@@ -2325,18 +2313,20 @@
   }
   view_model_->Remove(model_index);
 
+  if (drag_view_ == view.get()) {
+    ClearDragState();
+  } else {
+    if (start_drag_index_ >= 0 && model_index < start_drag_index_) {
+      start_drag_index_ = *start_drag_index_ - 1;
+    }
+  }
+
   if (old_item.id == context_menu_id_ && shelf_menu_model_adapter_)
     shelf_menu_model_adapter_->Cancel();
 
   if (old_item.id == item_awaiting_response_)
     ResetActiveMenuModelRequest();
 
-  {
-    base::AutoReset<bool> cancelling_drag(&cancelling_drag_model_changed_,
-                                          true);
-    CancelDrag(std::nullopt);
-  }
-
   if (view.get() == shelf_->tooltip()->GetCurrentAnchorView())
     shelf_->tooltip()->Close();
 
@@ -2408,41 +2398,6 @@
         &ShelfView::ShelfItemsUpdatedForDeskChange, base::Unretained(this)));
   }
 
-  if (old_item.type != item.type) {
-    // Type changed, swap the views.
-    model_index = static_cast<int>(CancelDrag(model_index).value());
-    std::unique_ptr<views::View> old_view(view_model_->view_at(model_index));
-    bounds_animator_->StopAnimatingView(old_view.get());
-    // Removing and re-inserting a view in our view model will strip the ideal
-    // bounds from the item. To avoid recalculation of everything the bounds
-    // get remembered and restored after the insertion to the previous value.
-    gfx::Rect old_ideal_bounds = view_model_->ideal_bounds(model_index);
-    view_model_->Remove(model_index);
-    views::View* new_view = CreateViewForItem(item);
-    // The view must be added to the |view_model_| before it's added as a child
-    // so that the model is consistent when UpdateShelfItemViewsVisibility() is
-    // called as a result the hierarchy changes caused by AddChildView(). See
-    // ScrollableShelfView::ViewHierarchyChanged().
-    view_model_->Add(new_view, model_index);
-    AddChildView(new_view);
-    view_model_->set_ideal_bounds(model_index, old_ideal_bounds);
-
-    bounds_animator_->StopAnimatingView(new_view);
-    new_view->SetBoundsRect(old_view->bounds());
-    bounds_animator_->AnimateViewTo(new_view, old_ideal_bounds);
-
-    // If an item is being pinned or unpinned, show the new status of the
-    // shelf immediately so that the separator gets drawn as needed.
-    if (old_item.type == TYPE_PINNED_APP || item.type == TYPE_PINNED_APP) {
-      if (model_->is_current_mutation_user_triggered()) {
-        AnnouncePinUnpinEvent(old_item, item.type == TYPE_PINNED_APP);
-        RecordPinUnpinUserAction(item.type == TYPE_PINNED_APP);
-      }
-      AnimateToIdealBounds();
-    }
-    return;
-  }
-
   views::View* view = view_model_->view_at(model_index);
   switch (item.type) {
     case TYPE_PINNED_APP:
@@ -2458,6 +2413,18 @@
     case TYPE_UNDEFINED:
       break;
   }
+
+  if (old_item.type != item.type) {
+    // If an item is being pinned or unpinned, show the new status of the
+    // shelf immediately so that the separator gets drawn as needed.
+    if (old_item.type == TYPE_PINNED_APP || item.type == TYPE_PINNED_APP) {
+      if (model_->is_current_mutation_user_triggered()) {
+        AnnouncePinUnpinEvent(old_item, item.type == TYPE_PINNED_APP);
+        RecordPinUnpinUserAction(item.type == TYPE_PINNED_APP);
+      }
+      AnimateToIdealBounds();
+    }
+  }
 }
 
 void ShelfView::ShelfItemsUpdatedForDeskChange() {
@@ -2475,6 +2442,13 @@
 }
 
 void ShelfView::ShelfItemMoved(int start_index, int target_index) {
+  if (start_drag_index_ > start_index && start_drag_index_ < target_index) {
+    start_drag_index_ = *start_drag_index_ - 1;
+  } else if (start_drag_index_ > target_index &&
+             start_drag_index_ < start_index) {
+    start_drag_index_ = *start_drag_index_ + 1;
+  }
+
   view_model_->Move(start_index, target_index);
 
   // Reorder the child view to be in the same order as in the |view_model_|.
@@ -2482,12 +2456,7 @@
   NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
                            true /* send_native_event */);
 
-  // When cancelling a drag due to a shelf item being added, the currently
-  // dragged item is moved back to its initial position. AnimateToIdealBounds
-  // will be called again when the new item is added to the |view_model_| but
-  // at this time the |view_model_| is inconsistent with the |model_|.
-  if (!cancelling_drag_model_changed_)
-    AnimateToIdealBounds();
+  AnimateToIdealBounds();
 }
 
 void ShelfView::ShelfItemDelegateChanged(const ShelfID& id,
diff --git a/ash/shelf/shelf_view.h b/ash/shelf/shelf_view.h
index f3ec7af..c16fa689 100644
--- a/ash/shelf/shelf_view.h
+++ b/ash/shelf/shelf_view.h
@@ -472,9 +472,12 @@
   // exists.
   bool CanDragAcrossSeparator(views::View* dragged_view) const;
 
-  // If there is a drag operation in progress it's canceled. If |modified_index|
-  // is valid, the new position of the corresponding item is returned.
-  std::optional<size_t> CancelDrag(std::optional<size_t> modified_index);
+  // If there is a drag operation in progress it's canceled.
+  void CancelDrag();
+
+  // Resets state set for handling drag interactions, including removing ghost
+  // views.
+  void ClearDragState();
 
   // Returns rectangle bounds used for drag insertion.
   gfx::Rect GetBoundsForDragInsertInScreen();
@@ -692,9 +695,6 @@
   std::unique_ptr<display::ScopedDisplayForNewWindows>
       scoped_display_for_new_windows_;
 
-  // True when an item being inserted or removed in the model cancels a drag.
-  bool cancelling_drag_model_changed_ = false;
-
   // The item with an in-flight async request for a context menu or selection
   // (which shows a shelf item application menu if multiple windows are open).
   // Used to avoid multiple concurrent menu requests. The value is null if none.
@@ -720,9 +720,6 @@
   // `ApplicationDragAndDropHost` interface.
   gfx::Rect drag_icon_bounds_in_screen_;
 
-  // The original launcher item's size before the dragging operation.
-  gfx::Size pre_drag_and_drop_size_;
-
   // True when the icon was dragged off the shelf.
   bool dragged_off_shelf_ = false;
 
@@ -749,15 +746,6 @@
   // alignment or auto-hide state).
   raw_ptr<views::View> announcement_view_ = nullptr;  // Owned by ShelfView
 
-  // For dragging: -1 if scrolling back, 1 if scrolling forward, 0 if neither.
-  int drag_scroll_dir_ = 0;
-
-  // Used to periodically call ScrollForUserDrag.
-  base::RepeatingTimer scrolling_timer_;
-
-  // Used to call SpeedUpDragScrolling.
-  base::OneShotTimer speed_up_drag_scrolling_;
-
   // Whether this view should focus its last focusable child (instead of its
   // first) when focused.
   bool default_last_focusable_child_ = false;
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index 98bca4f..90b6cf7 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -554,15 +554,17 @@
 
   void CheckModelIDs(
       const std::vector<std::pair<ShelfID, views::View*>>& id_map) {
-    size_t map_index = 0;
-    for (size_t model_index = 0; model_index < model_->items().size();
-         ++model_index) {
-      ShelfItem item = model_->items()[model_index];
-      ShelfID id = item.id;
-      EXPECT_EQ(id_map[map_index].first, id);
-      ++map_index;
+    std::vector<ShelfID> expected;
+    for (const auto& item : id_map) {
+      expected.push_back(item.first);
     }
-    ASSERT_EQ(map_index, id_map.size());
+
+    std::vector<ShelfID> actual;
+    for (const auto& item : model_->items()) {
+      actual.push_back(item.id);
+    }
+
+    EXPECT_EQ(expected, actual);
   }
 
   void ExpectHelpBubbleAnchorBoundsChangedEvent(
@@ -933,30 +935,113 @@
   ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
 
   // Deleting an item keeps the remaining intact.
-  dragged_button = SimulateDrag(ShelfView::MOUSE, 0, 2, false);
+  dragged_button = SimulateDrag(ShelfView::MOUSE, 3, 2, false);
   EXPECT_EQ(3, GetHapticTickEventsCount());
 
   // The dragged view has been moved to index 2 during drag.
-  std::rotate(id_map.begin(), id_map.begin() + 1, id_map.begin() + 3);
+  std::rotate(id_map.begin() + 2, id_map.begin() + 3, id_map.begin() + 4);
   ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
 
-  model_->RemoveItemAt(2);
-  id_map.erase(id_map.begin() + 2);
+  model_->RemoveItemAt(0);
+  id_map.erase(id_map.begin());
   ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
-  shelf_view_->PointerReleasedOnButton(dragged_button, ShelfView::MOUSE, false);
+  // Cancel drag, and verify that the model state has been correctly reset.
+  shelf_view_->PointerReleasedOnButton(dragged_button, ShelfView::MOUSE, true);
+
+  std::rotate(id_map.begin() + 1, id_map.begin() + 2, id_map.begin() + 3);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
 
   // Waits until app removal animation finishes.
   test_api_->RunMessageLoopUntilAnimationsDone();
 
-  // Adding a shelf item cancels the drag and respects the order.
   dragged_button = SimulateDrag(ShelfView::MOUSE, 0, 2, false);
   EXPECT_EQ(4, GetHapticTickEventsCount());
+  std::rotate(id_map.begin(), id_map.begin() + 1, id_map.begin() + 3);
   ShelfID new_id = AddAppShortcut();
   id_map.insert(id_map.begin() + 5,
                 std::make_pair(new_id, GetButtonByID(new_id)));
   ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
   shelf_view_->PointerReleasedOnButton(dragged_button, ShelfView::MOUSE, false);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
   EXPECT_EQ(4, GetHapticTickEventsCount());
+
+  test_api_->RunMessageLoopUntilAnimationsDone();
+
+  // Test removing dragged item mid drag.
+  dragged_button = SimulateDrag(ShelfView::MOUSE, 1, 2, false);
+  EXPECT_EQ(5, GetHapticTickEventsCount());
+  std::rotate(id_map.begin() + 1, id_map.begin() + 2, id_map.begin() + 3);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+  model_->RemoveItemAt(2);
+  id_map.erase(id_map.begin() + 2);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+  shelf_view_->PointerReleasedOnButton(dragged_button, ShelfView::MOUSE, false);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+  EXPECT_EQ(5, GetHapticTickEventsCount());
+
+  test_api_->RunMessageLoopUntilAnimationsDone();
+
+  // Test drag cancellation after adding an item before the dragged item.
+  dragged_button = SimulateDrag(ShelfView::MOUSE, 1, 2, false);
+  EXPECT_EQ(6, GetHapticTickEventsCount());
+  std::rotate(id_map.begin() + 1, id_map.begin() + 2, id_map.begin() + 3);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+  ShelfItem prepend_item;
+  new_id = ShelfID("-1");
+  prepend_item.id = new_id;
+  prepend_item.type = TYPE_PINNED_APP;
+  ShelfModel::Get()->AddAt(
+      0, prepend_item,
+      std::make_unique<TestShelfItemDelegate>(prepend_item.id));
+  id_map.insert(id_map.begin(), std::make_pair(new_id, GetButtonByID(new_id)));
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+  shelf_view_->PointerReleasedOnButton(dragged_button, ShelfView::MOUSE, true);
+
+  std::rotate(id_map.begin() + 2, id_map.begin() + 3, id_map.begin() + 4);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+  EXPECT_EQ(6, GetHapticTickEventsCount());
+}
+
+// Check that model changes are handled correctly while a shelf icon is being
+// dragged.
+TEST_P(LtrRtlShelfViewTest, MovesInModelWhileDragging) {
+  std::vector<std::pair<ShelfID, views::View*>> id_map;
+  SetupForDragTest(&id_map);
+
+  views::View* dragged_button = SimulateDrag(ShelfView::MOUSE, 2, 4, false);
+  EXPECT_EQ(1, GetHapticTickEventsCount());
+  std::rotate(id_map.begin() + 2, id_map.begin() + 3, id_map.begin() + 5);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+  ShelfModel::Get()->Move(1, 3);
+  std::rotate(id_map.begin() + 1, id_map.begin() + 2, id_map.begin() + 4);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+  shelf_view_->PointerReleasedOnButton(dragged_button, ShelfView::MOUSE, true);
+  EXPECT_EQ(1, GetHapticTickEventsCount());
+
+  std::rotate(id_map.begin() + 1, id_map.begin() + 4, id_map.begin() + 5);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+  test_api_->RunMessageLoopUntilAnimationsDone();
+
+  dragged_button = SimulateDrag(ShelfView::MOUSE, 2, 4, false);
+  EXPECT_EQ(2, GetHapticTickEventsCount());
+  std::rotate(id_map.begin() + 2, id_map.begin() + 3, id_map.begin() + 5);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+  ShelfModel::Get()->Move(3, 1);
+  std::rotate(id_map.begin() + 1, id_map.begin() + 3, id_map.begin() + 4);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+  shelf_view_->PointerReleasedOnButton(dragged_button, ShelfView::MOUSE, true);
+  EXPECT_EQ(2, GetHapticTickEventsCount());
+
+  std::rotate(id_map.begin() + 3, id_map.begin() + 4, id_map.begin() + 5);
+  ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
 }
 
 // Check that 2nd drag from the other pointer would be ignored.
diff --git a/ash/system/holding_space/pinned_files_section.cc b/ash/system/holding_space/pinned_files_section.cc
index 7167c26..c1a9487 100644
--- a/ash/system/holding_space/pinned_files_section.cc
+++ b/ash/system/holding_space/pinned_files_section.cc
@@ -147,8 +147,8 @@
 
     // Icon.
     auto* icon = AddChildView(std::make_unique<views::ImageView>());
-    icon->SetImage(gfx::CreateVectorIcon(kFilesAppIcon, kFilesAppChipIconSize,
-                                         gfx::kPlaceholderColor));
+    icon->SetImage(ui::ImageModel::FromVectorIcon(
+        kFilesAppIcon, gfx::kPlaceholderColor, kFilesAppChipIconSize));
 
     // Label.
     auto* label =
diff --git a/ash/system/phonehub/continue_browsing_chip.cc b/ash/system/phonehub/continue_browsing_chip.cc
index 1b7dc79..4e06321 100644
--- a/ash/system/phonehub/continue_browsing_chip.cc
+++ b/ash/system/phonehub/continue_browsing_chip.cc
@@ -89,12 +89,12 @@
   favicon->SetImageSize(kContinueBrowsingChipFaviconSize);
 
   if (metadata.favicon.IsEmpty()) {
-    favicon->SetImage(CreateVectorIcon(
+    favicon->SetImage(ui::ImageModel::FromVectorIcon(
         kPhoneHubDefaultFaviconIcon,
         AshColorProvider::Get()->GetContentLayerColor(
             AshColorProvider::ContentLayerType::kIconColorPrimary)));
   } else {
-    favicon->SetImage(metadata.favicon.AsImageSkia());
+    favicon->SetImage(ui::ImageModel::FromImage(metadata.favicon));
   }
 
   auto* url_label = header_view->AddChildView(
diff --git a/ash/system/phonehub/phone_hub_app_icon.cc b/ash/system/phonehub/phone_hub_app_icon.cc
index 41aa3a0..340b63c 100644
--- a/ash/system/phonehub/phone_hub_app_icon.cc
+++ b/ash/system/phonehub/phone_hub_app_icon.cc
@@ -10,9 +10,10 @@
 namespace ash {
 
 AppIcon::AppIcon(const gfx::Image& icon, int size) {
-  SetImage(gfx::ImageSkiaOperations::CreateResizedImage(
-      icon.AsImageSkia(), skia::ImageOperations::RESIZE_BEST,
-      gfx::Size(size, size)));
+  SetImage(ui::ImageModel::FromImageSkia(
+      gfx::ImageSkiaOperations::CreateResizedImage(
+          icon.AsImageSkia(), skia::ImageOperations::RESIZE_BEST,
+          gfx::Size(size, size))));
 }
 
 BEGIN_METADATA(AppIcon)
diff --git a/ash/system/phonehub/phone_status_view.cc b/ash/system/phonehub/phone_status_view.cc
index 5922f3c..3c9c183 100644
--- a/ash/system/phonehub/phone_status_view.cc
+++ b/ash/system/phonehub/phone_status_view.cc
@@ -227,7 +227,7 @@
       break;
   }
 
-  signal_icon_->SetImage(signal_image);
+  signal_icon_->SetImage(ui::ImageModel::FromImageSkia(signal_image));
   signal_icon_->SetTooltipText(tooltip_text);
 }
 
@@ -240,9 +240,10 @@
           ? AshColorProvider::ContentLayerType::kIconColorWarning
           : AshColorProvider::ContentLayerType::kIconColorPrimary);
 
-  battery_icon_->SetImage(PowerStatus::GetBatteryImage(
-      CalculateBatteryInfo(icon_fg_color), kUnifiedTrayBatteryIconSize,
-      battery_icon_->GetColorProvider()));
+  battery_icon_->SetImage(
+      ui::ImageModel::FromImageSkia(PowerStatus::GetBatteryImage(
+          CalculateBatteryInfo(icon_fg_color), kUnifiedTrayBatteryIconSize,
+          battery_icon_->GetColorProvider())));
   SetBatteryTooltipText();
   battery_label_->SetText(
       base::FormatPercent(phone_status.battery_percentage()));
@@ -318,10 +319,10 @@
 
 void PhoneStatusView::ClearExistingStatus() {
   // Clear mobile status.
-  signal_icon_->SetImage(gfx::ImageSkia());
+  signal_icon_->SetImage(ui::ImageModel());
 
   // Clear battery status.
-  battery_icon_->SetImage(gfx::ImageSkia());
+  battery_icon_->SetImage(ui::ImageModel());
   battery_label_->SetText(std::u16string());
 
   // TODO(b/281844561): When the phone is disconnected the |phone_name_label_|
diff --git a/ash/webui/boca_ui/resources/app/client_delegate.ts b/ash/webui/boca_ui/resources/app/client_delegate.ts
index 481a7e1..07fdbdb 100644
--- a/ash/webui/boca_ui/resources/app/client_delegate.ts
+++ b/ash/webui/boca_ui/resources/app/client_delegate.ts
@@ -40,9 +40,7 @@
   return {
   sessionDurationInMinutes:
     Number(session.sessionDuration.microseconds / MICRO_SECS_IN_MINUTES),
-        sessionStartTime: session.sessionStartTime?.msec ?
-        new Date(session.sessionStartTime.msec) :
-        undefined,
+        sessionStartTime: session.sessionStartTime || undefined,
         teacher: session.teacher ? {
           id: session.teacher.id,
           name: session.teacher.name,
diff --git a/ash/webui/focus_mode/resources/app.ts b/ash/webui/focus_mode/resources/app.ts
index 10bd1d6f..df323e8 100644
--- a/ash/webui/focus_mode/resources/app.ts
+++ b/ash/webui/focus_mode/resources/app.ts
@@ -100,15 +100,13 @@
       state: getPlaybackState(newPlaybackStatus.state),
       title: currentTrack.title,
       url: currentTrack.mediaUrl,
-      clientCurrentTime: {msec: clientCurrentTime.getTime()},
+      clientCurrentTime: clientCurrentTime,
       playbackStartOffset: playbackStartOffset,
       mediaTimeCurrent: newPlaybackStatus.position,
       mediaStart: start,
       mediaEnd: end,
-      clientStartTime: {
-        msec: (initial ? newPlaybackStatus.loadTime : clientTimeLastReport)
-                  .getTime(),
-      },
+      clientStartTime: initial ? newPlaybackStatus.loadTime :
+                                 clientTimeLastReport,
       initialPlayback: initial,
     });
     playbackStatus = newPlaybackStatus;
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 24d7eb9..a6085be0 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -4245,7 +4245,6 @@
       "sequence_checker_nocompile.nc",
       "strings/cstring_view_nocompile.nc",
       "strings/span_printf_nocompile.nc",
-      "strings/stringprintf_nocompile.nc",
       "synchronization/lock_nocompile.nc",
       "task/bind_post_task_nocompile.nc",
       "task/task_traits_nocompile.nc",
diff --git a/base/metrics/histogram_macros.h b/base/metrics/histogram_macros.h
index c527bbb..5a1d37e 100644
--- a/base/metrics/histogram_macros.h
+++ b/base/metrics/histogram_macros.h
@@ -423,7 +423,8 @@
     constant_histogram_name, index, constant_maximum,                       \
     histogram_add_method_invocation, histogram_factory_get_invocation)      \
   do {                                                                      \
-    static std::atomic_uintptr_t atomic_histograms[constant_maximum];       \
+    static std::array<std::atomic_uintptr_t, constant_maximum>              \
+        atomic_histograms;                                                  \
     DCHECK_LE(0, index);                                                    \
     DCHECK_LT(index, constant_maximum);                                     \
     HISTOGRAM_POINTER_USE(                                                  \
diff --git a/base/strings/stringprintf.h b/base/strings/stringprintf.h
index 7de6f91..81a70dc 100644
--- a/base/strings/stringprintf.h
+++ b/base/strings/stringprintf.h
@@ -26,42 +26,6 @@
                                        const Args&... args) {
   return absl::StrFormat(format, args...);
 }
-// Returns a C++ string given `printf()`-like input. The format string must be a
-// run-time value (like with `std::vformat()`), or this will not compile.
-// Because this does not check arguments at compile-time, prefer
-// `StringPrintf()` whenever possible.
-template <typename... Args>
-[[nodiscard]] std::string StringPrintfNonConstexpr(std::string_view format,
-                                                   const Args&... args) {
-  std::string output;
-  CHECK(absl::FormatUntyped(&output, absl::UntypedFormatSpec(format),
-                            {absl::FormatArg(args)...}));
-  return output;
-}
-
-// If possible, guide users to use `StringPrintf()` instead of
-// `StringPrintfNonConstexpr()` when the format string is constexpr.
-//
-// It would be nice to do this with `std::enable_if`, but I don't know of a way;
-// whether a string constant's value is available at compile time is not
-// something easily obtained from the type system, and trying to pass various
-// forms of string constant to non-type template parameters produces a variety
-// of compile errors.
-#if HAS_ATTRIBUTE(enable_if)
-// Disable calling with a constexpr `std::string_view`.
-template <typename... Args>
-[[nodiscard]] std::string StringPrintfNonConstexpr(std::string_view format,
-                                                   const Args&... args)
-    ENABLE_IF_ATTR(
-        [](std::string_view s) { return s.empty() || s[0] == s[0]; }(format),
-        "Use StringPrintf() for constexpr format strings") = delete;
-// Disable calling with a constexpr `char[]` or `char*`.
-template <typename... Args>
-[[nodiscard]] std::string StringPrintfNonConstexpr(const char* format,
-                                                   const Args&... args)
-    ENABLE_IF_ATTR([](const char* s) { return !!s; }(format),
-                   "Use StringPrintf() for constexpr format strings") = delete;
-#endif
 
 // Returns a C++ string given `vprintf()`-like input.
 [[nodiscard]] PRINTF_FORMAT(1, 0) BASE_EXPORT std::string
diff --git a/base/strings/stringprintf_nocompile.nc b/base/strings/stringprintf_nocompile.nc
deleted file mode 100644
index 7166ec9..0000000
--- a/base/strings/stringprintf_nocompile.nc
+++ /dev/null
@@ -1,31 +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.
-
-// This is a "No Compile Test" suite.
-// http://dev.chromium.org/developers/testing/no-compile-tests
-
-#include "base/strings/stringprintf.h"
-
-#include <tuple>
-
-#include "base/strings/cstring_view.h"
-
-namespace base {
-
-void ConstexprStringView() {
-  static constexpr base::cstring_view kTest = "test %s";
-  std::ignore = StringPrintfNonConstexpr(kTest.data(), "123");  // expected-error {{call to deleted function 'StringPrintfNonConstexpr'}}
-}
-
-void ConstexprCharArray() {
-  static constexpr char kTest[] = "test %s";
-  std::ignore = StringPrintfNonConstexpr(kTest, "123");  // expected-error {{call to deleted function 'StringPrintfNonConstexpr'}}
-}
-
-void ConstexprCharPointer() {
-  static constexpr const char* kTest = "test %s";
-  std::ignore = StringPrintfNonConstexpr(kTest, "123");  // expected-error {{call to deleted function 'StringPrintfNonConstexpr'}}
-}
-
-}  // namespace base
diff --git a/build/linux/sysroot_scripts/generated_package_lists/bullseye.amd64 b/build/linux/sysroot_scripts/generated_package_lists/bullseye.amd64
index 2526157..f38b6c7 100644
--- a/build/linux/sysroot_scripts/generated_package_lists/bullseye.amd64
+++ b/build/linux/sysroot_scripts/generated_package_lists/bullseye.amd64
@@ -5,7 +5,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom0_1.0.0.errata1-3_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom3_3.6.0-1~bpo11+1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/apparmor/libapparmor1_2.13.6-10_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/argon2/libargon2-1_0~20171227-0.2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-4~bpo11+1_amd64.deb
@@ -17,7 +16,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-0_2.38.0-1~bpo11+1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-data_2.38.0-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-dev_2.38.0-1~bpo11+1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/attr/libattr1_2.4.48-6_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit-common_3.0-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit1_3.0-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/avahi/libavahi-client3_0.8-5+deb11u2_amd64.deb
@@ -41,9 +39,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2_1.16.0-5_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/chromaprint/libchromaprint1_1.5.0-2_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cjson/libcjson1_1.7.14-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-0.9_0.9.2-4_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-1.0_1.0.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/colord/libcolord2_1.4.5-3_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/coreutils/coreutils_8.32-4+b1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cryptsetup/libcryptsetup12_2.3.7-1+deb11u1_amd64.deb
@@ -56,9 +52,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-modules-db_2.1.27+dfsg-2.1+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d4_0.7.1-3_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d6_1.0.0-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-bin_1.14.6-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-daemon_1.14.6-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-session-bus-common_1.14.6-1_all.deb
@@ -83,16 +77,12 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/e/expat/libexpat1_2.2.10-2+deb11u5_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec-dev_4.3.5-0+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec58_4.3.5-0+deb11u1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec59_5.1.3-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat-dev_4.3.5-0+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat58_4.3.5-0+deb11u1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat59_5.1.3-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil-dev_4.3.5-0+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil56_4.3.5-0+deb11u1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil57_5.1.3-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample-dev_4.3.5-0+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample3_4.3.5-0+deb11u1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample4_5.1.3-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac-dev_1.3.3-2+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac8_1.3.3-2+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/fontconfig/fontconfig-config_2.13.1-4.2_all.deb
@@ -129,8 +119,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gcc-defaults/gcc_10.2.1-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm-compat4_1.19-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm6_1.19-2_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf-xlib-2.0-0_2.40.2-2_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf2.0-0_2.40.2-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/gir1.2-gdkpixbuf-2.0_2.42.2+dfsg-1+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1+deb11u1_amd64.deb
@@ -187,8 +175,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/hicolor-icon-theme/hicolor-icon-theme_0.17-2_all.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/highway/libhwy1_1.0.3-3_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/icu-devtools_67.1-7_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu-dev_67.1-7_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu67_67.1-7_amd64.deb
@@ -197,7 +183,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/isl/libisl23_0.23-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig-dev_2.1-3.1+b2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jpeg-xl/libjxl0.7_0.7.0-10_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-c/libjson-c5_0.15-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-common_1.6.2-1_all.deb
@@ -238,8 +223,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/gir1.2-dbusmenu-glib-0.4_18.10.20180917~bzr492+repack1-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate-dev_1.10-2~bpo11+1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate0_1.10-2~bpo11+1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_amd64.deb
@@ -282,8 +265,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libg/libgudev/libgudev-1.0-dev_234-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice-dev_1.0.10-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice6_1.0.10-1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn/libidn11_1.33-3_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-dev_2.3.0-5_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libinput/libinput-bin_1.16.4-3_amd64.deb
@@ -297,7 +278,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libm/libmd/libmd0_1.0.3-3_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl-dev_1.3.0-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libopenmpt/libopenmpt0_0.4.11-1_amd64.deb
@@ -310,7 +290,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librabbitmq/librabbitmq4_0.10.0-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librist/librist4_0.2.7+dfsg-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librsvg/librsvg2-2_2.50.3+dfsg-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libseccomp/libseccomp2_2.5.1-1+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_amd64.deb
@@ -338,7 +317,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-common_1.3.1-1+deb11u1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-dev_1.3.1-1+deb11u1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1+deb11u1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtool/libltdl7_2.4.6-15_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libudfread/libudfread0_1.1.1-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libutempter/libutempter-dev_1.2.1-2_amd64.deb
@@ -355,7 +333,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvorbis/libvorbisfile3_1.3.7-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx7_1.12.0-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-common_1.8-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-dev_1.8-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom2_1.8-2_amd64.deb
@@ -440,20 +417,16 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/make-dfsg/make_4.3-4.1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mako/python3-mako_1.1.3+ds1-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/markupsafe/python3-markupsafe_2.0.1-2~bpo11+1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mbedtls/libmbedcrypto7_2.28.3-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/md4c/libmd4c0_0.4.7-2_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/media-types/media-types_4.0.0_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl-mesa0_20.3.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.5-1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa_20.3.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm-dev_20.3.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm1_20.3.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dri_20.3.5-1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglapi-mesa_20.3.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglx-mesa0_20.3.5-1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/mesa-common-dev_20.3.5-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip1_1.1-8+b1_amd64.deb
@@ -498,7 +471,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/pango1.0-tools_1.46.2-3_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/patch/patch_2.7.6-7_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pci.ids/pci.ids_0.0~2021.02.08-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_amd64.deb
@@ -573,7 +545,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/libreadline8_8.1-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/readline-common_8.1-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rust-rav1e/librav1e0_0.5.1-6_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sensible-utils/sensible-utils_0.0.14_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/setuptools/python3-pkg-resources_66.1.1-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/shadow/login_4.8.1-1_amd64.deb
@@ -587,8 +558,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/speex/libspeex1_1.2~rc1.2-1.1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.4-gnutls_1.4.2-1.3_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.5-gnutls_1.5.1-1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/svt-av1/libsvtav1enc1_1.4.1+dfsg-1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libpam-systemd_252.5-2~bpo11+1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-dev_252.5-2~bpo11+1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-shared_252.5-2~bpo11+1_amd64.deb
@@ -631,9 +600,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/wayland/libwayland-server0_1.18.0-2~exp1.1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/webrtc-audio-processing/libwebrtc-audio-processing1_0.3-1+b1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-160_0.160.3011+gitcde9a93-2.1_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-164_0.164.3095+gitbaee400-3_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-192_3.4-2_amd64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-199_3.5-2+b1_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0-dev_0.4.0-1+b3_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0_0.4.0-1+b3_amd64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-keysyms/libxcb-keysyms1_0.4.0-1+b2_amd64.deb
diff --git a/build/linux/sysroot_scripts/generated_package_lists/bullseye.arm64 b/build/linux/sysroot_scripts/generated_package_lists/bullseye.arm64
index 73b565d..82d8486 100644
--- a/build/linux/sysroot_scripts/generated_package_lists/bullseye.arm64
+++ b/build/linux/sysroot_scripts/generated_package_lists/bullseye.arm64
@@ -5,7 +5,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom0_1.0.0.errata1-3_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom3_3.6.0-1~bpo11+1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/apparmor/libapparmor1_2.13.6-10_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/argon2/libargon2-1_0~20171227-0.2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-4~bpo11+1_arm64.deb
@@ -17,7 +16,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-0_2.38.0-1~bpo11+1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-data_2.38.0-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-dev_2.38.0-1~bpo11+1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/attr/libattr1_2.4.48-6_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit-common_3.0-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit1_3.0-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/avahi/libavahi-client3_0.8-5+deb11u2_arm64.deb
@@ -41,9 +39,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2_1.16.0-5_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/chromaprint/libchromaprint1_1.5.0-2_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cjson/libcjson1_1.7.14-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-0.9_0.9.2-4_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-1.0_1.0.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/colord/libcolord2_1.4.5-3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/coreutils/coreutils_8.32-4_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cryptsetup/libcryptsetup12_2.3.7-1+deb11u1_arm64.deb
@@ -56,9 +52,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-modules-db_2.1.27+dfsg-2.1+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d4_0.7.1-3_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d6_1.0.0-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-bin_1.14.6-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-daemon_1.14.6-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-session-bus-common_1.14.6-1_all.deb
@@ -83,16 +77,12 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/e/expat/libexpat1_2.2.10-2+deb11u5_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec-dev_4.3.5-0+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec58_4.3.5-0+deb11u1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec59_5.1.3-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat-dev_4.3.5-0+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat58_4.3.5-0+deb11u1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat59_5.1.3-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil-dev_4.3.5-0+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil56_4.3.5-0+deb11u1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil57_5.1.3-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample-dev_4.3.5-0+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample3_4.3.5-0+deb11u1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample4_5.1.3-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac-dev_1.3.3-2+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac8_1.3.3-2+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/fontconfig/fontconfig-config_2.13.1-4.2_all.deb
@@ -128,8 +118,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gcc-defaults/gcc_10.2.1-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm-compat4_1.19-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm6_1.19-2_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf-xlib-2.0-0_2.40.2-2_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf2.0-0_2.40.2-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/gir1.2-gdkpixbuf-2.0_2.42.2+dfsg-1+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1+deb11u1_arm64.deb
@@ -186,8 +174,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/hicolor-icon-theme/hicolor-icon-theme_0.17-2_all.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/highway/libhwy1_1.0.3-3_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/icu-devtools_67.1-7_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu-dev_67.1-7_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu67_67.1-7_arm64.deb
@@ -195,7 +181,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/isl/libisl23_0.23-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig-dev_2.1-3.1+b2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jpeg-xl/libjxl0.7_0.7.0-10_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-c/libjson-c5_0.15-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-common_1.6.2-1_all.deb
@@ -236,8 +221,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/gir1.2-dbusmenu-glib-0.4_18.10.20180917~bzr492+repack1-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate-dev_1.10-2~bpo11+1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate0_1.10-2~bpo11+1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_arm64.deb
@@ -282,8 +265,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libg/libgudev/libgudev-1.0-dev_234-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice-dev_1.0.10-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice6_1.0.10-1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn/libidn11_1.33-3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-dev_2.3.0-5_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libinput/libinput-bin_1.16.4-3_arm64.deb
@@ -297,11 +278,9 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libm/libmd/libmd0_1.0.3-3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl-dev_1.3.0-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libopenmpt/libopenmpt0_0.4.11-1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpgm/libpgm-5.3-0_5.3.128~dfsg-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_arm64.deb
@@ -310,7 +289,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librabbitmq/librabbitmq4_0.10.0-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librist/librist4_0.2.7+dfsg-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librsvg/librsvg2-2_2.50.3+dfsg-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libseccomp/libseccomp2_2.5.1-1+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_arm64.deb
@@ -338,7 +316,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-common_1.3.1-1+deb11u1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-dev_1.3.1-1+deb11u1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1+deb11u1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtool/libltdl7_2.4.6-15_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libudfread/libudfread0_1.1.1-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libutempter/libutempter-dev_1.2.1-2_arm64.deb
@@ -355,7 +332,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvorbis/libvorbisfile3_1.3.7-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx7_1.12.0-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-common_1.8-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-dev_1.8-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom2_1.8-2_arm64.deb
@@ -440,20 +416,16 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/make-dfsg/make_4.3-4.1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mako/python3-mako_1.1.3+ds1-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/markupsafe/python3-markupsafe_2.0.1-2~bpo11+1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mbedtls/libmbedcrypto7_2.28.3-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/md4c/libmd4c0_0.4.7-2_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/media-types/media-types_4.0.0_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl-mesa0_20.3.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.5-1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa_20.3.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm-dev_20.3.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm1_20.3.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dri_20.3.5-1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglapi-mesa_20.3.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglx-mesa0_20.3.5-1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/mesa-common-dev_20.3.5-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip1_1.1-8+b1_arm64.deb
@@ -498,7 +470,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/pango1.0-tools_1.46.2-3_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/patch/patch_2.7.6-7_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pci.ids/pci.ids_0.0~2021.02.08-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_arm64.deb
@@ -573,7 +544,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/libreadline8_8.1-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/readline-common_8.1-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rust-rav1e/librav1e0_0.5.1-6_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sensible-utils/sensible-utils_0.0.14_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/setuptools/python3-pkg-resources_66.1.1-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/shadow/login_4.8.1-1_arm64.deb
@@ -587,8 +557,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/speex/libspeex1_1.2~rc1.2-1.1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.4-gnutls_1.4.2-1.3_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.5-gnutls_1.5.1-1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/svt-av1/libsvtav1enc1_1.4.1+dfsg-1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libpam-systemd_252.5-2~bpo11+1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-dev_252.5-2~bpo11+1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-shared_252.5-2~bpo11+1_arm64.deb
@@ -631,9 +599,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/wayland/libwayland-server0_1.18.0-2~exp1.1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/webrtc-audio-processing/libwebrtc-audio-processing1_0.3-1+b1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-160_0.160.3011+gitcde9a93-2.1_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-164_0.164.3095+gitbaee400-3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-192_3.4-2_arm64.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-199_3.5-2+b1_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0-dev_0.4.0-1+b3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0_0.4.0-1+b3_arm64.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-keysyms/libxcb-keysyms1_0.4.0-1+b2_arm64.deb
diff --git a/build/linux/sysroot_scripts/generated_package_lists/bullseye.armhf b/build/linux/sysroot_scripts/generated_package_lists/bullseye.armhf
index d479c216..2fabdd8 100644
--- a/build/linux/sysroot_scripts/generated_package_lists/bullseye.armhf
+++ b/build/linux/sysroot_scripts/generated_package_lists/bullseye.armhf
@@ -5,7 +5,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom0_1.0.0.errata1-3_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom3_3.6.0-1~bpo11+1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/apparmor/libapparmor1_2.13.6-10_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/argon2/libargon2-1_0~20171227-0.2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-4~bpo11+1_armhf.deb
@@ -17,7 +16,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-0_2.38.0-1~bpo11+1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-data_2.38.0-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-dev_2.38.0-1~bpo11+1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/attr/libattr1_2.4.48-6_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit-common_3.0-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit1_3.0-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/avahi/libavahi-client3_0.8-5+deb11u2_armhf.deb
@@ -41,9 +39,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2_1.16.0-5_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/chromaprint/libchromaprint1_1.5.0-2_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cjson/libcjson1_1.7.14-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-0.9_0.9.2-4_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-1.0_1.0.5-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/colord/libcolord2_1.4.5-3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/coreutils/coreutils_8.32-4_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cryptsetup/libcryptsetup12_2.3.7-1+deb11u1_armhf.deb
@@ -56,9 +52,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-modules-db_2.1.27+dfsg-2.1+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d4_0.7.1-3_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d6_1.0.0-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-bin_1.14.6-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-daemon_1.14.6-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-session-bus-common_1.14.6-1_all.deb
@@ -83,16 +77,12 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/e/expat/libexpat1_2.2.10-2+deb11u5_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec-dev_4.3.5-0+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec58_4.3.5-0+deb11u1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec59_5.1.3-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat-dev_4.3.5-0+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat58_4.3.5-0+deb11u1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat59_5.1.3-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil-dev_4.3.5-0+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil56_4.3.5-0+deb11u1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil57_5.1.3-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample-dev_4.3.5-0+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample3_4.3.5-0+deb11u1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample4_5.1.3-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac-dev_1.3.3-2+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac8_1.3.3-2+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/fontconfig/fontconfig-config_2.13.1-4.2_all.deb
@@ -125,8 +115,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gcc-defaults/gcc_10.2.1-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm-compat4_1.19-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm6_1.19-2_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf-xlib-2.0-0_2.40.2-2_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf2.0-0_2.40.2-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/gir1.2-gdkpixbuf-2.0_2.42.2+dfsg-1+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1+deb11u1_armhf.deb
@@ -183,8 +171,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/hicolor-icon-theme/hicolor-icon-theme_0.17-2_all.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/highway/libhwy1_1.0.3-3_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/icu-devtools_67.1-7_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu-dev_67.1-7_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu67_67.1-7_armhf.deb
@@ -192,7 +178,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/isl/libisl23_0.23-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig-dev_2.1-3.1+b2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jpeg-xl/libjxl0.7_0.7.0-10_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-c/libjson-c5_0.15-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-common_1.6.2-1_all.deb
@@ -233,8 +218,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/gir1.2-dbusmenu-glib-0.4_18.10.20180917~bzr492+repack1-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate-dev_1.10-2~bpo11+1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate0_1.10-2~bpo11+1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_armhf.deb
@@ -279,8 +262,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libg/libgudev/libgudev-1.0-dev_234-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice-dev_1.0.10-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice6_1.0.10-1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn/libidn11_1.33-3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-dev_2.3.0-5_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libinput/libinput-bin_1.16.4-3_armhf.deb
@@ -294,11 +275,9 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libm/libmd/libmd0_1.0.3-3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl-dev_1.3.0-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libopenmpt/libopenmpt0_0.4.11-1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpgm/libpgm-5.3-0_5.3.128~dfsg-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_armhf.deb
@@ -307,7 +286,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librabbitmq/librabbitmq4_0.10.0-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librist/librist4_0.2.7+dfsg-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librsvg/librsvg2-2_2.50.3+dfsg-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libseccomp/libseccomp2_2.5.1-1+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_armhf.deb
@@ -335,7 +313,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-common_1.3.1-1+deb11u1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-dev_1.3.1-1+deb11u1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1+deb11u1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtool/libltdl7_2.4.6-15_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libudfread/libudfread0_1.1.1-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libutempter/libutempter-dev_1.2.1-2_armhf.deb
@@ -352,7 +329,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvorbis/libvorbisfile3_1.3.7-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx7_1.12.0-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-common_1.8-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-dev_1.8-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom2_1.8-2_armhf.deb
@@ -437,20 +413,15 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/make-dfsg/make_4.3-4.1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mako/python3-mako_1.1.3+ds1-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/markupsafe/python3-markupsafe_2.0.1-2~bpo11+1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mbedtls/libmbedcrypto7_2.28.3-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/md4c/libmd4c0_0.4.7-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/media-types/media-types_4.0.0_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl-mesa0_20.3.5-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.5-1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa_20.3.5-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm-dev_20.3.5-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm1_20.3.5-1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.5-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dri_20.3.5-1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.5-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglapi-mesa_20.3.5-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglx-mesa0_20.3.5-1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.5-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/mesa-common-dev_20.3.5-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip1_1.1-8+b1_armhf.deb
@@ -474,7 +445,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/n/nspr/libnspr4_4.29-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/n/nss/libnss3-dev_3.61-1+deb11u3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/n/nss/libnss3_3.61-1+deb11u3_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/n/numactl/libnuma1_2.0.12-1+b1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/o/ocl-icd/ocl-icd-libopencl1_2.2.14-2_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/o/openjpeg2/libopenjp2-7_2.4.0-3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/o/openldap/libldap-2.4-2_2.4.59+dfsg-1~bpo11+1_armhf.deb
@@ -495,7 +465,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/pango1.0-tools_1.46.2-3_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/patch/patch_2.7.6-7_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pci.ids/pci.ids_0.0~2021.02.08-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_armhf.deb
@@ -570,7 +539,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/libreadline8_8.1-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/readline-common_8.1-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rust-rav1e/librav1e0_0.5.1-6_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sensible-utils/sensible-utils_0.0.14_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/setuptools/python3-pkg-resources_66.1.1-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/shadow/login_4.8.1-1_armhf.deb
@@ -584,8 +552,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/speex/libspeex1_1.2~rc1.2-1.1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.4-gnutls_1.4.2-1.3_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.5-gnutls_1.5.1-1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/svt-av1/libsvtav1enc1_1.4.1+dfsg-1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libpam-systemd_252.5-2~bpo11+1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-dev_252.5-2~bpo11+1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-shared_252.5-2~bpo11+1_armhf.deb
@@ -628,9 +594,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/wayland/libwayland-server0_1.18.0-2~exp1.1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/webrtc-audio-processing/libwebrtc-audio-processing1_0.3-1+b1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-160_0.160.3011+gitcde9a93-2.1_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-164_0.164.3095+gitbaee400-3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-192_3.4-2_armhf.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-199_3.5-2+b1_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0-dev_0.4.0-1+b3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0_0.4.0-1+b3_armhf.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-keysyms/libxcb-keysyms1_0.4.0-1+b2_armhf.deb
diff --git a/build/linux/sysroot_scripts/generated_package_lists/bullseye.i386 b/build/linux/sysroot_scripts/generated_package_lists/bullseye.i386
index 9d3591d..00c3b08 100644
--- a/build/linux/sysroot_scripts/generated_package_lists/bullseye.i386
+++ b/build/linux/sysroot_scripts/generated_package_lists/bullseye.i386
@@ -5,7 +5,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom0_1.0.0.errata1-3_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom3_3.6.0-1~bpo11+1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/apparmor/libapparmor1_2.13.6-10_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/argon2/libargon2-1_0~20171227-0.2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-4~bpo11+1_i386.deb
@@ -17,7 +16,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-0_2.38.0-1~bpo11+1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-data_2.38.0-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-dev_2.38.0-1~bpo11+1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/attr/libattr1_2.4.48-6_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit-common_3.0-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit1_3.0-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/avahi/libavahi-client3_0.8-5+deb11u2_i386.deb
@@ -41,9 +39,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2_1.16.0-5_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/chromaprint/libchromaprint1_1.5.0-2_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cjson/libcjson1_1.7.14-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-0.9_0.9.2-4_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-1.0_1.0.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/colord/libcolord2_1.4.5-3_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/coreutils/coreutils_8.32-4_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cryptsetup/libcryptsetup12_2.3.7-1+deb11u1_i386.deb
@@ -56,9 +52,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-modules-db_2.1.27+dfsg-2.1+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d4_0.7.1-3_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d6_1.0.0-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-bin_1.14.6-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-daemon_1.14.6-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-session-bus-common_1.14.6-1_all.deb
@@ -83,16 +77,12 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/e/expat/libexpat1_2.2.10-2+deb11u5_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec-dev_4.3.5-0+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec58_4.3.5-0+deb11u1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec59_5.1.3-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat-dev_4.3.5-0+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat58_4.3.5-0+deb11u1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat59_5.1.3-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil-dev_4.3.5-0+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil56_4.3.5-0+deb11u1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil57_5.1.3-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample-dev_4.3.5-0+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample3_4.3.5-0+deb11u1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample4_5.1.3-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac-dev_1.3.3-2+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac8_1.3.3-2+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/fontconfig/fontconfig-config_2.13.1-4.2_all.deb
@@ -127,8 +117,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gcc-defaults/gcc_10.2.1-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm-compat4_1.19-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm6_1.19-2_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf-xlib-2.0-0_2.40.2-2_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf2.0-0_2.40.2-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/gir1.2-gdkpixbuf-2.0_2.42.2+dfsg-1+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1+deb11u1_i386.deb
@@ -185,8 +173,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/hicolor-icon-theme/hicolor-icon-theme_0.17-2_all.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/highway/libhwy1_1.0.3-3_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/icu-devtools_67.1-7_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu-dev_67.1-7_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu67_67.1-7_i386.deb
@@ -194,7 +180,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/isl/libisl23_0.23-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig-dev_2.1-3.1+b2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jpeg-xl/libjxl0.7_0.7.0-10_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-c/libjson-c5_0.15-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-common_1.6.2-1_all.deb
@@ -235,8 +220,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/gir1.2-dbusmenu-glib-0.4_18.10.20180917~bzr492+repack1-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate-dev_1.10-2~bpo11+1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate0_1.10-2~bpo11+1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_i386.deb
@@ -279,8 +262,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libg/libgudev/libgudev-1.0-dev_234-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice-dev_1.0.10-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice6_1.0.10-1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn/libidn11_1.33-3_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-dev_2.3.0-5_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libinput/libinput-bin_1.16.4-3_i386.deb
@@ -294,7 +275,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libm/libmd/libmd0_1.0.3-3_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl-dev_1.3.0-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libopenmpt/libopenmpt0_0.4.11-1_i386.deb
@@ -307,7 +287,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librabbitmq/librabbitmq4_0.10.0-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librist/librist4_0.2.7+dfsg-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librsvg/librsvg2-2_2.50.3+dfsg-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libseccomp/libseccomp2_2.5.1-1+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_i386.deb
@@ -335,7 +314,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-common_1.3.1-1+deb11u1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-dev_1.3.1-1+deb11u1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1+deb11u1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtool/libltdl7_2.4.6-15_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libudfread/libudfread0_1.1.1-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libutempter/libutempter-dev_1.2.1-2_i386.deb
@@ -352,7 +330,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvorbis/libvorbisfile3_1.3.7-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx7_1.12.0-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-common_1.8-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-dev_1.8-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom2_1.8-2_i386.deb
@@ -437,20 +414,16 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/make-dfsg/make_4.3-4.1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mako/python3-mako_1.1.3+ds1-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/markupsafe/python3-markupsafe_2.0.1-2~bpo11+1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mbedtls/libmbedcrypto7_2.28.3-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/md4c/libmd4c0_0.4.7-2_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/media-types/media-types_4.0.0_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl-mesa0_20.3.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.5-1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa_20.3.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm-dev_20.3.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm1_20.3.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dri_20.3.5-1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglapi-mesa_20.3.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglx-mesa0_20.3.5-1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/mesa-common-dev_20.3.5-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip1_1.1-8+b1_i386.deb
@@ -495,7 +468,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/pango1.0-tools_1.46.2-3_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/patch/patch_2.7.6-7_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pci.ids/pci.ids_0.0~2021.02.08-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_i386.deb
@@ -570,7 +542,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/libreadline8_8.1-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/readline-common_8.1-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rust-rav1e/librav1e0_0.5.1-6_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sensible-utils/sensible-utils_0.0.14_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/setuptools/python3-pkg-resources_66.1.1-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/shadow/login_4.8.1-1_i386.deb
@@ -584,8 +555,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/speex/libspeex1_1.2~rc1.2-1.1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.4-gnutls_1.4.2-1.3_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.5-gnutls_1.5.1-1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/svt-av1/libsvtav1enc1_1.4.1+dfsg-1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libpam-systemd_252.5-2~bpo11+1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-dev_252.5-2~bpo11+1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-shared_252.5-2~bpo11+1_i386.deb
@@ -628,9 +597,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/wayland/libwayland-server0_1.18.0-2~exp1.1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/webrtc-audio-processing/libwebrtc-audio-processing1_0.3-1+b1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-160_0.160.3011+gitcde9a93-2.1_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-164_0.164.3095+gitbaee400-3_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-192_3.4-2_i386.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-199_3.5-2+b1_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0-dev_0.4.0-1+b3_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0_0.4.0-1+b3_i386.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-keysyms/libxcb-keysyms1_0.4.0-1+b2_i386.deb
diff --git a/build/linux/sysroot_scripts/generated_package_lists/bullseye.mips64el b/build/linux/sysroot_scripts/generated_package_lists/bullseye.mips64el
index db57835..efa39c1 100644
--- a/build/linux/sysroot_scripts/generated_package_lists/bullseye.mips64el
+++ b/build/linux/sysroot_scripts/generated_package_lists/bullseye.mips64el
@@ -5,7 +5,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom0_1.0.0.errata1-3_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom3_3.6.0-1~bpo11+1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/apparmor/libapparmor1_2.13.6-10_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/argon2/libargon2-1_0~20171227-0.2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-4~bpo11+1_mips64el.deb
@@ -17,7 +16,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-0_2.38.0-1~bpo11+1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-data_2.38.0-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-dev_2.38.0-1~bpo11+1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/attr/libattr1_2.4.48-6_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit-common_3.0-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit1_3.0-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/avahi/libavahi-client3_0.8-5+deb11u2_mips64el.deb
@@ -41,9 +39,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2_1.16.0-5_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/chromaprint/libchromaprint1_1.5.0-2_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cjson/libcjson1_1.7.14-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-0.9_0.9.2-4_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-1.0_1.0.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/colord/libcolord2_1.4.5-3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/coreutils/coreutils_8.32-4_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cryptsetup/libcryptsetup12_2.3.7-1+deb11u1_mips64el.deb
@@ -56,9 +52,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-modules-db_2.1.27+dfsg-2.1+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d4_0.7.1-3_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d6_1.0.0-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-bin_1.14.6-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-daemon_1.14.6-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-session-bus-common_1.14.6-1_all.deb
@@ -83,16 +77,12 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/e/expat/libexpat1_2.2.10-2+deb11u5_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec-dev_4.3.5-0+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec58_4.3.5-0+deb11u1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec59_5.1.3-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat-dev_4.3.5-0+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat58_4.3.5-0+deb11u1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat59_5.1.3-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil-dev_4.3.5-0+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil56_4.3.5-0+deb11u1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil57_5.1.3-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample-dev_4.3.5-0+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample3_4.3.5-0+deb11u1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample4_5.1.3-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac-dev_1.3.3-2+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac8_1.3.3-2+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/fontconfig/fontconfig-config_2.13.1-4.2_all.deb
@@ -123,8 +113,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gcc-defaults/gcc_10.2.1-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm-compat4_1.19-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm6_1.19-2_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf-xlib-2.0-0_2.40.2-2_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf2.0-0_2.40.2-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/gir1.2-gdkpixbuf-2.0_2.42.2+dfsg-1+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1+deb11u1_mips64el.deb
@@ -181,8 +169,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/hicolor-icon-theme/hicolor-icon-theme_0.17-2_all.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/highway/libhwy1_1.0.3-3_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/icu-devtools_67.1-7_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu-dev_67.1-7_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu67_67.1-7_mips64el.deb
@@ -190,7 +176,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/isl/libisl23_0.23-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig-dev_2.1-3.1+b2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jpeg-xl/libjxl0.7_0.7.0-10_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-c/libjson-c5_0.15-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-common_1.6.2-1_all.deb
@@ -231,8 +216,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/gir1.2-dbusmenu-glib-0.4_18.10.20180917~bzr492+repack1-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate-dev_1.10-2~bpo11+1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate0_1.10-2~bpo11+1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_mips64el.deb
@@ -274,8 +257,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libg/libgudev/libgudev-1.0-dev_234-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice-dev_1.0.10-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice6_1.0.10-1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn/libidn11_1.33-3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-dev_2.3.0-5_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libinput/libinput-bin_1.16.4-3_mips64el.deb
@@ -289,11 +270,9 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libm/libmd/libmd0_1.0.3-3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl-dev_1.3.0-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libopenmpt/libopenmpt0_0.4.11-1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpgm/libpgm-5.3-0_5.3.128~dfsg-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_mips64el.deb
@@ -302,7 +281,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librabbitmq/librabbitmq4_0.10.0-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librist/librist4_0.2.7+dfsg-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librsvg/librsvg2-2_2.50.3+dfsg-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libseccomp/libseccomp2_2.5.1-1+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_mips64el.deb
@@ -330,7 +308,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-common_1.3.1-1+deb11u1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-dev_1.3.1-1+deb11u1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1+deb11u1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtool/libltdl7_2.4.6-15_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libudfread/libudfread0_1.1.1-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libutempter/libutempter-dev_1.2.1-2_mips64el.deb
@@ -347,7 +324,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvorbis/libvorbisfile3_1.3.7-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx7_1.12.0-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-common_1.8-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-dev_1.8-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom2_1.8-2_mips64el.deb
@@ -432,20 +408,16 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/make-dfsg/make_4.3-4.1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mako/python3-mako_1.1.3+ds1-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/markupsafe/python3-markupsafe_2.0.1-2~bpo11+1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mbedtls/libmbedcrypto7_2.28.3-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/md4c/libmd4c0_0.4.7-2_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/media-types/media-types_4.0.0_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl-mesa0_20.3.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.5-1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa_20.3.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm-dev_20.3.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm1_20.3.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dri_20.3.5-1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglapi-mesa_20.3.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglx-mesa0_20.3.5-1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/mesa-common-dev_20.3.5-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip1_1.1-8+b1_mips64el.deb
@@ -490,7 +462,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/pango1.0-tools_1.46.2-3_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/patch/patch_2.7.6-7_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pci.ids/pci.ids_0.0~2021.02.08-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_mips64el.deb
@@ -565,7 +536,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/libreadline8_8.1-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/readline-common_8.1-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rust-rav1e/librav1e0_0.5.1-6_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sensible-utils/sensible-utils_0.0.14_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/setuptools/python3-pkg-resources_66.1.1-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/shadow/login_4.8.1-1_mips64el.deb
@@ -579,8 +549,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/speex/libspeex1_1.2~rc1.2-1.1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.4-gnutls_1.4.2-1.3_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.5-gnutls_1.5.1-1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/svt-av1/libsvtav1enc1_1.4.1+dfsg-1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libpam-systemd_252.5-2~bpo11+1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-dev_252.5-2~bpo11+1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-shared_252.5-2~bpo11+1_mips64el.deb
@@ -623,9 +591,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/wayland/libwayland-server0_1.18.0-2~exp1.1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/webrtc-audio-processing/libwebrtc-audio-processing1_0.3-1+b1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-160_0.160.3011+gitcde9a93-2.1_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-164_0.164.3095+gitbaee400-3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-192_3.4-2_mips64el.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-199_3.5-2+b1_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0-dev_0.4.0-1+b3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0_0.4.0-1+b3_mips64el.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-keysyms/libxcb-keysyms1_0.4.0-1+b2_mips64el.deb
diff --git a/build/linux/sysroot_scripts/generated_package_lists/bullseye.mipsel b/build/linux/sysroot_scripts/generated_package_lists/bullseye.mipsel
index 6180a771..bc3aee7 100644
--- a/build/linux/sysroot_scripts/generated_package_lists/bullseye.mipsel
+++ b/build/linux/sysroot_scripts/generated_package_lists/bullseye.mipsel
@@ -5,7 +5,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/alsa-lib/libasound2_1.2.4-1.1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom0_1.0.0.errata1-3_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/aom/libaom3_3.6.0-1~bpo11+1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/apparmor/libapparmor1_2.13.6-10_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/argon2/libargon2-1_0~20171227-0.2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/at-spi2-atk/libatk-bridge2.0-0_2.38.0-4~bpo11+1_mipsel.deb
@@ -17,7 +16,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-0_2.38.0-1~bpo11+1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-data_2.38.0-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/atk1.0/libatk1.0-dev_2.38.0-1~bpo11+1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/attr/libattr1_2.4.48-6_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit-common_3.0-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/audit/libaudit1_3.0-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/a/avahi/libavahi-client3_0.8-5+deb11u2_mipsel.deb
@@ -41,9 +39,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2-dev_1.16.0-5_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cairo/libcairo2_1.16.0-5_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/chromaprint/libchromaprint1_1.5.0-2_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cjson/libcjson1_1.7.14-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-0.9_0.9.2-4_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/codec2/libcodec2-1.0_1.0.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/colord/libcolord2_1.4.5-3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/coreutils/coreutils_8.32-4_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cryptsetup/libcryptsetup12_2.3.7-1+deb11u1_mipsel.deb
@@ -56,9 +52,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-2_2.1.27+dfsg-2.1+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/c/cyrus-sasl2/libsasl2-modules-db_2.1.27+dfsg-2.1+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d4_0.7.1-3_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dav1d/libdav1d6_1.0.0-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/db5.3/libdb5.3_5.3.28+dfsg1-0.8_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus-glib/libdbus-glib-1-2_0.110-6_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-bin_1.14.6-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-daemon_1.14.6-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/d/dbus/dbus-session-bus-common_1.14.6-1_all.deb
@@ -83,16 +77,12 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/e/expat/libexpat1_2.2.10-2+deb11u5_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec-dev_4.3.5-0+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec58_4.3.5-0+deb11u1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavcodec59_5.1.3-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat-dev_4.3.5-0+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat58_4.3.5-0+deb11u1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavformat59_5.1.3-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil-dev_4.3.5-0+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil56_4.3.5-0+deb11u1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libavutil57_5.1.3-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample-dev_4.3.5-0+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample3_4.3.5-0+deb11u1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/ffmpeg/libswresample4_5.1.3-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac-dev_1.3.3-2+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/flac/libflac8_1.3.3-2+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/f/fontconfig/fontconfig-config_2.13.1-4.2_all.deb
@@ -123,8 +113,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gcc-defaults/gcc_10.2.1-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm-compat4_1.19-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdbm/libgdbm6_1.19-2_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf-xlib-2.0-0_2.40.2-2_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf-xlib/libgdk-pixbuf2.0-0_2.40.2-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/gir1.2-gdkpixbuf-2.0_2.42.2+dfsg-1+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-0_2.42.2+dfsg-1+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/gdk-pixbuf/libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1+deb11u1_mipsel.deb
@@ -139,6 +127,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/glib2.0/libglib2.0-dev-bin_2.66.8-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/glib2.0/libglib2.0-dev_2.66.8-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/glibc/libc-dev-bin_2.31-13+deb11u5_mipsel.deb
+https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/glibc/libc6-dbg_2.31-13+deb11u5_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/glibc/libc6-dev_2.31-13+deb11u5_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/glibc/libc6-dev_2.31-13+deb11u6_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/g/glibc/libc6_2.31-13+deb11u5_mipsel.deb
@@ -180,8 +169,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz-icu0_2.7.4-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/harfbuzz/libharfbuzz0b_2.7.4-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/hicolor-icon-theme/hicolor-icon-theme_0.17-2_all.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/h/highway/libhwy1_1.0.3-3_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu-le-hb/libicu-le-hb0_1.0.3+git180724-3+b2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/icu-devtools_67.1-7_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu-dev_67.1-7_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/icu/libicu67_67.1-7_mipsel.deb
@@ -189,7 +176,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/i/isl/libisl23_0.23-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig-dev_2.1-3.1+b2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jbigkit/libjbig0_2.1-3.1+b2_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/jpeg-xl/libjxl0.7_0.7.0-10_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-c/libjson-c5_0.15-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-0_1.6.2-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/j/json-glib/libjson-glib-1.0-common_1.6.2-1_all.deb
@@ -230,8 +216,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/gir1.2-dbusmenu-glib-0.4_18.10.20180917~bzr492+repack1-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib-dev_18.10.20180917~bzr492+repack1-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-glib4_18.10.20180917~bzr492+repack1-2_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk3-4_18.10.20180917~bzr492+repack1-2_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdbusmenu/libdbusmenu-gtk4_18.10.20180917~bzr492+repack1-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate-dev_1.10-2~bpo11+1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdeflate/libdeflate0_1.10-2~bpo11+1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.104-1_mipsel.deb
@@ -273,8 +257,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libg/libgudev/libgudev-1.0-dev_234-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice-dev_1.0.10-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libice/libice6_1.0.10-1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidl/libidl-2-0_0.8.14-4+b12_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn/libidn11_1.33-3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-0_2.3.0-5_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libidn2/libidn2-dev_2.3.0-5_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libi/libinput/libinput-bin_1.16.4-3_mipsel.deb
@@ -288,11 +270,9 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libm/libmd/libmd0_1.0.3-3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl-dev_1.3.0-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnsl/libnsl2_1.3.0-2_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libn/libnss-db/libnss-db_2.2.3pre1-6+b10_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg-dev_1.3.4-0.1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libogg/libogg0_1.3.4-0.1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libo/libopenmpt/libopenmpt0_0.4.11-1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpciaccess/libpciaccess0_0.16-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpgm/libpgm-5.3-0_5.3.128~dfsg-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpng1.6/libpng-dev_1.6.37-3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpng1.6/libpng16-16_1.6.37-3_mipsel.deb
@@ -301,7 +281,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libp/libpthread-stubs/libpthread-stubs0-dev_0.4-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librabbitmq/librabbitmq4_0.10.0-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librest/librest-0.7-0_0.8.1-1.1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librist/librist4_0.2.7+dfsg-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libr/librsvg/librsvg2-2_2.50.3+dfsg-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libseccomp/libseccomp2_2.5.1-1+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libs/libselinux/libselinux1-dev_3.1-3_mipsel.deb
@@ -329,7 +308,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-common_1.3.1-1+deb11u1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc-dev_1.3.1-1+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtirpc/libtirpc3_1.3.1-1+deb11u1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libt/libtool/libltdl7_2.4.6-15_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libudfread/libudfread0_1.1.1-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libunistring/libunistring2_0.9.10-4_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libu/libutempter/libutempter-dev_1.2.1-2_mipsel.deb
@@ -346,7 +324,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvorbis/libvorbisfile3_1.3.7-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx-dev_1.9.0-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx6_1.9.0-1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libv/libvpx/libvpx7_1.12.0-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-common_1.8-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom-dev_1.8-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/libw/libwacom/libwacom2_1.8-2_mipsel.deb
@@ -431,20 +408,16 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/make-dfsg/make_4.3-4.1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mako/python3-mako_1.1.3+ds1-2_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/markupsafe/python3-markupsafe_2.0.1-2~bpo11+1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mbedtls/libmbedcrypto7_2.28.3-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/md4c/libmd4c0_0.4.7-2_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/media-types/media-types_4.0.0_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl-mesa0_20.3.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa-dev_20.3.5-1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libegl1-mesa_20.3.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm-dev_20.3.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgbm1_20.3.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dev_20.3.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-dri_20.3.5-1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libgl1-mesa-glx_20.3.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglapi-mesa_20.3.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libglx-mesa0_20.3.5-1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/libwayland-egl1-mesa_20.3.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/mesa/mesa-common-dev_20.3.5-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip-dev_1.1-8+b1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/m/minizip/libminizip1_1.1-8+b1_mipsel.deb
@@ -489,7 +462,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoft2-1.0-0_1.46.2-3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/libpangoxft-1.0-0_1.46.2-3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pango1.0/pango1.0-tools_1.46.2-3_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pangox-compat/libpangox-1.0-0_0.0.2-5.1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/patch/patch_2.7.6-7_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pci.ids/pci.ids_0.0~2021.02.08-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/p/pciutils/libpci-dev_3.7.0-5_mipsel.deb
@@ -564,7 +536,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/libreadline8_8.1-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/readline/readline-common_8.1-1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rtmpdump/librtmp1_2.4+20151223.gitfa8646d.1-2+b2_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/r/rust-rav1e/librav1e0_0.5.1-6_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sensible-utils/sensible-utils_0.0.14_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/setuptools/python3-pkg-resources_66.1.1-1~bpo11+1_all.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/shadow/login_4.8.1-1_mipsel.deb
@@ -578,8 +549,6 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/speex/libspeex1_1.2~rc1.2-1.1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/sqlite3/libsqlite3-0_3.34.1-3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.4-gnutls_1.4.2-1.3_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/srt/libsrt1.5-gnutls_1.5.1-1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/svt-av1/libsvtav1enc1_1.4.1+dfsg-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libpam-systemd_252.5-2~bpo11+1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-dev_252.5-2~bpo11+1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/s/systemd/libsystemd-shared_252.5-2~bpo11+1_mipsel.deb
@@ -608,6 +577,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/u/util-linux/mount_2.36.1-8+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/u/util-linux/util-linux_2.36.1-8+deb11u1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/u/util-linux/uuid-dev_2.36.1-8+deb11u1_mipsel.deb
+https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/v/valgrind/valgrind_3.16.1-1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/v/vulkan-loader/libvulkan-dev_1.3.224.0-1~bpo11+1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/v/vulkan-loader/libvulkan1_1.3.224.0-1~bpo11+1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/wavpack/libwavpack1_5.4.0-1_mipsel.deb
@@ -621,9 +591,7 @@
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/wayland/libwayland-server0_1.18.0-2~exp1.1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/w/webrtc-audio-processing/libwebrtc-audio-processing1_0.3-1+b1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-160_0.160.3011+gitcde9a93-2.1_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x264/libx264-164_0.164.3095+gitbaee400-3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-192_3.4-2_mipsel.deb
-https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/x265/libx265-199_3.5-2+b1_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0-dev_0.4.0-1+b3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-image/libxcb-image0_0.4.0-1+b3_mipsel.deb
 https://snapshot.debian.org/archive/debian/20230611T210420Z/pool/main/x/xcb-util-keysyms/libxcb-keysyms1_0.4.0-1+b2_mipsel.deb
diff --git a/build/linux/sysroot_scripts/sysroot_creator.py b/build/linux/sysroot_scripts/sysroot_creator.py
index 0b8b9ae..ee4378c 100755
--- a/build/linux/sysroot_scripts/sysroot_creator.py
+++ b/build/linux/sysroot_scripts/sysroot_creator.py
@@ -7,6 +7,7 @@
 """
 
 import argparse
+import collections
 import hashlib
 import lzma
 import os
@@ -71,524 +72,69 @@
 RELEASE_FILE = "Release"
 RELEASE_FILE_GPG = "Release.gpg"
 
-# Packages common to all architectures.
+# List of development packages. Dependencies are automatically included.
 DEBIAN_PACKAGES = [
-    "comerr-dev",
-    "krb5-multidev",
-    "libaom0",
-    "libaom3",
-    "libasound2",
     "libasound2-dev",
-    "libasyncns0",
-    "libatk-bridge2.0-0",
-    "libatk-bridge2.0-dev",
-    "libatk1.0-0",
-    "libatk1.0-dev",
-    "libatomic1",
-    "libatspi2.0-0",
-    "libatspi2.0-dev",
-    "libattr1",
-    "libaudit1",
-    "libavahi-client3",
-    "libavahi-common3",
-    "libavcodec-dev",
-    "libavcodec58",
-    "libavcodec59",
     "libavformat-dev",
-    "libavformat59",
-    "libavutil-dev",
-    "libavutil56",
-    "libavutil57",
-    "libb2-1",
-    "libblkid-dev",
-    "libblkid1",
     "libbluetooth-dev",
-    "libbluetooth3",
-    "libbluray2",
-    "libbrotli-dev",
-    "libbrotli1",
-    "libbsd0",
-    "libbz2-1.0",
-    "libc6",
     "libc6-dev",
-    "libcairo-gobject2",
-    "libcairo-script-interpreter2",
-    "libcairo2",
-    "libcairo2-dev",
     "libcap-dev",
-    "libcap-ng0",
-    "libcap2",
-    "libchromaprint1",
-    "libcjson1",
-    "libcloudproviders0",
-    "libcodec2-0.9",
-    "libcodec2-1.0",
-    "libcolord2",
-    "libcom-err2",
-    "libcrypt-dev",
-    "libcrypt1",
-    "libcups2",
     "libcups2-dev",
-    "libcupsimage2",
     "libcupsimage2-dev",
-    "libcurl3-gnutls",
     "libcurl4-gnutls-dev",
-    "libdatrie-dev",
-    "libdatrie1",
-    "libdav1d4",
-    "libdav1d6",
-    "libdb5.3",
-    "libdbus-1-3",
-    "libdbus-1-dev",
-    "libdbus-glib-1-2",
     "libdbusmenu-glib-dev",
-    "libdbusmenu-glib4",
-    "libdbusmenu-gtk3-4",
-    "libdbusmenu-gtk4",
     "libdeflate-dev",
-    "libdeflate0",
-    "libdouble-conversion3",
-    "libdrm-amdgpu1",
-    "libdrm-dev",
-    "libdrm-nouveau2",
-    "libdrm-radeon1",
-    "libdrm2",
-    "libegl-dev",
-    "libegl1",
-    "libegl1-mesa",
-    "libegl1-mesa-dev",
     "libelf-dev",
-    "libelf1",
-    "libepoxy-dev",
-    "libepoxy0",
-    "libevdev-dev",
-    "libevdev2",
-    "libevent-2.1-7",
-    "libexpat1",
-    "libexpat1-dev",
-    "libffi-dev",
-    "libffi7",
     "libflac-dev",
-    "libflac8",
-    "libfontconfig-dev",
-    "libfontconfig1",
-    "libfreetype-dev",
-    "libfreetype6",
-    "libfribidi-dev",
-    "libfribidi0",
     "libgbm-dev",
-    "libgbm1",
-    "libgcc-10-dev",
-    "libgcc-s1",
-    "libgcrypt20",
     "libgcrypt20-dev",
-    "libgdk-pixbuf-2.0-0",
-    "libgdk-pixbuf-2.0-dev",
-    "libgl-dev",
-    "libgl1",
-    "libgl1-mesa-dev",
-    "libgl1-mesa-glx",
-    "libglapi-mesa",
-    "libgles-dev",
-    "libgles1",
-    "libgles2",
-    "libglib2.0-0",
-    "libglib2.0-dev",
-    "libglvnd-dev",
-    "libglvnd0",
-    "libglx-dev",
-    "libglx0",
-    "libgme0",
-    "libgmp10",
-    "libgnutls-dane0",
-    "libgnutls-openssl27",
     "libgnutls28-dev",
-    "libgnutls30",
-    "libgnutlsxx28",
-    "libgomp1",
-    "libgpg-error-dev",
-    "libgpg-error0",
-    "libgraphene-1.0-0",
-    "libgraphene-1.0-dev",
-    "libgraphite2-3",
-    "libgraphite2-dev",
-    "libgsm1",
-    "libgssapi-krb5-2",
-    "libgssrpc4",
-    "libgtk-3-0",
     "libgtk-3-dev",
-    "libgtk-4-1",
     "libgtk-4-dev",
-    "libgtk2.0-0",
-    "libgudev-1.0-0",
-    "libharfbuzz-dev",
-    "libharfbuzz-gobject0",
-    "libharfbuzz-icu0",
-    "libharfbuzz0b",
-    "libhogweed6",
-    "libhwy1",
-    "libice6",
-    "libicu-le-hb0",
-    "libicu67",
-    "libidl-2-0",
-    "libidn11",
-    "libidn2-0",
     "libinput-dev",
-    "libinput10",
     "libjbig-dev",
-    "libjbig0",
-    "libjpeg62-turbo",
-    "libjpeg62-turbo-dev",
-    "libjson-glib-1.0-0",
+    "libjpeg-dev",
     "libjsoncpp-dev",
-    "libjsoncpp24",
-    "libjxl0.7",
-    "libjxl0.7",
-    "libk5crypto3",
-    "libkadm5clnt-mit12",
-    "libkadm5srv-mit12",
-    "libkdb5-10",
-    "libkeyutils1",
-    "libkrb5-3",
     "libkrb5-dev",
-    "libkrb5support0",
-    "liblcms2-2",
-    "libldap-2.4-2",
-    "liblerc4",
-    "libltdl7",
-    "liblz4-1",
-    "liblzma5",
-    "liblzo2-2",
-    "libmbedcrypto7",
-    "libmd0",
-    "libmd4c0",
+    "liblzma-dev",
     "libminizip-dev",
-    "libminizip1",
-    "libmount-dev",
-    "libmount1",
-    "libmp3lame0",
-    "libmpg123-0",
-    "libmtdev1",
     "libncurses-dev",
-    "libncurses6",
-    "libncursesw6",
-    "libnettle8",
-    "libnghttp2-14",
-    "libnorm1",
-    "libnsl2",
-    "libnspr4",
-    "libnspr4-dev",
-    "libnss-db",
-    "libnss3",
     "libnss3-dev",
-    "libnuma1",
-    "libogg-dev",
-    "libogg0",
-    "libopengl0",
-    "libopenjp2-7",
-    "libopenmpt0",
     "libopus-dev",
-    "libopus-dev",
-    "libopus0",
-    "libp11-kit0",
-    "libpam0g",
     "libpam0g-dev",
-    "libpango-1.0-0",
-    "libpango1.0-dev",
-    "libpangocairo-1.0-0",
-    "libpangoft2-1.0-0",
-    "libpangox-1.0-0",
-    "libpangoxft-1.0-0",
     "libpci-dev",
-    "libpci3",
-    "libpciaccess0",
-    "libpcre16-3",
-    "libpcre2-16-0",
-    "libpcre2-32-0",
-    "libpcre2-8-0",
-    "libpcre2-dev",
-    "libpcre2-posix2",
-    "libpcre3",
-    "libpcre3-dev",
-    "libpcre32-3",
-    "libpcrecpp0v5",
-    "libpgm-5.3-0",
-    "libpipewire-0.3-0",
     "libpipewire-0.3-dev",
-    "libpixman-1-0",
-    "libpixman-1-dev",
-    "libpng-dev",
-    "libpng16-16",
-    "libproxy1v5",
-    "libpsl5",
-    "libpthread-stubs0-dev",
     "libpulse-dev",
-    "libpulse-mainloop-glib0",
-    "libpulse0",
-    "libqt5concurrent5",
-    "libqt5core5a",
-    "libqt5dbus5",
-    "libqt5gui5",
-    "libqt5network5",
-    "libqt5printsupport5",
-    "libqt5sql5",
-    "libqt5test5",
-    "libqt5widgets5",
-    "libqt5xml5",
-    "libqt6concurrent6",
-    "libqt6core6",
-    "libqt6dbus6",
-    "libqt6gui6",
-    "libqt6network6",
-    "libqt6opengl6",
-    "libqt6openglwidgets6",
-    "libqt6printsupport6",
-    "libqt6sql6",
-    "libqt6test6",
-    "libqt6widgets6",
-    "libqt6xml6",
-    "librabbitmq4",
-    "librav1e0",
-    "libre2-9",
     "libre2-dev",
-    "librest-0.7-0",
-    "librist4",
-    "librsvg2-2",
-    "librtmp1",
-    "libsasl2-2",
-    "libselinux1",
-    "libselinux1-dev",
-    "libsepol1",
-    "libsepol1-dev",
-    "libshine3",
-    "libsm6",
     "libsnappy-dev",
-    "libsnappy1v5",
-    "libsndfile1",
-    "libsodium23",
-    "libsoup-gnome2.4-1",
-    "libsoup2.4-1",
-    "libsoxr0",
-    "libspa-0.2-dev",
     "libspeechd-dev",
-    "libspeechd2",
-    "libspeex1",
-    "libsqlite3-0",
-    "libsrt1.5-gnutls",
-    "libssh-gcrypt-4",
-    "libssh2-1",
     "libssl-dev",
-    "libssl1.1",
-    "libstdc++-10-dev",
-    "libstdc++6",
-    "libsvtav1enc1",
-    "libswresample-dev",
-    "libswresample3",
-    "libswresample4",
     "libsystemd-dev",
-    "libsystemd0",
-    "libtasn1-6",
-    "libthai-dev",
-    "libthai0",
-    "libtheora0",
-    "libtheora0",
     "libtiff-dev",
-    "libtiff5",
-    "libtiff6",
-    "libtiffxx5",
-    "libtinfo6",
-    "libtirpc3",
-    "libts0",
-    "libtwolame0",
-    "libudev-dev",
-    "libudev1",
-    "libudfread0",
-    "libunbound8",
-    "libunistring2",
     "libutempter-dev",
-    "libutempter0",
-    "libuuid1",
     "libva-dev",
-    "libva-drm2",
-    "libva-glx2",
-    "libva-wayland2",
-    "libva-x11-2",
-    "libva2",
-    "libvdpau1",
-    "libvorbis0a",
-    "libvorbisenc2",
-    "libvorbisfile3",
     "libvpx-dev",
-    "libvpx6",
-    "libvpx7",
-    "libvulkan-dev",
-    "libvulkan1",
-    "libwacom2",
-    "libwavpack1",
-    "libwayland-bin",
-    "libwayland-client0",
-    "libwayland-cursor0",
-    "libwayland-dev",
     "libwayland-egl-backend-dev",
-    "libwayland-egl1",
-    "libwayland-egl1-mesa",
-    "libwayland-server0",
     "libwebp-dev",
-    "libwebp6",
-    "libwebp7",
-    "libwebpdemux2",
-    "libwebpmux3",
-    "libwrap0",
-    "libx11-6",
-    "libx11-dev",
     "libx11-xcb-dev",
-    "libx11-xcb1",
-    "libx264-160",
-    "libx264-164",
-    "libx265-192",
-    "libx265-199",
-    "libxau-dev",
-    "libxau6",
-    "libxcb-dri2-0",
     "libxcb-dri2-0-dev",
-    "libxcb-dri3-0",
     "libxcb-dri3-dev",
-    "libxcb-glx0",
     "libxcb-glx0-dev",
-    "libxcb-icccm4",
-    "libxcb-image0",
     "libxcb-image0-dev",
-    "libxcb-keysyms1",
     "libxcb-present-dev",
-    "libxcb-present0",
-    "libxcb-randr0",
-    "libxcb-randr0-dev",
-    "libxcb-render-util0",
     "libxcb-render-util0-dev",
-    "libxcb-render0",
-    "libxcb-render0-dev",
-    "libxcb-shape0",
-    "libxcb-shape0-dev",
-    "libxcb-shm0",
-    "libxcb-shm0-dev",
-    "libxcb-sync-dev",
-    "libxcb-sync1",
     "libxcb-util-dev",
-    "libxcb-util1",
-    "libxcb-xfixes0",
-    "libxcb-xfixes0-dev",
-    "libxcb-xinerama0",
-    "libxcb-xinput0",
-    "libxcb-xkb1",
-    "libxcb1",
-    "libxcb1-dev",
-    "libxcomposite-dev",
-    "libxcomposite1",
-    "libxcursor-dev",
-    "libxcursor1",
-    "libxdamage-dev",
-    "libxdamage1",
-    "libxdmcp-dev",
-    "libxdmcp6",
-    "libxext-dev",
-    "libxext6",
-    "libxfixes-dev",
-    "libxfixes3",
-    "libxft-dev",
-    "libxft2",
-    "libxi-dev",
-    "libxi6",
-    "libxinerama-dev",
-    "libxinerama1",
-    "libxkbcommon-dev",
-    "libxkbcommon-x11-0",
-    "libxkbcommon0",
-    "libxml2",
-    "libxml2-dev",
-    "libxrandr-dev",
-    "libxrandr2",
-    "libxrender-dev",
-    "libxrender1",
     "libxshmfence-dev",
-    "libxshmfence1",
     "libxslt1-dev",
-    "libxslt1.1",
     "libxss-dev",
-    "libxss1",
     "libxt-dev",
-    "libxt6",
-    "libxtst-dev",
-    "libxtst6",
-    "libxvidcore4",
     "libxxf86vm-dev",
-    "libxxf86vm1",
-    "libzmq5",
-    "libzstd1",
-    "libzvbi0",
-    "linux-libc-dev",
     "mesa-common-dev",
-    "ocl-icd-libopencl1",
     "qt6-base-dev",
-    "qt6-base-dev-tools",
     "qtbase5-dev",
-    "qtbase5-dev-tools",
-    "shared-mime-info",
-    "uuid-dev",
-    "wayland-protocols",
-    "x11proto-dev",
-    "zlib1g",
-    "zlib1g-dev",
+    "valgrind",
 ]
 
-DEBIAN_PACKAGES_ARCH = {
-    "amd64": [
-        "libasan6",
-        "libdrm-intel1",
-        "libitm1",
-        "liblsan0",
-        "libmfx1",
-        "libquadmath0",
-        "libtsan0",
-        "libubsan1",
-        "valgrind",
-    ],
-    "i386": [
-        "libasan6",
-        "libdrm-intel1",
-        "libitm1",
-        "libquadmath0",
-        "libubsan1",
-        "valgrind",
-    ],
-    "armhf": [
-        "libasan6",
-        "libdrm-etnaviv1",
-        "libdrm-exynos1",
-        "libdrm-freedreno1",
-        "libdrm-omap1",
-        "libdrm-tegra0",
-        "libubsan1",
-        "valgrind",
-    ],
-    "arm64": [
-        "libasan6",
-        "libdrm-etnaviv1",
-        "libdrm-freedreno1",
-        "libdrm-tegra0",
-        "libgmp10",
-        "libitm1",
-        "liblsan0",
-        "libthai0",
-        "libtsan0",
-        "libubsan1",
-        "valgrind",
-    ],
-    "mipsel": [],
-    "mips64el": [
-        "valgrind",
-    ],
-}
-
 
 def banner(message: str) -> None:
     print("#" * 70)
@@ -775,7 +321,7 @@
 
     # Read the input file and create a dictionary mapping package names to URLs
     # and checksums.
-    missing = set(DEBIAN_PACKAGES + DEBIAN_PACKAGES_ARCH[arch])
+    missing = set(DEBIAN_PACKAGES)
     package_dict: dict[str, str] = {}
     for meta in package_meta.values():
         package = meta["Package"]
@@ -796,6 +342,11 @@
 def hacks_and_patches(install_root: str, script_dir: str, arch: str) -> None:
     banner("Misc Hacks & Patches")
 
+    debian_dir = os.path.join(install_root, "debian")
+    control_file = os.path.join(debian_dir, "control")
+    # Create an empty control file
+    open(control_file, "a").close()
+
     # Remove an unnecessary dependency on qtchooser.
     qtchooser_conf = os.path.join(install_root, "usr", "lib", TRIPLES[arch],
                                   "qt-default/qtchooser/default.conf")
@@ -852,10 +403,6 @@
 
     debian_dir = os.path.join(install_root, "debian")
     os.makedirs(debian_dir, exist_ok=True)
-    control_file = os.path.join(debian_dir, "control")
-    # Create an empty control file
-    open(control_file, "a").close()
-
     for package, sha256sum in packages.items():
         package_name = os.path.basename(package)
         package_path = os.path.join(debian_packages_dir, package_name)
@@ -953,6 +500,42 @@
                     os.symlink(relative_path, full_path)
 
 
+def removing_unnecessary_files(install_root, arch):
+    """
+    Minimizes the sysroot by removing unnecessary files.
+    """
+    # Preserve these files.
+    ALLOWLIST = {
+        "usr/bin/cups-config",
+        f"usr/lib/gcc/{TRIPLES[arch]}/10/libgcc.a",
+        f"usr/lib/{TRIPLES[arch]}/libc_nonshared.a",
+        f"usr/lib/{TRIPLES[arch]}/libffi_pic.a",
+    }
+
+    # Remove all executables and static libraries, and any symlinks that
+    # were pointing to them.
+    reverse_links = collections.defaultdict(list)
+    remove = []
+    for root, _, files in os.walk(install_root):
+        for filename in files:
+            filepath = os.path.join(root, filename)
+            if os.path.relpath(filepath, install_root) in ALLOWLIST:
+                continue
+            if os.path.islink(filepath):
+                target_path = os.readlink(filepath)
+                if not os.path.isabs(target_path):
+                    target_path = os.path.join(root, target_path)
+                reverse_links[os.path.realpath(target_path)].append(filepath)
+            elif "so" in filepath.split(".")[-3:]:
+                continue
+            elif os.access(filepath, os.X_OK) or filepath.endswith(".a"):
+                remove.append(filepath)
+    for filepath in remove:
+        os.remove(filepath)
+        for link in reverse_links[filepath]:
+            os.remove(link)
+
+
 def strip_sections(install_root: str):
     """
     Strips all sections from ELF files except for dynamic linking and
@@ -1008,14 +591,63 @@
                 subprocess.run(objcopy_cmd, check=True, stderr=subprocess.PIPE)
 
 
+def record_metadata(install_root: str) -> dict[str, tuple[float, float]]:
+    """
+    Recursively walk the install_root directory and record the metadata of all
+    files. Symlinks are not followed. Returns a dictionary mapping each path
+    (relative to install_root) to its original metadata.
+    """
+    metadata = {}
+    for root, dirs, files in os.walk(install_root):
+        for name in dirs + files:
+            full_path = os.path.join(root, name)
+            rel_path = os.path.relpath(full_path, install_root)
+            st = os.lstat(full_path)
+            metadata[rel_path] = (st.st_atime, st.st_mtime)
+    return metadata
+
+
+def restore_metadata(install_root: str,
+                     old_meta: dict[str, tuple[float, float]]) -> None:
+    """
+    1. Restore the metadata of any file that exists in old_meta.
+    2. For all other files, set their timestamp to ARCHIVE_TIMESTAMP.
+    3. For all directories (including install_root), set the timestamp
+       to ARCHIVE_TIMESTAMP.
+    """
+    # Convert the timestamp to a UNIX epoch time.
+    archive_time = time.mktime(
+        time.strptime(ARCHIVE_TIMESTAMP, "%Y%m%dT%H%M%SZ"))
+
+    # Walk through the install_root, applying old_meta where available;
+    # otherwise set times to archive_time.
+    for root, dirs, files in os.walk(install_root):
+        # Directories get archive_time.
+        os.utime(root, (archive_time, archive_time))
+
+        # Files: old_meta if available, else archive_time.
+        for file_name in files:
+            file_path = os.path.join(root, file_name)
+            if os.path.lexists(file_path):
+                rel_path = os.path.relpath(file_path, install_root)
+                if rel_path in old_meta:
+                    restore_time = old_meta[rel_path]
+                else:
+                    restore_time = (archive_time, archive_time)
+                os.utime(file_path, restore_time, follow_symlinks=False)
+
+
 def build_sysroot(arch: str) -> None:
     install_root = os.path.join(BUILD_DIR, f"{RELEASE}_{arch}_staging")
     clear_install_dir(install_root)
     packages = generate_package_list(arch)
     install_into_sysroot(BUILD_DIR, install_root, packages)
+    old_metadata = record_metadata(install_root)
     hacks_and_patches(install_root, SCRIPT_DIR, arch)
     cleanup_jail_symlinks(install_root)
+    removing_unnecessary_files(install_root, arch)
     strip_sections(install_root)
+    restore_metadata(install_root, old_metadata)
     create_tarball(install_root, arch)
 
 
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 8a08fed6..c276f8e 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -518,6 +518,8 @@
     "test/animation_test_common.h",
     "test/animation_timelines_test_common.cc",
     "test/animation_timelines_test_common.h",
+    "test/fake_compositor_delegate_for_input.cc",
+    "test/fake_compositor_delegate_for_input.h",
     "test/fake_compositor_frame_reporting_controller.cc",
     "test/fake_compositor_frame_reporting_controller.h",
     "test/fake_content_layer_client.cc",
@@ -589,6 +591,8 @@
     "test/layer_tree_test.h",
     "test/lottie_test_data.cc",
     "test/lottie_test_data.h",
+    "test/mock_input_handler.cc",
+    "test/mock_input_handler.h",
     "test/mock_latency_info_swap_promise_monitor.cc",
     "test/mock_latency_info_swap_promise_monitor.h",
     "test/mock_layer_tree_mutator.cc",
diff --git a/cc/layers/heads_up_display_layer_impl.cc b/cc/layers/heads_up_display_layer_impl.cc
index 9488375..7677872 100644
--- a/cc/layers/heads_up_display_layer_impl.cc
+++ b/cc/layers/heads_up_display_layer_impl.cc
@@ -42,7 +42,6 @@
 #include "components/viz/common/gpu/raster_context_provider.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/quads/texture_draw_quad.h"
-#include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/resources/platform_color.h"
 #include "components/viz/common/resources/shared_image_format.h"
 #include "gpu/GLES2/gl2extchromium.h"
@@ -174,14 +173,8 @@
 class HudSoftwareBacking : public ResourcePool::SoftwareBacking {
  public:
   ~HudSoftwareBacking() override {
-    if (shared_image) {
-      auto sii = layer_tree_frame_sink->shared_image_interface();
-      if (sii) {
-        sii->DestroySharedImage(mailbox_sync_token, std::move(shared_image));
-      }
-    } else {
-      layer_tree_frame_sink->DidDeleteSharedBitmap(shared_bitmap_id);
-    }
+    DCHECK(shared_image);
+    shared_image->UpdateDestructionSyncToken(mailbox_sync_token);
   }
 
   void OnMemoryDump(
@@ -338,39 +331,20 @@
     DCHECK_EQ(draw_mode, DRAW_MODE_SOFTWARE);
 
     auto sii = layer_tree_frame_sink->shared_image_interface();
-    if (sii) {
-      pool_resource = pool_->AcquireResource(internal_content_bounds_,
-                                             viz::SinglePlaneFormat::kBGRA_8888,
-                                             gfx::ColorSpace());
+    DCHECK(sii);
+    pool_resource = pool_->AcquireResource(internal_content_bounds_,
+                                           viz::SinglePlaneFormat::kBGRA_8888,
+                                           gfx::ColorSpace());
 
-      if (!pool_resource.software_backing()) {
-        auto backing = std::make_unique<HudSoftwareBacking>();
-        backing->layer_tree_frame_sink = layer_tree_frame_sink;
-        backing->shared_image = sii->CreateSharedImageForSoftwareCompositor(
-            {pool_resource.format(), pool_resource.size(),
-             pool_resource.color_space(),
-             gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "HeadsUpDisplayLayer"});
-        CHECK(backing->shared_image);
-        pool_resource.set_software_backing(std::move(backing));
-      }
-
-    } else {
-      pool_resource = pool_->AcquireResource(internal_content_bounds_,
-                                             viz::SinglePlaneFormat::kRGBA_8888,
-                                             gfx::ColorSpace());
-      if (!pool_resource.software_backing()) {
-        auto backing = std::make_unique<HudSoftwareBacking>();
-        backing->layer_tree_frame_sink = layer_tree_frame_sink;
-        backing->shared_bitmap_id = viz::SharedBitmap::GenerateId();
-        base::MappedReadOnlyRegion shm =
-            viz::bitmap_allocation::AllocateSharedBitmap(
-                pool_resource.size(), pool_resource.format());
-        backing->shared_mapping = std::move(shm.mapping);
-
-        layer_tree_frame_sink->DidAllocateSharedBitmap(
-            std::move(shm.region), backing->shared_bitmap_id);
-        pool_resource.set_software_backing(std::move(backing));
-      }
+    if (!pool_resource.software_backing()) {
+      auto backing = std::make_unique<HudSoftwareBacking>();
+      backing->layer_tree_frame_sink = layer_tree_frame_sink;
+      backing->shared_image = sii->CreateSharedImageForSoftwareCompositor(
+          {pool_resource.format(), pool_resource.size(),
+           pool_resource.color_space(), gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY,
+           "HeadsUpDisplayLayer"});
+      CHECK(backing->shared_image);
+      pool_resource.set_software_backing(std::move(backing));
     }
   }
 
@@ -462,9 +436,7 @@
     DrawHudContents(&canvas);
 
     auto sii = layer_tree_frame_sink->shared_image_interface();
-    if (backing->shared_image && sii) {
-      backing->mailbox_sync_token = sii->GenVerifiedSyncToken();
-    }
+    backing->mailbox_sync_token = sii->GenVerifiedSyncToken();
   }
 
   // Exports the backing to the ResourceProvider, giving it a ResourceId that
diff --git a/cc/layers/texture_layer_unittest.cc b/cc/layers/texture_layer_unittest.cc
index c74b6413..3a08a91 100644
--- a/cc/layers/texture_layer_unittest.cc
+++ b/cc/layers/texture_layer_unittest.cc
@@ -1500,18 +1500,21 @@
       scoped_refptr<viz::RasterContextProvider> compositor_context_provider,
       scoped_refptr<viz::RasterContextProvider> worker_context_provider)
       override {
+    context_provider_sw_ = viz::TestContextProvider::CreateRaster();
+    gpu::SharedImageInterface* shared_image_interface_sw =
+        context_provider_sw_->SharedImageInterface();
+
     constexpr bool disable_display_vsync = false;
     bool synchronous_composite =
         !HasImplThread() &&
         !layer_tree_host()->GetSettings().single_thread_proxy_scheduler;
     auto sink = std::make_unique<TestLayerTreeFrameSink>(
-        nullptr, nullptr, /*shared_image_interface=*/nullptr,
+        nullptr, nullptr, shared_image_interface_sw,
         gpu_memory_buffer_manager(), renderer_settings, &debug_settings_,
         task_runner_provider(), synchronous_composite, disable_display_vsync,
         refresh_rate);
     frame_sink_ = sink.get();
     num_frame_sinks_created_++;
-    shared_image_interface_ = frame_sink_->GetSharedImageInterface();
     return sink;
   }
 
@@ -1523,7 +1526,7 @@
       nullptr;
   int num_frame_sinks_created_ = 0;
 
-  scoped_refptr<gpu::SharedImageInterface> shared_image_interface_;
+  scoped_refptr<viz::RasterContextProvider> context_provider_sw_;
 };
 
 class SoftwareTextureLayerSwitchTreesTest : public SoftwareTextureLayerTest {
@@ -1542,8 +1545,8 @@
         scoped_refptr<gpu::ClientSharedImage> shared_image_;
         gpu::SyncToken sync_token_;
         gfx::Size size(1, 1);
-        AllocateSharedImage(shared_image_interface_.get(), size, shared_image_,
-                            sync_token_);
+        AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size,
+                            shared_image_, sync_token_);
         auto transferable_resource =
             viz::TransferableResource::MakeSoftwareSharedImage(
                 shared_image_, sync_token_, shared_image_->size(),
@@ -1612,8 +1615,8 @@
         scoped_refptr<gpu::ClientSharedImage> shared_image_;
         gpu::SyncToken sync_token_;
         gfx::Size size(1, 1);
-        AllocateSharedImage(shared_image_interface_.get(), size, shared_image_,
-                            sync_token_);
+        AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size,
+                            shared_image_, sync_token_);
         auto transferable_resource =
             viz::TransferableResource::MakeSoftwareSharedImage(
                 shared_image_, sync_token_, shared_image_->size(),
@@ -1688,10 +1691,10 @@
         scoped_refptr<gpu::ClientSharedImage> shared_image2_;
         gpu::SyncToken sync_token2_;
         gfx::Size size(1, 1);
-        AllocateSharedImage(shared_image_interface_.get(), size, shared_image1_,
-                            sync_token1_);
-        AllocateSharedImage(shared_image_interface_.get(), size, shared_image2_,
-                            sync_token2_);
+        AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size,
+                            shared_image1_, sync_token1_);
+        AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size,
+                            shared_image2_, sync_token2_);
         auto transferable_resource1 =
             viz::TransferableResource::MakeSoftwareSharedImage(
                 shared_image1_, sync_token1_, shared_image1_->size(),
@@ -1772,8 +1775,8 @@
         scoped_refptr<gpu::ClientSharedImage> shared_image_;
         gpu::SyncToken sync_token_;
         gfx::Size size(1, 1);
-        AllocateSharedImage(shared_image_interface_.get(), size, shared_image_,
-                            sync_token_);
+        AllocateSharedImage(frame_sink_->GetSharedImageInterface(), size,
+                            shared_image_, sync_token_);
         auto transferable_resource =
             viz::TransferableResource::MakeSoftwareSharedImage(
                 shared_image_, sync_token_, shared_image_->size(),
diff --git a/cc/metrics/compositor_frame_reporter.cc b/cc/metrics/compositor_frame_reporter.cc
index eb42b36f..46afa59 100644
--- a/cc/metrics/compositor_frame_reporter.cc
+++ b/cc/metrics/compositor_frame_reporter.cc
@@ -2,11 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
-#pragma allow_unsafe_buffers
-#endif
-
 #include "cc/metrics/compositor_frame_reporter.h"
 
 #include <algorithm>
diff --git a/cc/metrics/compositor_frame_reporter.h b/cc/metrics/compositor_frame_reporter.h
index be20c1d7..c4bd318 100644
--- a/cc/metrics/compositor_frame_reporter.h
+++ b/cc/metrics/compositor_frame_reporter.h
@@ -5,6 +5,7 @@
 #ifndef CC_METRICS_COMPOSITOR_FRAME_REPORTER_H_
 #define CC_METRICS_COMPOSITOR_FRAME_REPORTER_H_
 
+#include <array>
 #include <bitset>
 #include <deque>
 #include <memory>
@@ -231,7 +232,9 @@
     Iterator CreateIterator() const;
 
    private:
-    base::TimeDelta list_[static_cast<int>(BlinkBreakdown::kBreakdownCount)];
+    std::array<base::TimeDelta,
+               static_cast<size_t>(BlinkBreakdown::kBreakdownCount)>
+        list_;
   };
 
   // Holds a processed list of Viz breakdowns with an `Iterator` class to easily
@@ -279,8 +282,9 @@
     base::TimeTicks swap_start() const { return swap_start_; }
 
    private:
-    std::optional<std::pair<base::TimeTicks, base::TimeTicks>>
-        list_[static_cast<int>(VizBreakdown::kBreakdownCount)];
+    std::array<std::optional<std::pair<base::TimeTicks, base::TimeTicks>>,
+               static_cast<size_t>(VizBreakdown::kBreakdownCount)>
+        list_;
 
     bool buffer_ready_available_ = false;
     base::TimeTicks swap_start_;
diff --git a/cc/raster/bitmap_raster_buffer_provider.cc b/cc/raster/bitmap_raster_buffer_provider.cc
index 6a2f0d6..6d695ef 100644
--- a/cc/raster/bitmap_raster_buffer_provider.cc
+++ b/cc/raster/bitmap_raster_buffer_provider.cc
@@ -15,7 +15,6 @@
 #include "base/trace_event/traced_value.h"
 #include "cc/trees/layer_tree_frame_sink.h"
 #include "components/viz/client/client_resource_provider.h"
-#include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/resources/shared_image_format.h"
 #include "gpu/command_buffer/client/client_shared_image.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
@@ -28,14 +27,8 @@
 class BitmapSoftwareBacking : public ResourcePool::SoftwareBacking {
  public:
   ~BitmapSoftwareBacking() override {
-    if (shared_image) {
-      auto sii = frame_sink->shared_image_interface();
-      if (sii) {
-        sii->DestroySharedImage(mailbox_sync_token, std::move(shared_image));
-      }
-    } else {
-      frame_sink->DidDeleteSharedBitmap(shared_bitmap_id);
-    }
+    DCHECK(shared_image);
+    shared_image->UpdateDestructionSyncToken(mailbox_sync_token);
   }
 
   void OnMemoryDump(
@@ -111,7 +104,10 @@
 
 BitmapRasterBufferProvider::BitmapRasterBufferProvider(
     LayerTreeFrameSink* frame_sink)
-    : frame_sink_(frame_sink) {}
+    : frame_sink_(frame_sink) {
+  auto sii = frame_sink_->shared_image_interface();
+  CHECK(sii) << "BitmapRasterBufferProvider() SharedImageInterface is null!";
+}
 
 BitmapRasterBufferProvider::~BitmapRasterBufferProvider() = default;
 
@@ -133,24 +129,16 @@
     auto backing = std::make_unique<BitmapSoftwareBacking>();
     backing->frame_sink = frame_sink_;
     auto sii = frame_sink_->shared_image_interface();
-    if (sii) {
-      auto shared_image_mapping = sii->CreateSharedImage(
-          {viz::SinglePlaneFormat::kBGRA_8888, size, color_space,
-           gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY,
-           "BitmapRasterBufferProvider"});
-      backing->shared_image = std::move(shared_image_mapping.shared_image);
-      CHECK(backing->shared_image);
-      backing->mapping = std::move(shared_image_mapping.mapping);
-      backing->mailbox_sync_token = sii->GenVerifiedSyncToken();
-    } else {
-      backing->shared_bitmap_id = viz::SharedBitmap::GenerateId();
-      base::MappedReadOnlyRegion shm =
-          viz::bitmap_allocation::AllocateSharedBitmap(
-              size, viz::SinglePlaneFormat::kRGBA_8888);
-      backing->mapping = std::move(shm.mapping);
-      frame_sink_->DidAllocateSharedBitmap(std::move(shm.region),
-                                           backing->shared_bitmap_id);
-    }
+    CHECK(sii) << "SharedImageInterface is null!";
+
+    auto shared_image_mapping = sii->CreateSharedImage(
+        {viz::SinglePlaneFormat::kBGRA_8888, size, color_space,
+         gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "BitmapRasterBufferProvider"});
+    backing->shared_image = std::move(shared_image_mapping.shared_image);
+    CHECK(backing->shared_image);
+
+    backing->mapping = std::move(shared_image_mapping.mapping);
+    backing->mailbox_sync_token = sii->GenVerifiedSyncToken();
 
     resource.set_software_backing(std::move(backing));
   }
diff --git a/cc/test/fake_compositor_delegate_for_input.cc b/cc/test/fake_compositor_delegate_for_input.cc
new file mode 100644
index 0000000..1ae6943
--- /dev/null
+++ b/cc/test/fake_compositor_delegate_for_input.cc
@@ -0,0 +1,56 @@
+// 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 "cc/test/fake_compositor_delegate_for_input.h"
+
+namespace cc {
+
+FakeCompositorDelegateForInput::FakeCompositorDelegateForInput()
+    : host_impl_(&task_runner_provider_, &task_graph_runner_) {}
+
+FakeCompositorDelegateForInput::~FakeCompositorDelegateForInput() = default;
+
+ScrollTree& FakeCompositorDelegateForInput::GetScrollTree() const {
+  return scroll_tree_;
+}
+
+bool FakeCompositorDelegateForInput::HasAnimatedScrollbars() const {
+  return false;
+}
+
+bool FakeCompositorDelegateForInput::IsInHighLatencyMode() const {
+  return false;
+}
+
+float FakeCompositorDelegateForInput::DeviceScaleFactor() const {
+  return 0;
+}
+
+float FakeCompositorDelegateForInput::PageScaleFactor() const {
+  return 0;
+}
+
+gfx::Size FakeCompositorDelegateForInput::VisualDeviceViewportSize() const {
+  return gfx::Size();
+}
+
+const LayerTreeSettings& FakeCompositorDelegateForInput::GetSettings() const {
+  return settings_;
+}
+
+LayerTreeHostImpl& FakeCompositorDelegateForInput::GetImplDeprecated() {
+  return host_impl_;
+}
+
+const LayerTreeHostImpl& FakeCompositorDelegateForInput::GetImplDeprecated()
+    const {
+  return host_impl_;
+}
+
+bool FakeCompositorDelegateForInput::HasScrollLinkedAnimation(
+    ElementId for_scroller) const {
+  return false;
+}
+
+}  // namespace cc
diff --git a/cc/test/fake_compositor_delegate_for_input.h b/cc/test/fake_compositor_delegate_for_input.h
new file mode 100644
index 0000000..d76ac74
--- /dev/null
+++ b/cc/test/fake_compositor_delegate_for_input.h
@@ -0,0 +1,70 @@
+// 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 CC_TEST_FAKE_COMPOSITOR_DELEGATE_FOR_INPUT_H_
+#define CC_TEST_FAKE_COMPOSITOR_DELEGATE_FOR_INPUT_H_
+
+#include <memory>
+
+#include "base/types/optional_ref.h"
+#include "cc/input/browser_controls_offset_tags_info.h"
+#include "cc/input/browser_controls_state.h"
+#include "cc/input/compositor_input_interfaces.h"
+#include "cc/paint/element_id.h"
+#include "cc/test/fake_impl_task_runner_provider.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/test_task_graph_runner.h"
+#include "cc/trees/layer_tree_settings.h"
+#include "cc/trees/property_tree.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace cc {
+
+class FakeCompositorDelegateForInput : public CompositorDelegateForInput {
+ public:
+  FakeCompositorDelegateForInput();
+  ~FakeCompositorDelegateForInput() override;
+  void BindToInputHandler(
+      std::unique_ptr<InputDelegateForCompositor> delegate) override {}
+  ScrollTree& GetScrollTree() const override;
+  bool HasAnimatedScrollbars() const override;
+  void SetNeedsCommit() override {}
+  void SetNeedsFullViewportRedraw() override {}
+  void SetDeferBeginMainFrame(bool defer_begin_main_frame) const override {}
+  void DidUpdateScrollAnimationCurve() override {}
+  void AccumulateScrollDeltaForTracing(const gfx::Vector2dF& delta) override {}
+  void DidStartPinchZoom() override {}
+  void DidUpdatePinchZoom() override {}
+  void DidEndPinchZoom() override {}
+  void DidStartScroll() override {}
+  void DidEndScroll() override {}
+  void DidMouseLeave() override {}
+  bool IsInHighLatencyMode() const override;
+  void WillScrollContent(ElementId element_id) override {}
+  void DidScrollContent(ElementId element_id, bool animated) override {}
+  float DeviceScaleFactor() const override;
+  float PageScaleFactor() const override;
+  gfx::Size VisualDeviceViewportSize() const override;
+  const LayerTreeSettings& GetSettings() const override;
+  LayerTreeHostImpl& GetImplDeprecated() override;
+  const LayerTreeHostImpl& GetImplDeprecated() const override;
+  void UpdateBrowserControlsState(
+      BrowserControlsState constraints,
+      BrowserControlsState current,
+      bool animate,
+      base::optional_ref<const BrowserControlsOffsetTagsInfo> offset_tags_info)
+      override {}
+  bool HasScrollLinkedAnimation(ElementId for_scroller) const override;
+
+ private:
+  mutable ScrollTree scroll_tree_;
+  LayerTreeSettings settings_;
+  FakeImplTaskRunnerProvider task_runner_provider_;
+  TestTaskGraphRunner task_graph_runner_;
+  FakeLayerTreeHostImpl host_impl_;
+};
+
+}  // namespace cc
+
+#endif  // CC_TEST_FAKE_COMPOSITOR_DELEGATE_FOR_INPUT_H_
diff --git a/cc/test/layer_tree_pixel_test.cc b/cc/test/layer_tree_pixel_test.cc
index 7cf5e20..9bd09fd 100644
--- a/cc/test/layer_tree_pixel_test.cc
+++ b/cc/test/layer_tree_pixel_test.cc
@@ -165,6 +165,13 @@
 
 void LayerTreePixelTest::InitializeSettings(LayerTreeSettings* settings) {
   LayerTreeTest::InitializeSettings(settings);
+
+  if (settings->UseLayerContextForDisplay()) {
+    SkipTest();
+    GTEST_SKIP() << "TODO(crbug.com/389148369) TreesInViz: Implement copy "
+                    "output requests";
+  }
+
   settings->gpu_rasterization_disabled = !use_accelerated_raster();
   settings->use_zero_copy = raster_type() == TestRasterType::kZeroCopy;
 }
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 1fe4e00f..7b80889 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -1184,6 +1184,17 @@
   }
   InitializeSettings(&settings_);
 
+  // Tests may be skipped at this point for unsupported setting combinations.
+  if (skip_test_) {
+    return;
+  }
+
+  if (mode_ == CompositorMode::SINGLE_THREADED &&
+      settings_.UseLayerContextForDisplay()) {
+    GTEST_SKIP() << "TODO(crbug.com/389147356) TreesInViz: Implement single "
+                    "threaded mode";
+  }
+
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       base::BindOnce(&LayerTreeTest::DoBeginTest, base::Unretained(this)));
diff --git a/cc/test/layer_tree_test.h b/cc/test/layer_tree_test.h
index 039ff3f..52611b7 100644
--- a/cc/test/layer_tree_test.h
+++ b/cc/test/layer_tree_test.h
@@ -140,6 +140,8 @@
 
   void RealEndTest();
 
+  void SkipTest() { skip_test_ = true; }
+
   std::unique_ptr<LayerTreeFrameSink>
   ReleaseLayerTreeFrameSinkOnLayerTreeHost();
   void SetVisibleOnLayerTreeHost(bool visible);
@@ -284,6 +286,8 @@
   mutable base::Lock test_ended_lock_;
   bool ended_ = false;
 
+  bool skip_test_ = false;
+
   int timeout_seconds_ = 0;
 
   raw_ptr<viz::BeginFrameSource> begin_frame_source_ = nullptr;  // NOT OWNED.
diff --git a/cc/test/mock_input_handler.cc b/cc/test/mock_input_handler.cc
new file mode 100644
index 0000000..46874c7
--- /dev/null
+++ b/cc/test/mock_input_handler.cc
@@ -0,0 +1,23 @@
+// 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 "cc/test/mock_input_handler.h"
+
+#include "base/lazy_instance.h"
+
+namespace cc {
+
+namespace {
+
+base::LazyInstance<FakeCompositorDelegateForInput>::Leaky
+    g_fake_compositor_delegate = LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+MockInputHandler::MockInputHandler()
+    : InputHandler(g_fake_compositor_delegate.Get()) {}
+
+MockInputHandler::~MockInputHandler() = default;
+
+}  // namespace cc
diff --git a/cc/test/mock_input_handler.h b/cc/test/mock_input_handler.h
new file mode 100644
index 0000000..4459c095
--- /dev/null
+++ b/cc/test/mock_input_handler.h
@@ -0,0 +1,144 @@
+// 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 CC_TEST_MOCK_INPUT_HANDLER_H_
+#define CC_TEST_MOCK_INPUT_HANDLER_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "cc/input/browser_controls_offset_tags_info.h"
+#include "cc/input/browser_controls_state.h"
+#include "cc/input/event_listener_properties.h"
+#include "cc/input/input_handler.h"
+#include "cc/input/scroll_elasticity_helper.h"
+#include "cc/input/scroll_state.h"
+#include "cc/metrics/events_metrics_manager.h"
+#include "cc/paint/element_id.h"
+#include "cc/test/fake_compositor_delegate_for_input.h"
+#include "cc/trees/latency_info_swap_promise_monitor.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/events/types/scroll_input_type.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace cc {
+
+class MockInputHandler : public InputHandler {
+ public:
+  MockInputHandler();
+  MockInputHandler(const MockInputHandler&) = delete;
+  MockInputHandler& operator=(const MockInputHandler&) = delete;
+
+  ~MockInputHandler() override;
+
+  base::WeakPtr<InputHandler> AsWeakPtr() override {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+  MOCK_METHOD2(PinchGestureBegin,
+               void(const gfx::Point& anchor, ui::ScrollInputType type));
+  MOCK_METHOD2(PinchGestureUpdate,
+               void(float magnify_delta, const gfx::Point& anchor));
+  MOCK_METHOD1(PinchGestureEnd, void(const gfx::Point& anchor));
+
+  MOCK_METHOD0(SetNeedsAnimateInput, void());
+
+  MOCK_METHOD2(ScrollBegin,
+               ScrollStatus(ScrollState*, ui::ScrollInputType type));
+  MOCK_METHOD2(RootScrollBegin,
+               ScrollStatus(ScrollState*, ui::ScrollInputType type));
+  MOCK_METHOD2(ScrollUpdate,
+               InputHandlerScrollResult(ScrollState, base::TimeDelta));
+  MOCK_METHOD1(ScrollEnd, void(bool));
+  MOCK_METHOD2(RecordScrollBegin,
+               void(ui::ScrollInputType type, ScrollBeginThreadState state));
+  MOCK_METHOD1(RecordScrollEnd, void(ui::ScrollInputType type));
+  MOCK_METHOD1(HitTest, PointerResultType(const gfx::PointF& mouse_position));
+  MOCK_METHOD2(MouseDown,
+               InputHandlerPointerResult(const gfx::PointF& mouse_position,
+                                         const bool shift_modifier));
+  MOCK_METHOD1(MouseUp,
+               InputHandlerPointerResult(const gfx::PointF& mouse_position));
+  MOCK_METHOD1(SetIsHandlingTouchSequence, void(bool));
+  void NotifyInputEvent() override {}
+
+  std::unique_ptr<LatencyInfoSwapPromiseMonitor>
+  CreateLatencyInfoSwapPromiseMonitor(ui::LatencyInfo* latency) override {
+    return nullptr;
+  }
+
+  std::unique_ptr<EventsMetricsManager::ScopedMonitor>
+  GetScopedEventMetricsMonitor(
+      EventsMetricsManager::ScopedMonitor::DoneCallback) override {
+    return nullptr;
+  }
+
+  ScrollElasticityHelper* CreateScrollElasticityHelper() override {
+    return nullptr;
+  }
+  void DestroyScrollElasticityHelper() override {}
+
+  bool GetScrollOffsetForLayer(ElementId element_id,
+                               gfx::PointF* offset) override {
+    return false;
+  }
+  bool ScrollLayerTo(ElementId element_id, const gfx::PointF& offset) override {
+    return false;
+  }
+
+  void BindToClient(InputHandlerClient* client) override {}
+
+  void MouseLeave() override {}
+
+  MOCK_METHOD1(FindFrameElementIdAtPoint, ElementId(const gfx::PointF&));
+
+  InputHandlerPointerResult MouseMoveAt(
+      const gfx::Point& mouse_position) override {
+    return InputHandlerPointerResult();
+  }
+
+  MOCK_CONST_METHOD1(GetEventListenerProperties,
+                     EventListenerProperties(EventListenerClass event_class));
+  MOCK_METHOD2(EventListenerTypeForTouchStartOrMoveAt,
+               InputHandler::TouchStartOrMoveEventListenerType(
+                   const gfx::Point& point,
+                   TouchAction* touch_action));
+  MOCK_CONST_METHOD1(HasBlockingWheelEventHandlerAt, bool(const gfx::Point&));
+
+  MOCK_METHOD0(RequestUpdateForSynchronousInputHandler, void());
+  MOCK_METHOD1(SetSynchronousInputHandlerRootScrollOffset,
+               void(const gfx::PointF& root_offset));
+
+  bool IsCurrentlyScrollingViewport() const override {
+    return is_scrolling_root_;
+  }
+  void set_is_scrolling_root(bool is) { is_scrolling_root_ = is; }
+
+  MOCK_METHOD4(GetSnapFlingInfoAndSetAnimatingSnapTarget,
+               bool(const gfx::Vector2dF& current_delta,
+                    const gfx::Vector2dF& natural_displacement,
+                    gfx::PointF* initial_offset,
+                    gfx::PointF* target_offset));
+  MOCK_METHOD1(ScrollEndForSnapFling, void(bool));
+
+  bool ScrollbarScrollIsActive() override { return false; }
+
+  void SetDeferBeginMainFrame(bool defer_begin_main_frame) const override {}
+
+  MOCK_METHOD4(UpdateBrowserControlsState,
+               void(BrowserControlsState constraints,
+                    BrowserControlsState current,
+                    bool animate,
+                    base::optional_ref<const BrowserControlsOffsetTagsInfo>
+                        offset_tags_info));
+
+ private:
+  bool is_scrolling_root_ = true;
+
+  base::WeakPtrFactory<MockInputHandler> weak_ptr_factory_{this};
+};
+
+}  // namespace cc
+#endif  // CC_TEST_MOCK_INPUT_HANDLER_H_
diff --git a/cc/test/test_client_shared_image_interface.cc b/cc/test/test_client_shared_image_interface.cc
index 57a5dd4..55a936b 100644
--- a/cc/test/test_client_shared_image_interface.cc
+++ b/cc/test/test_client_shared_image_interface.cc
@@ -14,14 +14,18 @@
                      gpu::GpuFeatureInfo(),
                      gpu::SharedImageCapabilities(),
                      mojo::ScopedMessagePipeHandle(
-                         mojo::MessagePipeHandle(mojo::kInvalidHandleValue))) {}
+                         mojo::MessagePipeHandle(mojo::kInvalidHandleValue))) {
+  // There is a "LeakSanitizer: detected memory leaks" on
+  // mojo::SharedRemoteBase<mojo::AssociatedRemote<gpu::mojom::GpuChannel>> in
+  // the multithread ASAN test when TestGpuChannelHost is created on the Main
+  // thread and released on the Compositor thread. Because |remote_| is not
+  // actually used in the tests, so it's reset here to avoid the memory leak at
+  // the end.
+  ResetChannelRemoteForTesting();
+}
 
 TestGpuChannelHost::~TestGpuChannelHost() = default;
 
-gpu::mojom::GpuChannel& TestGpuChannelHost::GetGpuChannel() {
-  return gpu_channel_;
-}
-
 TestClientSharedImageInterface::TestClientSharedImageInterface(
     scoped_refptr<gpu::SharedImageInterface> shared_image_interface)
     : gpu::ClientSharedImageInterface(
diff --git a/cc/test/test_client_shared_image_interface.h b/cc/test/test_client_shared_image_interface.h
index 364899f..e19d6ae 100644
--- a/cc/test/test_client_shared_image_interface.h
+++ b/cc/test/test_client_shared_image_interface.h
@@ -9,7 +9,6 @@
 #include "gpu/ipc/client/client_shared_image_interface.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
 #include "gpu/ipc/common/gpu_channel.mojom.h"
-#include "gpu/ipc/common/mock_gpu_channel.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 
 namespace cc {
@@ -18,11 +17,8 @@
  public:
   TestGpuChannelHost();
 
-  gpu::mojom::GpuChannel& GetGpuChannel() override;
-
  protected:
   ~TestGpuChannelHost() override;
-  gpu::MockGpuChannel gpu_channel_;
 };
 
 class TestClientSharedImageInterface : public gpu::ClientSharedImageInterface {
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 1075cca..f5aa65cb 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -5157,13 +5157,8 @@
       gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
   // For software compositing, shared memory will be allocated and the
   // UIResource will be copied into it.
-  base::MappedReadOnlyRegion shm;
   base::WritableSharedMemoryMapping shared_mapping;
-  viz::SharedBitmapId shared_bitmap_id;
   bool overlay_candidate = false;
-  // Use sharedImage for software composition;
-  bool use_shared_image_software =
-      !!layer_tree_frame_sink_->shared_image_interface();
 
   if (layer_tree_frame_sink_->context_provider()) {
     viz::RasterContextProvider* context_provider =
@@ -5177,7 +5172,8 @@
     if (overlay_candidate) {
       shared_image_usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
     }
-  } else if (use_shared_image_software) {
+  } else {
+    // software composition;
     DCHECK_EQ(bitmap.GetFormat(), UIResourceBitmap::RGBA8);
     // Must not include gpu::SHARED_IMAGE_USAGE_DISPLAY_READ here because
     // DISPLAY_READ means gpu composition.
@@ -5191,10 +5187,6 @@
     client_shared_image = std::move(shared_image_mapping.shared_image);
     shared_mapping = std::move(shared_image_mapping.mapping);
     CHECK(client_shared_image);
-  } else {
-    shm = viz::bitmap_allocation::AllocateSharedBitmap(upload_size, format);
-    shared_mapping = std::move(shm.mapping);
-    shared_bitmap_id = viz::SharedBitmap::GenerateId();
   }
 
   if (!scaled) {
@@ -5295,18 +5287,12 @@
     transferable = viz::TransferableResource::MakeGpu(
         client_shared_image, texture_target, sync_token, upload_size, format,
         overlay_candidate, viz::TransferableResource::ResourceSource::kUI);
-  } else if (use_shared_image_software) {
+  } else {
     auto sii = layer_tree_frame_sink_->shared_image_interface();
     gpu::SyncToken sync_token = sii->GenVerifiedSyncToken();
     transferable = viz::TransferableResource::MakeSoftwareSharedImage(
         client_shared_image, sync_token, upload_size, format,
         viz::TransferableResource::ResourceSource::kUI);
-  } else {
-    layer_tree_frame_sink_->DidAllocateSharedBitmap(std::move(shm.region),
-                                                    shared_bitmap_id);
-    transferable = viz::TransferableResource::MakeSoftwareSharedBitmap(
-        shared_bitmap_id, gpu::SyncToken(), upload_size, format,
-        viz::TransferableResource::ResourceSource::kUI);
   }
   transferable.color_space = color_space;
   id = resource_provider_->ImportResource(
@@ -5320,10 +5306,6 @@
 
   UIResourceData data;
   data.opaque = bitmap.GetOpaque();
-  if (!use_shared_image_software) {
-    data.shared_bitmap_id = shared_bitmap_id;
-    data.shared_mapping = std::move(shared_mapping);
-  }
   data.shared_image = std::move(client_shared_image);
   data.resource_id_for_export = id;
   ui_resource_map_[uid] = std::move(data);
@@ -5352,28 +5334,7 @@
 void LayerTreeHostImpl::DeleteUIResourceBacking(
     UIResourceData data,
     const gpu::SyncToken& sync_token) {
-  // Resources are either software or gpu backed, not both.
-  DCHECK(!(data.shared_mapping.IsValid() && data.shared_image));
-
-  if (data.shared_mapping.IsValid()) {
-    layer_tree_frame_sink_->DidDeleteSharedBitmap(data.shared_bitmap_id);
-  }
-
-  if (data.shared_image) {
-    if (layer_tree_frame_sink_->context_provider()) {
-      auto* sii =
-          layer_tree_frame_sink_->context_provider()->SharedImageInterface();
-      if (sii) {
-        sii->DestroySharedImage(sync_token, std::move(data.shared_image));
-      }
-    } else {
-      auto sii = layer_tree_frame_sink_->shared_image_interface();
-      if (sii) {
-        sii->DestroySharedImage(sync_token, std::move(data.shared_image));
-      }
-    }
-  }
-  // |data| goes out of scope and deletes anything it owned.
+  data.shared_image->UpdateDestructionSyncToken(sync_token);
 }
 
 void LayerTreeHostImpl::OnUIResourceReleased(UIResourceId uid,
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 21f806d..b6e9ee4 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -182,6 +182,12 @@
   }
 
   void SetUp() override {
+    LayerTreeSettings settings = DefaultSettings();
+    if (settings.UseLayerContextForDisplay()) {
+      GTEST_SKIP() << "TODO(crbug.com/389147356) TreesInViz: Implement single "
+                      "threaded mode";
+    }
+
     CreateHostImpl(DefaultSettings(), CreateLayerTreeFrameSink());
 
     // TODO(bokan): Mac wheel scrolls don't cause smooth scrolling in the real
@@ -935,7 +941,13 @@
 class LayerTreeHostImplTimelinesTest : public LayerTreeHostImplTest {
  public:
   void SetUp() override {
-    CreateHostImpl(DefaultSettings(), CreateLayerTreeFrameSink());
+    LayerTreeSettings settings = DefaultSettings();
+    if (settings.UseLayerContextForDisplay()) {
+      GTEST_SKIP() << "TODO(crbug.com/389147356) TreesInViz: Implement single "
+                      "threaded mode";
+    }
+
+    CreateHostImpl(settings, CreateLayerTreeFrameSink());
 
     // TODO(bokan): Mac wheel scrolls don't cause smooth scrolling in the real
     // world. In tests, we force it on for consistency. Can be removed when
@@ -957,6 +969,10 @@
     settings.scrollbar_fade_delay = base::Milliseconds(500);
     settings.scrollbar_fade_duration = base::Milliseconds(300);
     settings.idle_thickness_scale = 0.4f;
+    if (settings.UseLayerContextForDisplay()) {
+      GTEST_SKIP() << "TODO(crbug.com/389147356) TreesInViz: Implement single "
+                      "threaded mode";
+    }
     CreateHostImpl(settings, CreateLayerTreeFrameSink());
   }
 
@@ -11846,8 +11862,14 @@
 class LayerTreeHostImplTestPrepareTiles : public LayerTreeHostImplTest {
  public:
   void SetUp() override {
+    LayerTreeSettings settings = LayerTreeSettings();
+    if (settings.UseLayerContextForDisplay()) {
+      GTEST_SKIP() << "TODO(crbug.com/389147356) TreesInViz: Implement single "
+                      "threaded mode";
+    }
+
     fake_host_impl_ = new FakeLayerTreeHostImpl(
-        LayerTreeSettings(), &task_runner_provider_, &task_graph_runner_);
+        settings, &task_runner_provider_, &task_graph_runner_);
     host_impl_.reset(fake_host_impl_);
     layer_tree_frame_sink_ = CreateLayerTreeFrameSink();
     host_impl_->SetVisible(true);
@@ -12230,7 +12252,12 @@
 class LayerTreeHostImplWithBrowserControlsTest : public LayerTreeHostImplTest {
  public:
   void SetUp() override {
-    CreateHostImpl(DefaultSettings(), CreateLayerTreeFrameSink());
+    LayerTreeSettings settings = DefaultSettings();
+    if (settings.UseLayerContextForDisplay()) {
+      GTEST_SKIP() << "TODO(crbug.com/389147356) TreesInViz: Implement single "
+                      "threaded mode";
+    }
+    CreateHostImpl(settings, CreateLayerTreeFrameSink());
     host_impl_->active_tree()->SetBrowserControlsParams(
         {static_cast<float>(top_controls_height_), 0, 0, 0, false, false});
     host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1.f, 1.f);
@@ -17772,6 +17799,11 @@
   }
 
   void SetUp() override {
+    LayerTreeSettings settings = DefaultSettings();
+    if (settings.UseLayerContextForDisplay()) {
+      GTEST_SKIP() << "TODO(crbug.com/389147356) TreesInViz: Implement single "
+                      "threaded mode";
+    }
     LayerTreeHostImplTest::SetUp();
 
     cur_time_ = base::TimeTicks() + base::Milliseconds(100);
@@ -19124,6 +19156,11 @@
   void SetUp() override {
     scoped_feature_list_.InitAndEnableFeature(
         features::kMultipleImplOnlyScrollAnimations);
+    LayerTreeSettings settings = DefaultSettings();
+    if (settings.UseLayerContextForDisplay()) {
+      GTEST_SKIP() << "TODO(crbug.com/389147356) TreesInViz: Implement single "
+                      "threaded mode";
+    }
     LayerTreeHostImplTest::SetUp();
   }
   gfx::PointF CreateAndTickScrollAnimations();
@@ -19276,6 +19313,11 @@
   void SetUp() override {
     scoped_feature_list_.InitAndEnableFeature(
         features::kMultipleImplOnlyScrollAnimations);
+    LayerTreeSettings settings = DefaultSettings();
+    if (settings.UseLayerContextForDisplay()) {
+      GTEST_SKIP() << "TODO(crbug.com/389147356) TreesInViz: Implement single "
+                      "threaded mode";
+    }
     LayerTreeHostImplTest::SetUp();
     gfx::Size viewport_size(100, 100);
     gfx::Size content_size(100, 5000);
diff --git a/cc/trees/layer_tree_host_unittest_context.cc b/cc/trees/layer_tree_host_unittest_context.cc
index 0a3037a..87ddb63 100644
--- a/cc/trees/layer_tree_host_unittest_context.cc
+++ b/cc/trees/layer_tree_host_unittest_context.cc
@@ -1657,11 +1657,7 @@
   viz::ResourceId exported_resource_id_ = viz::kInvalidResourceId;
 };
 
-// TODO(crbug.com/388228379): Test is failing on ChromeOS ASan LSan builds.
-#if !BUILDFLAG(IS_CHROMEOS) || !defined(LEAK_SANITIZER) || \
-    !defined(ADDRESS_SANITIZER)
 SINGLE_AND_MULTI_THREAD_TEST_F(SoftwareTileResourceFreedIfLostWhileExported);
-#endif
 
 class LayerTreeHostContextTestLoseAfterSendingBeginMainFrame
     : public LayerTreeHostContextTest {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index 8da201b7..15b4dea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -67,6 +67,7 @@
 import org.chromium.chrome.browser.privacy_sandbox.ActivityTypeMapper;
 import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxBridge;
 import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxDialogController;
+import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxSurveyController;
 import org.chromium.chrome.browser.privacy_sandbox.SurfaceType;
 import org.chromium.chrome.browser.privacy_sandbox.TrackingProtectionSnackbarController;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -754,12 +755,20 @@
                                         "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT",
                                         shouldShowPrivacySandboxDialog);
                             }
+                            PrivacySandboxSurveyController surveyController =
+                                    PrivacySandboxSurveyController.initialize(
+                                            mTabModelSelectorSupplier.get(),
+                                            mActivityLifecycleDispatcher,
+                                            mActivity,
+                                            mMessageDispatcher,
+                                            mActivityTabProvider,
+                                            profile);
+                            String appId = mIntentDataProvider.get().getClientPackageName();
                             if (ChromeFeatureList.isEnabled(
                                             ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT)
                                     && shouldShowPrivacySandboxDialog
                                     && isCustomTab) {
                                 boolean shouldShowPrivacySandboxDialogAppIdCheck = true;
-                                String appId = mIntentDataProvider.get().getClientPackageName();
                                 String paramAdsNoticeAppId =
                                         ChromeFeatureList.getFieldTrialParamByFeature(
                                                 ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
@@ -780,7 +789,17 @@
                                                             SurfaceType.AGACCT,
                                                             mWindowAndroid);
                                 }
+                            } else if (surveyController != null
+                                    && !ChromeFeatureList.isEnabled(
+                                            ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT)
+                                    && shouldShowPrivacySandboxDialog
+                                    && isCustomTab) {
+                                surveyController.scheduleAdsCctControlSurveyLaunch(
+                                        appId,
+                                        new PrivacySandboxBridge(currentModelProfile)
+                                                .getRequiredPromptType(SurfaceType.AGACCT));
                             }
+
                             if (!didShowPrompt) {
                                 didShowPrompt =
                                         RequestDesktopUtils
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
index 6cd0866..c65281b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
@@ -367,7 +367,7 @@
 
                 // Reader Mode should not pollute the navigation stack. To avoid this, watch for
                 // navigations and prepare to remove any that are "chrome-distiller" urls.
-                NavigationController controller = mWebContents.get().getNavigationController();
+                NavigationController controller = getWebContents().getNavigationController();
                 int index = controller.getLastCommittedEntryIndex();
                 NavigationEntry entry = controller.getEntryAtIndex(index);
 
@@ -395,7 +395,7 @@
 
                 if (mShouldRemovePreviousNavigation) {
                     mShouldRemovePreviousNavigation = false;
-                    NavigationController controller = mWebContents.get().getNavigationController();
+                    NavigationController controller = getWebContents().getNavigationController();
                     if (controller.getEntryAtIndex(mLastDistillerPageIndex) != null) {
                         controller.removeEntryAtIndex(mLastDistillerPageIndex);
                     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyController.java b/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyController.java
index cd94b3d..ed9dc35b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyController.java
@@ -128,7 +128,10 @@
     private boolean mHasSeenNtp;
     private boolean mOverrideChannelForTesting;
     private int mChannelForTesting;
+    private static long sAdsCctDelayOverrideMilliseconds;
+    private static boolean sOverrideAdsCctDelay;
     private static boolean sEnableForTesting;
+    private static final long DEFAULT_ADS_CCT_DELAY_MS = 20_000L;
 
     PrivacySandboxSurveyController(
             TabModelSelector tabModelSelector,
@@ -195,6 +198,10 @@
         return true;
     }
 
+    private long getAdsCctDelayMilliseconds() {
+        return sOverrideAdsCctDelay ? sAdsCctDelayOverrideMilliseconds : DEFAULT_ADS_CCT_DELAY_MS;
+    }
+
     // Schedules the launch of an Ads CCT Treatment survey.
     // Should only be invoked after the closure of either the EEA or ROW notice.
     public void scheduleAdsCctTreatmentSurveyLaunch(String appId) {
@@ -204,7 +211,7 @@
         PostTask.postDelayedTask(
                 TaskTraits.UI_DEFAULT,
                 () -> maybeLaunchAdsCctTreatmentSurvey(),
-                /*20 seconds*/ 20000);
+                getAdsCctDelayMilliseconds());
     }
 
     // Determines the appropriate survey to launch based on the user interaction with either the EEA
@@ -240,7 +247,7 @@
         PostTask.postDelayedTask(
                 TaskTraits.UI_DEFAULT,
                 () -> maybeLaunchAdsCctControlSurvey(promptType),
-                /*20 seconds*/ 20000);
+                getAdsCctDelayMilliseconds());
     }
 
     // Determines the appropriate survey to launch based on the prompt type.
@@ -458,4 +465,11 @@
         mChannelForTesting = channel;
         ResettersForTesting.register(() -> mChannelForTesting = Channel.DEFAULT);
     }
+
+    // Overrides the survey delay
+    public static void overrideAdsCctSurveyDelayForTesting(long delayMilliseconds) {
+        sAdsCctDelayOverrideMilliseconds = delayMilliseconds;
+        sOverrideAdsCctDelay = true;
+        ResettersForTesting.register(() -> sOverrideAdsCctDelay = false);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
index 56ddbc5..5bdfcf95 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
@@ -889,6 +889,14 @@
     public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
         super_onTopResumedActivityChanged(isTopResumedActivity);
 
+        // For hub search use in split screen and multi window mode, search activity should be
+        // dismissed when focus is lost to prevent focus from causing the suggestion list to flicker
+        // on window toggling.
+        if (!isTopResumedActivity && mIntentOrigin == IntentOrigin.HUB) {
+            finish(TerminationReason.ACTIVITY_FOCUS_LOST, null);
+            return;
+        }
+
         // TODO(crbug.com/329702834): Ensure showing Suggestions when activity resumes.
         // This may only happen when user enters tab switcher, and immediately returns to the
         // SearchActivity.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index 84654b3bf..d7a6684 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -1324,11 +1324,6 @@
             }
 
             @Override
-            public void openLearnMoreSharedTabGroupsPage(Context context, GURL gurl) {
-                CustomTabActivity.showInfoPage(context, gurl.getSpec());
-            }
-
-            @Override
             public void openUrlInChromeCustomTab(Context context, GURL gurl) {
                 CustomTabActivity.showInfoPage(context, gurl.getSpec());
             }
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 657d329..d104833 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
@@ -288,7 +288,8 @@
     }
 
     @Test
-    public void testTabRemovalFlow_SingleCollaboration_WithDialog_NoCollborationData_UnknownRole() {
+    public void
+            testTabRemovalFlow_SingleCollaboration_WithDialog_NoCollaborationData_UnknownRole() {
         GroupsPendingDestroy groupsPendingDestroy = new GroupsPendingDestroy();
         groupsPendingDestroy.collaborationGroupsDestroyed.add(TAB_GROUP_1);
         when(mHandler.computeGroupsPendingDestroy()).thenReturn(groupsPendingDestroy);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerIntegrationTest.java
index 00b898a..9f4a4031 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerIntegrationTest.java
@@ -4,6 +4,15 @@
 
 package org.chromium.chrome.browser.privacy_sandbox;
 
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Assert;
@@ -12,16 +21,25 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.CommandLine;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Features;
+import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.RequiresRestart;
+import org.chromium.chrome.browser.browserservices.intents.SessionHolder;
+import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
+import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
+import org.chromium.chrome.browser.customtabs.CustomTabsIntentTestUtils;
+import org.chromium.chrome.browser.firstrun.FirstRunStatus;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.ui.hats.TestSurveyUtils;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.R;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.messages.DismissReason;
 import org.chromium.components.messages.MessageBannerProperties;
@@ -30,6 +48,7 @@
 import org.chromium.components.messages.MessageIdentifier;
 import org.chromium.components.messages.MessageStateHandler;
 import org.chromium.components.messages.MessagesTestHelper;
+import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.List;
@@ -42,16 +61,245 @@
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
 
     @Rule
+    public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
+
+    @Rule
+    public ChromeTabbedActivityTestRule mChromeTabbedActivityTestRule =
+            new ChromeTabbedActivityTestRule();
+
+    @Rule
     public TestSurveyUtils.TestSurveyComponentRule mTestSurveyComponentRule =
             new TestSurveyUtils.TestSurveyComponentRule();
 
     private MessageDispatcher mMessageDispatcher;
     private PropertyModel mSurveyMessage;
+    private String mTestPage;
+    private EmbeddedTestServer mTestServer;
+    private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
 
     @Before
     public void setup() {
         PrivacySandboxSurveyController.setEnableForTesting();
         mActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL);
+
+        // CCT setup.
+        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
+        Context appContext = getInstrumentation().getTargetContext().getApplicationContext();
+        mTestServer = EmbeddedTestServer.createAndStartServer(appContext);
+        mTestPage = mTestServer.getURL(TEST_PAGE);
+
+        // Explicitly remove the `DISABLE_FIRST_RUN_EXPERIENCE` (set via `TestSurveyComponentRule`)
+        // commandline switch which prevents us from receiving a valid prompt type via
+        // `PrivacySandboxBridge`.
+        CommandLine.getInstance().removeSwitch(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE);
+        PrivacySandboxSurveyController.overrideAdsCctSurveyDelayForTesting(
+                /* delayMilliseconds= */ 0);
+    }
+
+    private Intent createMinimalCustomTabIntent() {
+        Intent intent =
+                CustomTabsIntentTestUtils.createMinimalCustomTabIntent(
+                        ApplicationProvider.getApplicationContext(), mTestPage);
+        var token = SessionHolder.getSessionHolderFromIntent(intent);
+        // x86 devices will return a null package name unless we explicitly override it.
+        CustomTabsConnection connection = CustomTabsConnection.getInstance();
+        connection.newSession(token.getSessionAsCustomTab());
+        connection.overridePackageNameForSessionForTesting(token, "org.chromium.chrome.tests");
+        return intent;
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY
+                + ":app-id/org.chromium.chrome.tests/"
+                + "row-control-trigger-id/"
+                + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
+        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
+                + ":force-show-notice-row-for-testing/true/notice-required/true"
+    })
+    @DisableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
+    })
+    public void adsCctSurveyForRowControlAcceptSurvey() {
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
+        waitForSurveyMessageToShowOnCct();
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mSurveyMessage.get(MessageBannerProperties.ON_PRIMARY_ACTION).get();
+                });
+        Assert.assertEquals(
+                "Last shown survey triggerId not match.",
+                TestSurveyUtils.TEST_TRIGGER_ID_FOO,
+                mTestSurveyComponentRule.getLastShownTriggerId());
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY
+                + ":app-id/org.chromium.chrome.tests/"
+                + "row-control-trigger-id/"
+                + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
+        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
+                + ":force-show-notice-row-for-testing/true/notice-required/true"
+    })
+    @DisableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
+    })
+    public void adsCctSurveyForRowControlDismissSurvey() {
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
+        waitForSurveyMessageToShowOnCct();
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> mMessageDispatcher.dismissMessage(mSurveyMessage, DismissReason.GESTURE));
+        Assert.assertTrue(
+                "Survey displayed not recorded.",
+                mTestSurveyComponentRule.isPromptShownForTriggerId(
+                        TestSurveyUtils.TEST_TRIGGER_ID_FOO));
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY
+                + ":app-id/org.chromium.chrome.tests/",
+        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
+                + ":force-show-notice-row-for-testing/true/notice-required/true"
+    })
+    @DisableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
+    })
+    public void adsCctSurveyForRowControlNotShownWhenNoTriggerIdProvided() {
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        Assert.assertFalse(
+                "Survey was displayed.",
+                mTestSurveyComponentRule.isPromptShownForTriggerId(
+                        TestSurveyUtils.TEST_TRIGGER_ID_FOO));
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY
+                + ":app-id/org.chromium.chrome.tests/"
+                + "eea-control-trigger-id/"
+                + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
+        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
+                + ":force-show-consent-for-testing/true/consent-required/true"
+    })
+    @DisableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
+    })
+    public void adsCctSurveyForEeaControlAcceptSurvey() {
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
+        waitForSurveyMessageToShowOnCct();
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mSurveyMessage.get(MessageBannerProperties.ON_PRIMARY_ACTION).get();
+                });
+        Assert.assertEquals(
+                "Last shown survey triggerId not match.",
+                TestSurveyUtils.TEST_TRIGGER_ID_FOO,
+                mTestSurveyComponentRule.getLastShownTriggerId());
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY
+                + ":app-id/org.chromium.chrome.tests/"
+                + "eea-control-trigger-id/"
+                + TestSurveyUtils.TEST_TRIGGER_ID_FOO,
+        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
+                + ":force-show-consent-for-testing/true/consent-required/true"
+    })
+    @DisableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
+    })
+    public void adsCctSurveyForEeaControlDismissSurvey() {
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
+        waitForSurveyMessageToShowOnCct();
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> mMessageDispatcher.dismissMessage(mSurveyMessage, DismissReason.GESTURE));
+        Assert.assertTrue(
+                "Survey displayed not recorded.",
+                mTestSurveyComponentRule.isPromptShownForTriggerId(
+                        TestSurveyUtils.TEST_TRIGGER_ID_FOO));
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY
+                + ":app-id/org.chromium.chrome.tests/",
+        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
+                + ":force-show-consent-for-testing/true/consent-required/true"
+    })
+    @DisableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
+    })
+    public void adsCctSurveyForEeaControlNotShownWhenNoTriggerIdProvided() {
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        Assert.assertFalse(
+                "Survey was displayed.",
+                mTestSurveyComponentRule.isPromptShownForTriggerId(
+                        TestSurveyUtils.TEST_TRIGGER_ID_FOO));
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY
+                + ":app-id/org.chromium.chrome.tests/",
+        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
+                + ":force-show-consent-for-testing/true/consent-required/true"
+                + "/force-show-notice-row-for-testing/true/notice-required/true",
+        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT
+    })
+    public void adsCctSurveyForControlSurveyNotShownWhenAdsNoticeCctFeatureEnabled() {
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        Assert.assertFalse(
+                "Survey was displayed.",
+                mTestSurveyComponentRule.isPromptShownForTriggerId(
+                        TestSurveyUtils.TEST_TRIGGER_ID_FOO));
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY + ":app-id/invalid-app-id/",
+        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
+                + ":force-show-consent-for-testing/true/consent-required/true"
+                + "/force-show-notice-row-for-testing/true/notice-required/true",
+    })
+    @DisableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT})
+    public void adsCctSurveyForControlSurveyNotShownWithInvalidAppId() {
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        Assert.assertFalse(
+                "Survey was displayed.",
+                mTestSurveyComponentRule.isPromptShownForTriggerId(
+                        TestSurveyUtils.TEST_TRIGGER_ID_FOO));
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
+                + ":force-show-consent-for-testing/true/consent-required/true"
+                + "/force-show-notice-row-for-testing/true/notice-required/true",
+    })
+    @DisableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
+        ChromeFeatureList.PRIVACY_SANDBOX_CCT_ADS_NOTICE_SURVEY
+    })
+    public void adsCctSurveyForControlSurveyNotShownWithSurveyFeatureDisabled() {
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
+        Assert.assertEquals(
+                "Survey was displayed.", mTestSurveyComponentRule.getLastShownTriggerId(), null);
     }
 
     @Test
@@ -109,6 +357,22 @@
                         TestSurveyUtils.TEST_TRIGGER_ID_FOO));
     }
 
+    private void waitForSurveyMessageToShowOnCct() {
+        Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
+        CriteriaHelper.pollUiThread(() -> !tab.isLoading() && tab.isUserInteractable());
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mMessageDispatcher =
+                            MessageDispatcherProvider.from(
+                                    mCustomTabActivityTestRule.getActivity().getWindowAndroid());
+                });
+        CriteriaHelper.pollUiThread(
+                () -> {
+                    mSurveyMessage = getSurveyMessage();
+                    return mSurveyMessage != null;
+                });
+    }
+
     private void waitForSurveyMessageToShow() {
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
         CriteriaHelper.pollUiThread(() -> !tab.isLoading() && tab.isUserInteractable());
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUnitTest.java
index 50a65d99..2f3883a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/searchwidget/SearchActivityUnitTest.java
@@ -1100,6 +1100,7 @@
     @Test
     public void onTopResumedActivityChanged_clearOmniboxFocusIfNotActive() {
         doNothing().when(mActivity).super_onTopResumedActivityChanged(anyBoolean());
+        mActivity.handleNewIntent(buildTestServiceIntent(IntentOrigin.SEARCH_WIDGET), false);
         mActivity.onTopResumedActivityChanged(false);
         verify(mLocationBar).clearOmniboxFocus();
         verify(mActivity).super_onTopResumedActivityChanged(false);
@@ -1108,8 +1109,30 @@
     @Test
     public void onTopResumedActivityChanged_requestOmniboxFocusIfActive() {
         doNothing().when(mActivity).super_onTopResumedActivityChanged(anyBoolean());
+        mActivity.handleNewIntent(buildTestServiceIntent(IntentOrigin.SEARCH_WIDGET), false);
         mActivity.onTopResumedActivityChanged(true);
         verify(mLocationBar).requestOmniboxFocus();
         verify(mActivity).super_onTopResumedActivityChanged(true);
     }
+
+    @Test
+    public void onTopResumedActivityChanged_finishActivityFocusLostHubSearch() {
+        LocationBarCoordinator locationBarCoordinator = mock(LocationBarCoordinator.class);
+        StatusCoordinator statusCoordinator = mock(StatusCoordinator.class);
+        doReturn(statusCoordinator).when(locationBarCoordinator).getStatusCoordinator();
+        mActivity.setLocationBarCoordinatorForTesting(locationBarCoordinator);
+
+        doNothing().when(mActivity).super_onTopResumedActivityChanged(anyBoolean());
+        var histograms =
+                HistogramWatcher.newBuilder()
+                        .expectIntRecord(
+                                SearchActivity.HISTOGRAM_SESSION_TERMINATION_REASON
+                                        + HISTOGRAM_SUFFIX_HUB,
+                                TerminationReason.ACTIVITY_FOCUS_LOST)
+                        .build();
+
+        mActivity.handleNewIntent(buildTestServiceIntent(IntentOrigin.HUB), false);
+        mActivity.onTopResumedActivityChanged(false);
+        histograms.assertExpected();
+    }
 }
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h
index 19bb96da..ed36209 100644
--- a/chrome/app/chrome_command_ids.h
+++ b/chrome/app/chrome_command_ids.h
@@ -273,6 +273,8 @@
 #define IDC_TASK_MANAGER_MAIN_MENU      40288
 #define IDC_COMPARE_MENU                40289
 #define IDC_SHOW_ALL_COMPARISON_TABLES  40290
+#define IDC_ADD_TO_COMPARISON_TABLE_MENU 40291
+#define IDC_CREATE_NEW_COMPARISON_TABLE_WITH_TAB 40292
 
 // Spell-check
 // Insert any additional suggestions before _LAST; these have to be consecutive.
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 0555d6e..ad0ab60 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -12275,6 +12275,9 @@
         <message name="IDS_DATA_SHARING_LEAVE_DIALOG_BODY" desc="The body for the leave group dialog.">
           You’ll immediately lose access to the “<ph name="GROUP_NAME">$1<ex>Vacation</ex></ph>” tab group, and it will be deleted from all your devices
         </message>
+        <message name="IDS_DATA_SHARING_LEAVE_DIALOG_BODY_NO_GROUP_TITLE" desc="The body for the leave group dialog when the group does not have a title.">
+          You’ll immediately lose access to this tab group, and it will be deleted from all your devices
+        </message>
         <message name="IDS_DATA_SHARING_LEAVE_DIALOG_CONFIRM" desc="The text for the confirmation button for the leave group dialog.">
           Leave
         </message>
diff --git a/chrome/app/generated_resources_grd/IDS_DATA_SHARING_LEAVE_DIALOG_BODY_NO_GROUP_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_DATA_SHARING_LEAVE_DIALOG_BODY_NO_GROUP_TITLE.png.sha1
new file mode 100644
index 0000000..962c6993
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_DATA_SHARING_LEAVE_DIALOG_BODY_NO_GROUP_TITLE.png.sha1
@@ -0,0 +1 @@
+721c724b2ef391cc3bb5c36a8cc2c304aa3ff547
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 925c234d..398c736 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1672,6 +1672,13 @@
     "webid/identity_provider_permission_request.h",
   ]
 
+  if (is_chromeos) {
+    sources += [
+      "component_updater/lacros_component_remover.cc",
+      "component_updater/lacros_component_remover.h",
+    ]
+  }
+
   configs += [
     "//build/config/compiler:wexit_time_destructors",
     "//build/config:precompiled_headers",
@@ -6715,6 +6722,7 @@
       deps += [
         "//chrome/common:chrome_features",
         "//components/dbus",
+        "//components/os_crypt/async/browser:freedesktop_secret_key_provider",
         "//components/os_crypt/async/browser:secret_portal_key_provider",
       ]
     }
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 01608d7..f755f73 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -7856,6 +7856,11 @@
      kOsCrOS,
      FEATURE_VALUE_TYPE(features::kAccessibilityMagnifierFollowsChromeVox)},
 
+    {"enable-accessibility-manifest-v3-braille-ime",
+     flag_descriptions::kAccessibilityManifestV3BrailleImeName,
+     flag_descriptions::kAccessibilityManifestV3BrailleImeDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(features::kAccessibilityManifestV3BrailleIme)},
+
     {"enable-accessibility-manifest-v3-enhanced-network-tts",
      flag_descriptions::kAccessibilityManifestV3EnhancedNetworkTtsName,
      flag_descriptions::kAccessibilityManifestV3EnhancedNetworkTtsDescription,
diff --git a/chrome/browser/android/examples/custom_tabs_client/OWNERS b/chrome/browser/android/examples/custom_tabs_client/OWNERS
index 8dd5b62..1f2b21d 100644
--- a/chrome/browser/android/examples/custom_tabs_client/OWNERS
+++ b/chrome/browser/android/examples/custom_tabs_client/OWNERS
@@ -1,4 +1,3 @@
 file://chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS
-katzz@google.com
 jinsukkim@chromium.org
 sinansahin@google.com
diff --git a/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc b/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc
index ed1d398..c34651c 100644
--- a/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc
+++ b/chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.cc
@@ -39,6 +39,7 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_constants.h"
 #include "net/base/url_util.h"
+#include "ui/accessibility/accessibility_features.h"
 #include "ui/base/ime/ash/extension_ime_util.h"
 #include "ui/base/resource/resource_bundle.h"
 
@@ -417,6 +418,13 @@
     std::vector<ComponentExtensionIME>* out_imes) {
   DCHECK(out_imes);
   for (auto& extension : allowlisted_component_extensions) {
+    // TODO(crbug.com/384675323): Remove this check and update
+    // `allowlisted_component_extensions` when flag is removed.
+    if (extension.manifest_resource_id == IDR_BRAILLE_MANIFEST &&
+        ::features::IsAccessibilityManifestV3EnabledForBrailleIme()) {
+      extension.manifest_resource_id = IDR_BRAILLE_MANIFEST_MV3;
+    }
+
     ComponentExtensionIME component_ime;
     component_ime.manifest =
         ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
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 2841f68..2bd32089 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
@@ -199,8 +199,7 @@
     public void requestLayerUpdate(boolean animate) {
         assert isEnabled();
 
-        updateLayerVisibilities();
-        recalculateLayerSizes();
+        updateLayerVisibilitiesAndSizes();
         updateBrowserControlsHeight(animate);
         if (mBrowserControlsSizer.offsetOverridden() && isDispatchingYOffset()) {
             repositionLayers(
@@ -512,6 +511,19 @@
         }
     }
 
+    /**
+     * Recalculates layer visibilities and sizes without mutating bottom controls height or actually
+     * repositioning layers. A call to this method must be followed by a call to
+     * requestLayerUpdate() in the same stack frame to avoid inconsistency between
+     * BottomControlsStacker's state and the state of individual layers. This is useful if you need
+     * to mutate browser controls height(s) *before* BottomControlsStacker, e.g. animating a
+     * simultaneous top and bottom height change.
+     */
+    public void updateLayerVisibilitiesAndSizes() {
+        updateLayerVisibilities();
+        recalculateLayerSizes();
+    }
+
     /** Recalculate the browser controls height based on layer sizes. */
     private void recalculateLayerSizes() {
         int height = 0;
diff --git a/chrome/browser/browser_features.cc b/chrome/browser/browser_features.cc
index d78fc586..8100ea1 100644
--- a/chrome/browser/browser_features.cc
+++ b/chrome/browser/browser_features.cc
@@ -78,6 +78,12 @@
 BASE_FEATURE(kDbusSecretPortal,
              "DbusSecretPortal",
              base::FEATURE_ENABLED_BY_DEFAULT);
+
+// Enables usage of os_crypt_async::FreedesktopSecretKeyProvider, which is
+// compatible with the synchronous backend.
+BASE_FEATURE(kUseFreedesktopSecretKeyProvider,
+             "UseFreedesktopSecretKeyProvider",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_LINUX)
 
 // Destroy profiles when their last browser window is closed, instead of when
@@ -234,6 +240,12 @@
 BASE_FEATURE(kSecretPortalKeyProviderUseForEncryption,
              "SecretPortalKeyProviderUseForEncryption",
              base::FEATURE_DISABLED_BY_DEFAULT);
+
+// If true, encrypt new data with the key provided by
+// FreedesktopSecretKeyProvider. Otherwise, it will only decrypt existing data.
+BASE_FEATURE(kUseFreedesktopSecretKeyProviderForEncryption,
+             "UseFreedesktopSecretKeyProviderForEncryption",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_LINUX)
 
 // This flag controls whether to trigger prerendering when the default search
diff --git a/chrome/browser/browser_features.h b/chrome/browser/browser_features.h
index 7978bef0e..4acda03 100644
--- a/chrome/browser/browser_features.h
+++ b/chrome/browser/browser_features.h
@@ -39,6 +39,7 @@
 
 #if BUILDFLAG(IS_LINUX)
 BASE_DECLARE_FEATURE(kDbusSecretPortal);
+BASE_DECLARE_FEATURE(kUseFreedesktopSecretKeyProvider);
 #endif
 
 BASE_DECLARE_FEATURE(kDestroyProfileOnBrowserClose);
@@ -104,6 +105,7 @@
 
 #if BUILDFLAG(IS_LINUX)
 BASE_DECLARE_FEATURE(kSecretPortalKeyProviderUseForEncryption);
+BASE_DECLARE_FEATURE(kUseFreedesktopSecretKeyProviderForEncryption);
 #endif
 
 BASE_DECLARE_FEATURE(kSupportSearchSuggestionForPrerender2);
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index 375bf3c9..aefbe00 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -256,6 +256,8 @@
 
 #if BUILDFLAG(IS_LINUX)
 #include "chrome/browser/browser_features.h"
+#include "components/os_crypt/async/browser/fallback_linux_key_provider.h"
+#include "components/os_crypt/async/browser/freedesktop_secret_key_provider.h"
 #include "components/os_crypt/async/browser/secret_portal_key_provider.h"
 #endif
 
@@ -1397,6 +1399,21 @@
             base::FeatureList::IsEnabled(
                 features::kSecretPortalKeyProviderUseForEncryption)));
   }
+  if (base::FeatureList::IsEnabled(
+          features::kUseFreedesktopSecretKeyProvider)) {
+    // Use a higher priority than the SecretPortalKeyProvider.
+    providers.emplace_back(
+        /*precedence=*/15u,
+        std::make_unique<os_crypt_async::FreedesktopSecretKeyProvider>(
+            base::FeatureList::IsEnabled(
+                features::kUseFreedesktopSecretKeyProviderForEncryption),
+            l10n_util::GetStringUTF8(IDS_PRODUCT_NAME), nullptr));
+    providers.emplace_back(
+        /*precedence=*/15u,
+        std::make_unique<os_crypt_async::FallbackLinuxKeyProvider>(
+            base::FeatureList::IsEnabled(
+                features::kUseFreedesktopSecretKeyProviderForEncryption)));
+  }
 #endif  // BUILDFLAG(IS_LINUX)
 
   os_crypt_async_ =
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 5d02943..9e2507b1b 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -155,6 +155,7 @@
         </if>
 
         <include name="IDR_BRAILLE_MANIFEST" file="resources\chromeos\accessibility\braille_ime\manifest.json" type="BINDATA" />
+        <include name="IDR_BRAILLE_MANIFEST_MV3" file="resources\chromeos\accessibility\braille_ime\mv3\manifest.json" type="BINDATA" />
         <include name="IDR_SMB_SHARES_DIALOG_CONTAINER_HTML" file="resources\chromeos\smb_shares\smb_share_dialog_container.html" type="chrome_html" />
         <include name="IDR_SMB_SHARES_DIALOG_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\smb_shares\smb_share_dialog.js" use_base_dir="false" type="chrome_html" />
         <include name="IDR_SMB_CREDENTIALS_DIALOG_CONTAINER_HTML" file="resources\chromeos\smb_shares\smb_credentials_dialog_container.html" type="chrome_html" />
diff --git a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediator.java b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediator.java
index b4f1cc7..ba35d39 100644
--- a/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediator.java
+++ b/chrome/browser/commerce/merchant_viewer/android/java/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustBottomSheetMediator.java
@@ -121,7 +121,7 @@
                         if (navigation.hasCommitted()) {
                             mToolbarModel.set(
                                     BottomSheetToolbarProperties.URL,
-                                    mWebContents.get().getVisibleUrl());
+                                    getWebContents().getVisibleUrl());
                         }
                     }
                 };
diff --git a/chrome/browser/component_updater/lacros_component_remover.cc b/chrome/browser/component_updater/lacros_component_remover.cc
new file mode 100644
index 0000000..3662fe80
--- /dev/null
+++ b/chrome/browser/component_updater/lacros_component_remover.cc
@@ -0,0 +1,34 @@
+// 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/component_updater/lacros_component_remover.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
+
+namespace component_updater {
+
+void DeleteStatefulLacros(const base::FilePath& user_data_dir) {
+  scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner =
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
+  sequenced_task_runner->PostTask(
+      FROM_HERE, base::BindOnce(
+                     [](const base::FilePath& user_data_dir) {
+                       base::DeletePathRecursively(user_data_dir.Append(
+                           FILE_PATH_LITERAL("lacros-dogfood-canary")));
+                       base::DeletePathRecursively(user_data_dir.Append(
+                           FILE_PATH_LITERAL("lacros-dogfood-dev")));
+                       base::DeletePathRecursively(user_data_dir.Append(
+                           FILE_PATH_LITERAL("lacros-dogfood-beta")));
+                       base::DeletePathRecursively(user_data_dir.Append(
+                           FILE_PATH_LITERAL("lacros-dogfood-stable")));
+                     },
+                     user_data_dir));
+}
+
+}  // namespace component_updater
diff --git a/chrome/browser/component_updater/lacros_component_remover.h b/chrome/browser/component_updater/lacros_component_remover.h
new file mode 100644
index 0000000..5581f1e
--- /dev/null
+++ b/chrome/browser/component_updater/lacros_component_remover.h
@@ -0,0 +1,18 @@
+// 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_COMPONENT_UPDATER_LACROS_COMPONENT_REMOVER_H_
+#define CHROME_BROWSER_COMPONENT_UPDATER_LACROS_COMPONENT_REMOVER_H_
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace component_updater {
+
+void DeleteStatefulLacros(const base::FilePath& user_data_dir);
+
+}  // namespace component_updater
+
+#endif  // CHROME_BROWSER_COMPONENT_UPDATER_LACROS_COMPONENT_REMOVER_H_
diff --git a/chrome/browser/component_updater/registration.cc b/chrome/browser/component_updater/registration.cc
index a05936c6..672bb01 100644
--- a/chrome/browser/component_updater/registration.cc
+++ b/chrome/browser/component_updater/registration.cc
@@ -15,7 +15,6 @@
 #include "base/task/thread_pool.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/buildflags.h"
 #include "chrome/browser/component_updater/app_provisioning_component_installer.h"
@@ -83,9 +82,9 @@
 #include "media/base/media_switches.h"
 #endif  // !BUILDFLAG(IS_ANDROID)
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/component_updater/smart_dim_component_installer.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(ENABLE_MEDIA_FOUNDATION_WIDEVINE_CDM)
 #include "chrome/browser/component_updater/media_foundation_widevine_cdm_component_installer.h"
@@ -110,6 +109,10 @@
 #include "ui/aura/env.h"
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chrome/browser/component_updater/lacros_component_remover.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 namespace component_updater {
 
 void RegisterComponentsForUpdate() {
@@ -167,14 +170,13 @@
       DeleteHistorySearchStringsComponent(path);
     }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-    if (base::SysInfo::IsRunningOnChromeOS()) {
-      // PNaCl on Lacros used to be a component, but on real devices this has
-      // been replaced by a link to the files also used by ash.
-      // Clean up the component if it is present.
-      DeletePnaclComponent(path);
-    }
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS)
+    // Lacros is sunsetted. While rootfs Lacros was already taken care of,
+    // stateful Lacros needs to be cleaned up just like a regular component.
+    // TODO(crbug.com/380780352): Remove this after the stepping stone.
+    component_updater::DeleteStatefulLacros(path);
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
     // NaCl and PNaCl are no longer supported on Windows and Mac, clean up
     // remaining component.
@@ -188,12 +190,12 @@
   // policies on Fuchsia.
   RegisterFileTypePoliciesComponent(cus);
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
+#if !BUILDFLAG(IS_CHROMEOS)
   // CRLSetFetcher attempts to load a CRL set from either the local disk or
   // network.
   // For Chrome OS this registration is delayed until user login.
   component_updater::RegisterCRLSetComponent(cus);
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
   RegisterOriginTrialsComponent(cus);
   RegisterMediaEngagementPreloadComponent(cus, base::OnceClosure());
@@ -207,10 +209,10 @@
   RegisterSafetyTipsComponent(cus);
   RegisterCrowdDenyComponent(cus);
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   RegisterSmartDimComponent(cus);
   RegisterAppProvisioningComponent(cus);
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(USE_MINIKIN_HYPHENATION) && !BUILDFLAG(IS_ANDROID)
   RegisterHyphenationComponent(cus);
diff --git a/chrome/browser/component_updater/widevine_cdm_component_installer.cc b/chrome/browser/component_updater/widevine_cdm_component_installer.cc
index 7ae4e6d8..034a5f3 100644
--- a/chrome/browser/component_updater/widevine_cdm_component_installer.cc
+++ b/chrome/browser/component_updater/widevine_cdm_component_installer.cc
@@ -52,7 +52,7 @@
 #include "chrome/common/media/component_widevine_cdm_hint_file_linux.h"
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 #include "chromeos/ash/components/dbus/image_loader/image_loader_client.h"
 #endif
 
@@ -72,7 +72,7 @@
 static_assert(std::size(kWidevineSha2Hash) == crypto::kSHA256Length,
               "Wrong hash length");
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 // On ChromeOS the component updated CDM comes as a disk image which must be
 // registered and then mounted in order to access the files. The startup
 // script that mounts the image (widevine-cdm.conf) also uses this name.
@@ -146,7 +146,7 @@
   return cdm_path;
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 // This is called when ImageLoaderClient::RegisterComponent() is done.
 void OnImageRegistered(std::optional<bool> result) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -244,7 +244,7 @@
   loader->LoadComponent(ImageLoaderComponentName,
                         base::BindOnce(&OnImageLoaded));
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace
 
@@ -300,12 +300,12 @@
   DVLOG(1) << __func__ << ": install_dir=" << install_dir
            << ", manifest=" << manifest;
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // On ASH ChromeOS, anything downloaded by Component Updater is an image
-  // that needs to be mounted before the files it contains can be used. So
-  // simply register the image, so that it can be mounted next time the
-  // device boots. It will also be mounted by UpdateCdmPath() so that the hint
-  // file can be updated.
+#if BUILDFLAG(IS_CHROMEOS)
+  // On ChromeOS, anything downloaded by Component Updater is an image that
+  // needs to be mounted before the files it contains can be used. So simply
+  // register the image, so that it can be mounted next time the device boots.
+  // It will also be mounted by UpdateCdmPath() so that the hint file can be
+  // updated.
   auto* version = manifest.FindString("version");
   if (!version) {
     return update_client::CrxInstaller::Result(
@@ -344,7 +344,7 @@
 bool WidevineCdmComponentInstallerPolicy::VerifyInstallation(
     const base::Value::Dict& manifest,
     const base::FilePath& install_dir) const {
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
+#if !BUILDFLAG(IS_CHROMEOS)
   // On ChromeOS, what gets downloaded is an image rather than the directory
   // structure expected. As a result, we can not check that there is an
   // library contained until the image is loaded. But on all other systems
@@ -401,7 +401,7 @@
     return;
   }
 
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_LINUX)
   VLOG(1) << "Updating hint file with Widevine CDM " << cdm_version;
 
   // This is running on a thread that allows IO, so simply update the hint file.
@@ -409,8 +409,8 @@
     PLOG(WARNING) << "Failed to update Widevine CDM hint path.";
   }
 
-#elif BUILDFLAG(IS_CHROMEOS_ASH)
-  // On ChromeOS ASH, the selected CDM could be the bundled CDM or an image
+#elif BUILDFLAG(IS_CHROMEOS)
+  // On ChromeOS, the selected CDM could be the bundled CDM or an image
   // containing the CDM downloaded by CU. As the CDM is loaded when Chrome
   // starts, there is no need to register it as the new version can't be
   // used until the device restarts. However, we do want to update the hint
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabMediator.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabMediator.java
index 8d4fc9a..fadbff1 100644
--- a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabMediator.java
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorTabMediator.java
@@ -126,7 +126,7 @@
                     public void didFinishNavigationInPrimaryMainFrame(NavigationHandle navigation) {
                         if (navigation.hasCommitted()) {
                             mIsOnErrorPage = navigation.isErrorPage();
-                            mSheetContent.updateURL(mWebContents.get().getVisibleUrl());
+                            mSheetContent.updateURL(getWebContents().getVisibleUrl());
                         } else {
                             // Not viewable contents such as download. Show a toast and close the
                             // tab.
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabGroupsDelegate.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabGroupsDelegate.java
index ed611cc..c78bf072 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabGroupsDelegate.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabGroupsDelegate.java
@@ -25,14 +25,6 @@
      * @param context The context of the current activity.
      * @param gurl The GURL of the page to be opened in CCT.
      */
-    public void openLearnMoreSharedTabGroupsPage(Context context, GURL gurl);
-
-    /**
-     * Open url in the Chrome Custom Tab.
-     *
-     * @param context The context of the current activity.
-     * @param gurl The GURL of the page to be opened in CCT.
-     */
     public void openUrlInChromeCustomTab(Context context, GURL gurl);
 
     /**
diff --git a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManager.java b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManager.java
index ba630a6..53bba183 100644
--- a/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManager.java
+++ b/chrome/browser/data_sharing/android/java/src/org/chromium/chrome/browser/data_sharing/DataSharingTabManager.java
@@ -84,7 +84,7 @@
     private static final String LEARN_MORE_SHARED_TAB_GROUP_PAGE_URL =
             "https://support.google.com/chrome/?p=chrome_collaboration";
     private static final String LEARN_ABOUT_BLOCKED_ACCOUNTS_URL =
-            "https://support.google.com/chrome/?p=chrome_collaboration";
+            "https://support.google.com/accounts/answer/6388749";
 
     private final ObservableSupplier<TabModelSelector> mTabModelSelectorSupplier;
     private final DataSharingTabGroupsDelegate mDataSharingTabGroupsDelegate;
@@ -846,12 +846,6 @@
         DataSharingUiConfig.DataSharingCallback dataSharingCallback =
                 new DataSharingUiConfig.DataSharingCallback() {
                     @Override
-                    public void onLearnMoreAboutSharedTabGroupsClicked(Context context, GURL url) {
-                        mDataSharingTabGroupsDelegate.openLearnMoreSharedTabGroupsPage(
-                                context, url);
-                    }
-
-                    @Override
                     public void onClickOpenChromeCustomTab(Context context, GURL url) {
                         mDataSharingTabGroupsDelegate.openUrlInChromeCustomTab(context, url);
                     }
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc
index ca8026378..46545fe1 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate.cc
@@ -1936,13 +1936,6 @@
                                                     show_download_in_folder);
   }
 #endif
-  if (!download->GetAutoOpened()) {
-    download::DownloadContent download_content =
-        download::DownloadContentFromMimeType(download->GetMimeType(), false);
-    safe_browsing::RecordDownloadOpenedLatency(
-        download->GetDangerType(), download_content, base::Time::Now(),
-        download->GetEndTime(), show_download_in_folder);
-  }
 }
 
 void ChromeDownloadManagerDelegate::MaybeSendDangerousDownloadCanceledReport(
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
index 003dc79f..00b0aff 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
@@ -18,6 +18,7 @@
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
+#include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
@@ -284,6 +285,20 @@
     return;
   }
 
+  auto* manager =
+      web_modal::WebContentsModalDialogManager::FromWebContents(web_contents());
+  if (!manager) {
+    // `manager` being null indicates that `web_contents()` doesn't correspond
+    // to a browser tab (ex: an extension background page reading the
+    // clipboard). In such a case, we don't show a dialog and instead simply
+    // accept/cancel the result immediately. See crbug.com/374120523 and
+    // crbug.com/388049470 for more context.
+    if (!is_pending()) {
+      CancelButtonCallback();
+    }
+    return;
+  }
+
   // If the web contents is still valid when the delay timer goes off and the
   // dialog has not yet been shown, show it now.
   if (web_contents() && !contents_view_) {
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
index cf23f05d6..a4fa0ac 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
@@ -22,12 +22,15 @@
 #include "chrome/browser/enterprise/connectors/test/fake_content_analysis_delegate.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
+#include "chrome/browser/ui/webui/chrome_web_contents_handler.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
 #include "components/download/public/common/mock_download_item.h"
 #include "components/enterprise/common/proto/connectors.pb.h"
 #include "components/prefs/scoped_user_pref_update.h"
+#include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -39,8 +42,10 @@
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/textarea/textarea.h"
 #include "ui/views/controls/throbber.h"
+#include "ui/views/controls/webview/web_dialog_view.h"
 #include "ui/views/test/ax_event_counter.h"
 #include "ui/views/test/views_test_utils.h"
+#include "ui/web_dialogs/test/test_web_dialog_delegate.h"
 
 namespace enterprise_connectors {
 
@@ -275,7 +280,36 @@
 
   base::TimeDelta response_delay() const { return std::get<2>(GetParam()); }
 
+  void SetUpOnMainThread() override {
+    ui::test::TestWebDialogDelegate* delegate =
+        new ui::test::TestWebDialogDelegate(GURL(url::kAboutBlankURL));
+    delegate->SetDeleteOnClosedAndObserve(&web_dialog_delegate_destroyed_);
+
+    auto view = std::make_unique<views::WebDialogView>(
+        browser()->profile(), delegate,
+        std::make_unique<ChromeWebContentsHandler>());
+    view->SetOwnedByWidget(true);
+    gfx::NativeView parent_view =
+        browser()->tab_strip_model()->GetActiveWebContents()->GetNativeView();
+    view_ = view.get();
+    view_tracker_.SetView(view_);
+
+    auto* widget =
+        views::Widget::CreateWindowWithParent(std::move(view), parent_view);
+    widget->Show();
+
+    EXPECT_TRUE(content::WaitForLoadStop(view_->web_contents()));
+  }
+
+  content::WebContents* GetWebViewDialogContents() {
+    return view_->web_contents();
+  }
+
  private:
+  views::ViewTracker view_tracker_;
+  raw_ptr<views::WebDialogView, DisableDanglingPtrDetection> view_ = nullptr;
+  bool web_dialog_delegate_destroyed_ = false;
+
   raw_ptr<ContentAnalysisDialog, DanglingUntriaged> dialog_;
 
   base::TimeTicks ctor_called_timestamp_;
@@ -589,6 +623,43 @@
   EXPECT_TRUE(called);
 }
 
+IN_PROC_BROWSER_TEST_P(ContentAnalysisDialogBehaviorBrowserTest,
+                       NoWebContentsModalDialogManager) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+
+  // Setup policies to enable deep scanning, its UI and the responses to be
+  // simulated.
+  enterprise_connectors::test::SetAnalysisConnector(
+      browser()->profile()->GetPrefs(), FILE_ATTACHED,
+      kBlockingScansForDlpAndMalware);
+  SetStatusCallbackResponse(
+      safe_browsing::SimpleContentAnalysisResponseForTesting(
+          dlp_success(), malware_success(), /*has_custom_rule_message=*/false));
+
+  // Set up delegate test values.
+  test::FakeContentAnalysisDelegate::SetResponseDelay(response_delay());
+  SetUpDelegate();
+
+  base::RunLoop run_loop;
+  ContentAnalysisDelegate::Data data;
+  CreateFilesForTest({"foo.doc"}, {"content"}, &data);
+  ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled(
+      browser()->profile(), GURL(kTestUrl), &data,
+      enterprise_connectors::AnalysisConnector::FILE_ATTACHED));
+
+  ContentAnalysisDelegate::CreateForWebContents(
+      GetWebViewDialogContents(), std::move(data),
+      base::BindOnce(
+          [](base::OnceClosure quit_closure,
+             const ContentAnalysisDelegate::Data& data,
+             ContentAnalysisDelegate::Result& result) {
+            std::move(quit_closure).Run();
+          },
+          run_loop.QuitClosure()),
+      safe_browsing::DeepScanAccessPoint::UPLOAD);
+  run_loop.Run();
+}
+
 // The scan type controls if DLP, malware or both are enabled via policies. The
 // dialog currently behaves identically in all 3 cases, so this parameter
 // ensures this assumption is not broken by new code.
diff --git a/chrome/browser/extensions/account_extension_tracker.cc b/chrome/browser/extensions/account_extension_tracker.cc
index a305b553..77ca943 100644
--- a/chrome/browser/extensions/account_extension_tracker.cc
+++ b/chrome/browser/extensions/account_extension_tracker.cc
@@ -127,6 +127,9 @@
     const signin::PrimaryAccountChangeEvent& event_details) {
   ExtensionRegistry* extension_registry = ExtensionRegistry::Get(profile_);
 
+  bool observers_notified = false;
+
+  // First look for whether or not the user has signed in or signed out.
   auto signin_event_type =
       event_details.GetEventTypeFor(signin::ConsentLevel::kSignin);
   switch (signin_event_type) {
@@ -149,6 +152,10 @@
       }
 
       extensions_installed_with_signin_promo_.clear();
+
+      // Don't notify observers that extensions uploadability has changed here,
+      // since initial sync data has not been received yet. Notifying here now
+      // may cause UI flickers from events fired in rapid succession.
       break;
     }
     case signin::PrimaryAccountChangeEvent::Type::kCleared: {
@@ -161,11 +168,33 @@
       for (const auto& extension : extensions) {
         SetAccountExtensionType(extension->id(), AccountExtensionType::kLocal);
       }
+
+      NotifyOnExtensionsUploadabilityChanged();
+      observers_notified = true;
       break;
     }
     case signin::PrimaryAccountChangeEvent::Type::kNone:
       break;
   }
+
+  // Now see if there is any change on whether the user has enabled or disabled
+  // full sync. If there is, notify observers that the eligibility for uploading
+  // extensions may have changed. If observers have already been notified, just
+  // return early.
+  if (observers_notified) {
+    return;
+  }
+
+  auto sync_event_type =
+      event_details.GetEventTypeFor(signin::ConsentLevel::kSync);
+  switch (sync_event_type) {
+    case signin::PrimaryAccountChangeEvent::Type::kCleared:
+    case signin::PrimaryAccountChangeEvent::Type::kSet:
+      NotifyOnExtensionsUploadabilityChanged();
+      break;
+    case signin::PrimaryAccountChangeEvent::Type::kNone:
+      break;
+  }
 }
 
 void AccountExtensionTracker::OnExtensionSyncDataReceived(
@@ -179,9 +208,16 @@
   if (type == AccountExtensionType::kLocal) {
     SetAccountExtensionType(extension_id,
                             AccountExtensionType::kAccountInstalledLocally);
+    for (auto& observer : observers_) {
+      observer.OnExtensionUploadabilityChanged(extension_id);
+    }
   }
 }
 
+void AccountExtensionTracker::OnInitialExtensionsSyncDataReceived() {
+  NotifyOnExtensionsUploadabilityChanged();
+}
+
 AccountExtensionTracker::AccountExtensionType
 AccountExtensionTracker::GetAccountExtensionType(
     const ExtensionId& extension_id) const {
@@ -221,6 +257,9 @@
   // Uploading extensions as "account extensions" aka extensions syncing to the
   // current signed in user, is only enabled if the user is signed in and
   // syncing extensions in transport mode.
+  // If the user is not signed in, then nothing syncs.
+  // If the user is signed into full sync, then any syncable extensions
+  // automatically get uploaded so user uploading is disabled.
   if (!sync_util::IsSyncingExtensionsInTransportMode(profile_)) {
     return false;
   }
@@ -238,6 +277,14 @@
   SetAccountExtensionType(extension_id, type);
 }
 
+void AccountExtensionTracker::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void AccountExtensionTracker::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
 void AccountExtensionTracker::SetAccountExtensionType(
     const ExtensionId& extension_id,
     AccountExtensionTracker::AccountExtensionType type) {
@@ -252,4 +299,10 @@
       [&extension_id](const ExtensionId& id) { return extension_id == id; });
 }
 
+void AccountExtensionTracker::NotifyOnExtensionsUploadabilityChanged() {
+  for (auto& observer : observers_) {
+    observer.OnExtensionsUploadabilityChanged();
+  }
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/account_extension_tracker.h b/chrome/browser/extensions/account_extension_tracker.h
index 9da7aa1..ce4c295 100644
--- a/chrome/browser/extensions/account_extension_tracker.h
+++ b/chrome/browser/extensions/account_extension_tracker.h
@@ -52,6 +52,18 @@
     kLast = 2,
   };
 
+  class Observer : public base::CheckedObserver {
+   public:
+    // Called when an extension's eligibility to be uploaded to the user's
+    // account may have changed.
+    virtual void OnExtensionUploadabilityChanged(const ExtensionId& id) = 0;
+
+    // Called when whether extensions can be uploaded to the user's account may
+    // be changed. Usually emitted when the initial sync download completes or
+    // when the user is no longer syncing extensions in transport mode.
+    virtual void OnExtensionsUploadabilityChanged() = 0;
+  };
+
   explicit AccountExtensionTracker(Profile* profile);
 
   AccountExtensionTracker(const AccountExtensionTracker&) = delete;
@@ -78,6 +90,11 @@
   // Called when sync data is received for the given `extension_id`.
   void OnExtensionSyncDataReceived(const ExtensionId& extension_id);
 
+  // Called just after the initial set of extension sync data is received.
+  // i.e. during browser startup (if extensions sync is already enabled), or
+  // once the initial download completes after extensions sync gets enabled.
+  void OnInitialExtensionsSyncDataReceived();
+
   AccountExtensionType GetAccountExtensionType(
       const ExtensionId& extension_id) const;
 
@@ -92,6 +109,9 @@
   void SetAccountExtensionTypeForTesting(const ExtensionId& extension_id,
                                          AccountExtensionType type);
 
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
  private:
   // Sets the extension's AccountExtensionType. Called when the extension is
   // installed (not updated) or when there is incoming sync data for the
@@ -102,12 +122,18 @@
   // Removes `extension_id` in `extensions_installed_with_signin_promo_`.
   void RemoveExpiredExtension(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();
+
   const raw_ptr<Profile> profile_;
 
   // Keeps track of extensions for which a signin promo was shown after
   // installation.
   std::vector<ExtensionId> extensions_installed_with_signin_promo_;
 
+  base::ObserverList<Observer> observers_;
+
   // IdentityManager observer.
   base::ScopedObservation<signin::IdentityManager,
                           signin::IdentityManager::Observer>
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 cd5d2b04..dada213 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api.cc
@@ -509,6 +509,7 @@
   DependsOn(ExtensionSystemFactory::GetInstance());
   DependsOn(PermissionsManager::GetFactory());
   DependsOn(ToolbarActionsModelFactory::GetInstance());
+  DependsOn(AccountExtensionTracker::GetFactory());
 }
 
 // static
@@ -537,6 +538,12 @@
       ExtensionSystem::Get(profile)->extension_service()->allowlist());
   permissions_manager_observation_.Observe(PermissionsManager::Get(profile));
   toolbar_actions_model_observation_.Observe(ToolbarActionsModel::Get(profile));
+
+  if (sync_util::IsExtensionsExplicitSigninEnabled()) {
+    account_extension_tracker_observation_.Observe(
+        AccountExtensionTracker::Get(profile));
+  }
+
   pref_change_registrar_.Init(profile->GetPrefs());
   // The unretained is safe, since the PrefChangeRegistrar unregisters the
   // callback on destruction.
@@ -750,6 +757,22 @@
   }
 }
 
+void DeveloperPrivateEventRouter::OnExtensionUploadabilityChanged(
+    const ExtensionId& id) {
+  BroadcastItemStateChanged(developer::EventType::kPrefsChanged, id);
+}
+
+void DeveloperPrivateEventRouter::OnExtensionsUploadabilityChanged() {
+  const ExtensionSet extensions =
+      ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
+  for (const auto& extension : extensions) {
+    if (sync_util::ShouldSync(profile_, extension.get())) {
+      BroadcastItemStateChanged(developer::EventType::kPrefsChanged,
+                                extension->id());
+    }
+  }
+}
+
 void DeveloperPrivateEventRouter::OnProfilePrefChanged() {
   base::Value::List args;
   args.Append(DeveloperPrivateAPI::CreateProfileInfo(profile_)->ToValue());
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 ead363f..8211ded 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api.h
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api.h
@@ -13,6 +13,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
+#include "chrome/browser/extensions/account_extension_tracker.h"
 #include "chrome/browser/extensions/commands/command_service.h"
 #include "chrome/browser/extensions/error_console/error_console.h"
 #include "chrome/browser/extensions/extension_allowlist.h"
@@ -78,7 +79,8 @@
                                     public ExtensionManagement::Observer,
                                     public WarningService::Observer,
                                     public PermissionsManager::Observer,
-                                    public ToolbarActionsModel::Observer {
+                                    public ToolbarActionsModel::Observer,
+                                    public AccountExtensionTracker::Observer {
  public:
   explicit DeveloperPrivateEventRouter(Profile* profile);
 
@@ -170,6 +172,10 @@
   void OnToolbarModelInitialized() override {}
   void OnToolbarPinnedActionsChanged() override;
 
+  // AccountExtensionTracker::Observer:
+  void OnExtensionUploadabilityChanged(const ExtensionId& id) override;
+  void OnExtensionsUploadabilityChanged() override;
+
   // Handles a profile preference change.
   void OnProfilePrefChanged();
 
@@ -204,6 +210,9 @@
       permissions_manager_observation_{this};
   base::ScopedObservation<ToolbarActionsModel, ToolbarActionsModel::Observer>
       toolbar_actions_model_observation_{this};
+  base::ScopedObservation<AccountExtensionTracker,
+                          AccountExtensionTracker::Observer>
+      account_extension_tracker_observation_{this};
 
   raw_ptr<Profile> profile_;
 
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 9069a56..29047e1 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
@@ -5,6 +5,7 @@
 #include "chrome/browser/extensions/api/developer_private/developer_private_api.h"
 
 #include <memory>
+#include <optional>
 #include <string_view>
 #include <utility>
 
@@ -33,6 +34,8 @@
 #include "chrome/browser/extensions/extension_management_test_util.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_with_install.h"
+#include "chrome/browser/extensions/extension_sync_data.h"
+#include "chrome/browser/extensions/extension_sync_service.h"
 #include "chrome/browser/extensions/extension_sync_util.h"
 #include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/manifest_v2_experiment_manager.h"
@@ -56,6 +59,7 @@
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/supervised_user/core/common/features.h"
 #include "components/sync/base/features.h"
+#include "components/sync/test/fake_sync_change_processor.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/web_contents_tester.h"
@@ -78,6 +82,7 @@
 #include "extensions/common/extension_features.h"
 #include "extensions/common/extension_id.h"
 #include "extensions/common/extension_set.h"
+#include "extensions/common/extension_urls.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/mojom/context_type.mojom.h"
 #include "extensions/common/permissions/permission_set.h"
@@ -112,6 +117,31 @@
   return has_pref(id, context);
 }
 
+bool DoesItemChangedEventMatch(
+    const Event& event,
+    const ExtensionId& extension_id,
+    const api::developer_private::EventType event_type,
+    api::developer_private::ExtensionInfo* info_from_event) {
+  CHECK_GE(1u, event.event_args.size());
+  std::optional<api::developer_private::EventData> event_data =
+      api::developer_private::EventData::FromValue(event.event_args[0]);
+  if (!event_data) {
+    return false;
+  }
+
+  if (event_data->item_id != extension_id ||
+      event_data->event_type != event_type) {
+    return false;
+  }
+
+  if (event_data->extension_info) {
+    CHECK_EQ(extension_id, event_data->extension_info->id);
+    *info_from_event = std::move(*event_data->extension_info);
+  }
+
+  return true;
+}
+
 bool WasItemChangedEventDispatched(
     const TestEventRouterObserver& observer,
     const ExtensionId& extension_id,
@@ -120,22 +150,13 @@
       api::developer_private::OnItemStateChanged::kEventName;
   const auto& event_map = observer.events();
   auto iter = event_map.find(kEventName);
-  if (iter == event_map.end())
-    return false;
-
-  const Event& event = *iter->second;
-  CHECK_GE(1u, event.event_args.size());
-  std::optional<api::developer_private::EventData> event_data =
-      api::developer_private::EventData::FromValue(event.event_args[0]);
-  if (!event_data)
-    return false;
-
-  if (event_data->item_id != extension_id ||
-      event_data->event_type != event_type) {
+  if (iter == event_map.end()) {
     return false;
   }
 
-  return true;
+  api::developer_private::ExtensionInfo info;
+  return DoesItemChangedEventMatch(*iter->second, extension_id, event_type,
+                                   &info);
 }
 
 bool WasUserSiteSettingsChangedEventDispatched(
@@ -279,6 +300,91 @@
       << function->GetError();
 }
 
+// A more targeted version of TestEventRouterObserver to pick up a prefs changed
+// event for a given extension.
+class ItemStatePrefsChangedObserver : public EventRouter::TestObserver {
+ public:
+  ItemStatePrefsChangedObserver(EventRouter* event_router,
+                                const ExtensionId& extension_id);
+
+  ItemStatePrefsChangedObserver(const ItemStatePrefsChangedObserver&) = delete;
+  ItemStatePrefsChangedObserver& operator=(
+      const ItemStatePrefsChangedObserver&) = delete;
+
+  ~ItemStatePrefsChangedObserver() override;
+
+  // Waits until a matching prefs changed event is dispatched for the
+  // `extension_id_`.
+  void WaitForEvent();
+
+  // Resets the `event_info_` so the observer can wait for another matching
+  // event.
+  void Reset();
+
+  bool WasEventDispatched() { return event_info_.has_value(); }
+
+  api::developer_private::ExtensionInfo event_info() {
+    return event_info_.has_value() ? event_info_->Clone()
+                                   : api::developer_private::ExtensionInfo();
+  }
+
+ private:
+  // EventRouter::TestObserver:
+  void OnWillDispatchEvent(const Event& event) override;
+  void OnDidDispatchEventToProcess(const Event& event,
+                                   int process_id) override {}
+
+  // The event info from the prefs changed event. Null if a matching event has
+  // not yet been dispatched.
+  std::optional<api::developer_private::ExtensionInfo> event_info_;
+  std::unique_ptr<base::RunLoop> run_loop_;
+
+  raw_ptr<EventRouter> event_router_;
+  const ExtensionId extension_id_;
+};
+
+ItemStatePrefsChangedObserver::ItemStatePrefsChangedObserver(
+    EventRouter* event_router,
+    const ExtensionId& extension_id)
+    : event_router_(event_router), extension_id_(extension_id) {
+  event_router_->AddObserverForTesting(this);
+}
+
+ItemStatePrefsChangedObserver::~ItemStatePrefsChangedObserver() {
+  // Note: can't use ScopedObserver<> here because the method is
+  // RemoveObserverForTesting() instead of RemoveObserver().
+  event_router_->RemoveObserverForTesting(this);
+}
+
+void ItemStatePrefsChangedObserver::WaitForEvent() {
+  while (!event_info_.has_value()) {
+    // Create a new `RunLoop` since reuse is not supported.
+    run_loop_ = std::make_unique<base::RunLoop>();
+    run_loop_->Run();
+    run_loop_.reset();
+  }
+}
+
+void ItemStatePrefsChangedObserver::Reset() {
+  event_info_ = std::nullopt;
+}
+
+void ItemStatePrefsChangedObserver::OnWillDispatchEvent(const Event& event) {
+  CHECK(!event.event_name.empty());
+
+  api::developer_private::ExtensionInfo info;
+  bool does_event_match = DoesItemChangedEventMatch(
+      event, extension_id_, api::developer_private::EventType::kPrefsChanged,
+      &info);
+
+  if (does_event_match) {
+    event_info_ = std::move(info);
+    if (run_loop_) {
+      run_loop_->Quit();
+    }
+  }
+}
+
 }  // namespace
 
 class DeveloperPrivateApiUnitTest : public ExtensionServiceTestWithInstall {
@@ -3351,23 +3457,95 @@
         /*disabled_features=*/{});
   }
 
+  void SetUp() override {
+    DeveloperPrivateApiUnitTest::SetUp();
+    identity_test_env_profile_adaptor_ =
+        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile());
+  }
+
   DeveloperPrivateApiTransportModeUnitTest(
       const DeveloperPrivateApiTransportModeUnitTest&) = delete;
   DeveloperPrivateApiTransportModeUnitTest& operator=(
       const DeveloperPrivateApiTransportModeUnitTest&) = delete;
 
+ protected:
+  signin::IdentityTestEnvironment* identity_test_env() {
+    return identity_test_env_profile_adaptor_->identity_test_env();
+  }
+
+  bool CanUploadToAccount(const Extension& extension) {
+    return AccountExtensionTracker::Get(profile())->CanUploadAsAccountExtension(
+        extension);
+  }
+
+  // Loads and returns a syncable extension with the given `name`.
+  const scoped_refptr<const Extension> LoadSyncableExtension(const char* name) {
+    const scoped_refptr<const Extension> syncable_extension =
+        ExtensionBuilder(name)
+            .SetLocation(mojom::ManifestLocation::kInternal)
+            .Build();
+    EXPECT_TRUE(sync_util::ShouldSync(profile(), syncable_extension.get()));
+    service()->AddExtension(syncable_extension.get());
+
+    return syncable_extension;
+  }
+
+  // Set up a listener for the given `kEventName` and returns the test
+  // observer.
+  ItemStatePrefsChangedObserver StartListeningForEvent(
+      const ExtensionId& extension_id) {
+    // We need to call DeveloperPrivateAPI::Get() in order to instantiate the
+    // keyed service, since it's not created by default in unit tests.
+    DeveloperPrivateAPI::Get(profile());
+    EventRouter* event_router = EventRouter::Get(profile());
+
+    // The DeveloperPrivateEventRouter will only dispatch events if there's at
+    // least one listener to dispatch to. Create one.
+    GURL dummy_url("chrome-untrusted://one");
+    event_router->AddEventListenerForURL(
+        api::developer_private::OnItemStateChanged::kEventName,
+        render_process_host(), dummy_url);
+
+    return ItemStatePrefsChangedObserver(event_router, extension_id);
+  }
+
+  // Simulates an explicit sign in. This involves both the sign in itself and
+  // flipping the pref to record an explicit sign in.
+  void SimulateExplicitSignIn() {
+    identity_test_env_profile_adaptor_->identity_test_env()
+        ->MakePrimaryAccountAvailable("testy@mctestface.com",
+                                      signin::ConsentLevel::kSignin);
+    profile()->GetPrefs()->SetBoolean(prefs::kExplicitBrowserSignin, true);
+  }
+
+  // Simulates an initial download of sync data with the given `extensions`
+  // present.
+  void SimulateInitialSync(const std::vector<const Extension*>& extensions) {
+    syncer::SyncDataList sync_data;
+    for (const auto* extension : extensions) {
+      ExtensionSyncData data(*extension, true,
+                             extensions::disable_reason::DISABLE_NONE, false,
+                             false, extension_urls::GetWebstoreUpdateUrl());
+
+      sync_data.push_back(data.GetSyncData());
+    }
+
+    ExtensionSyncService::Get(profile())->MergeDataAndStartSyncing(
+        syncer::EXTENSIONS, sync_data,
+        std::make_unique<syncer::FakeSyncChangeProcessor>());
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
+
+  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
+      identity_test_env_profile_adaptor_;
 };
 
 // Test that extensions cannot be uploaded if the user is signed out.
 TEST_F(DeveloperPrivateApiTransportModeUnitTest,
        UploadExtensionToAccount_SignedOut) {
-  const scoped_refptr<const Extension> extension =
-      ExtensionBuilder("ext")
-          .SetLocation(mojom::ManifestLocation::kInternal)
-          .Build();
-  service()->AddExtension(extension.get());
+  auto extension = LoadSyncableExtension("ext");
 
   std::string args = base::StringPrintf(R"(["%s"])", extension->id().c_str());
   auto upload_function = base::MakeRefCounted<
@@ -3390,14 +3568,7 @@
   service()->AddExtension(unsyncable_extension.get());
 
   // Sign the user in without full sync.
-  auto identity_test_env_profile_adaptor =
-      std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile());
-  identity_test_env_profile_adaptor->identity_test_env()
-      ->MakePrimaryAccountAvailable("testy@mctestface.com",
-                                    signin::ConsentLevel::kSignin);
-  // Pretend that the user has completed an explicit sign in before. This is
-  // necessary for extensions to sync in transport mode.
-  profile()->GetPrefs()->SetBoolean(prefs::kExplicitBrowserSignin, true);
+  SimulateExplicitSignIn();
 
   std::string args_str =
       base::StringPrintf(R"(["%s"])", unsyncable_extension->id().c_str());
@@ -3417,22 +3588,10 @@
 
 TEST_F(DeveloperPrivateApiTransportModeUnitTest, UploadExtensionToAccount) {
   // Add a syncable extension.
-  const scoped_refptr<const Extension> syncable_extension =
-      ExtensionBuilder("sync_ext")
-          .SetLocation(mojom::ManifestLocation::kInternal)
-          .Build();
-  EXPECT_TRUE(sync_util::ShouldSync(profile(), syncable_extension.get()));
-  service()->AddExtension(syncable_extension.get());
+  auto syncable_extension = LoadSyncableExtension("ext");
 
   // Sign the user in without full sync.
-  auto identity_test_env_profile_adaptor =
-      std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile());
-  identity_test_env_profile_adaptor->identity_test_env()
-      ->MakePrimaryAccountAvailable("testy@mctestface.com",
-                                    signin::ConsentLevel::kSignin);
-  // Pretend that the user has completed an explicit sign in before. This is
-  // necessary for extensions to sync in transport mode.
-  profile()->GetPrefs()->SetBoolean(prefs::kExplicitBrowserSignin, true);
+  SimulateExplicitSignIn();
 
   // The syncable extension can be uploaded, but pretend we don't proceed with
   // the upload by simulating cancelling the dialog.
@@ -3466,4 +3625,157 @@
           syncable_extension->id()));
 }
 
+// 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) {
+  auto extension = LoadSyncableExtension("ext");
+  ItemStatePrefsChangedObserver test_observer =
+      StartListeningForEvent(extension->id());
+
+  // Sign the user in without full sync.
+  SimulateExplicitSignIn();
+
+  // While the extension technically can be uploaded to the user's account,
+  // don't dispatch an update event if the initial sync data has not been
+  // received yet.
+  EXPECT_TRUE(CanUploadToAccount(*extension));
+  EXPECT_FALSE(test_observer.WasEventDispatched());
+  test_observer.Reset();
+
+  // Now simulate an initial sync where no extensions are present in the user's
+  // sync data.
+  SimulateInitialSync({});
+  test_observer.WaitForEvent();
+
+  // Upon receiving the sync data, the API's event router should be notified.
+  auto info = test_observer.event_info();
+
+  // Verify that the update has alerted observers that the extension can now be
+  // uploaded.
+  EXPECT_TRUE(info.can_upload_as_account_extension);
+  EXPECT_TRUE(CanUploadToAccount(*extension));
+}
+
+// Test that an extension is not uploadable when it's already present in the
+// user's sync data.
+TEST_F(DeveloperPrivateApiTransportModeUnitTest,
+       ExtensionNotUploadableFromInitialSync) {
+  auto extension = LoadSyncableExtension("ext");
+  ItemStatePrefsChangedObserver test_observer =
+      StartListeningForEvent(extension->id());
+
+  // Sign the user in without full sync.
+  SimulateExplicitSignIn();
+  EXPECT_FALSE(test_observer.WasEventDispatched());
+  test_observer.Reset();
+
+  // Simulate an initial sync where the extension is already present in the
+  // user's sync data.
+  SimulateInitialSync({extension.get()});
+  test_observer.WaitForEvent();
+
+  // An update event should be dispatched but the extension should not be
+  // uploadable since it's already present in sync data.
+  auto info = test_observer.event_info();
+  EXPECT_FALSE(info.can_upload_as_account_extension);
+  EXPECT_FALSE(CanUploadToAccount(*extension));
+}
+
+// Sign outs are not supported for ChromeOS hence this test is not run for
+// ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
+// Test that extensions can no longer be uploaded once the user signs out.
+TEST_F(DeveloperPrivateApiTransportModeUnitTest, CannotUploadAfterSignOut) {
+  // Test setup: Sign in and simulate an empty initial sync so the extension is
+  // uploadavble.
+  auto extension = LoadSyncableExtension("ext");
+  ItemStatePrefsChangedObserver test_observer =
+      StartListeningForEvent(extension->id());
+
+  // Sign the user in without full sync.
+  SimulateExplicitSignIn();
+
+  SimulateInitialSync({});
+  test_observer.WaitForEvent();
+
+  auto info = test_observer.event_info();
+  EXPECT_TRUE(info.can_upload_as_account_extension);
+  test_observer.Reset();
+
+  // Now sign out. An update should be dispatched indicating that the extension
+  // is no longer syncable.
+  identity_test_env()->ClearPrimaryAccount();
+  test_observer.WaitForEvent();
+  info = test_observer.event_info();
+  EXPECT_FALSE(info.can_upload_as_account_extension);
+  EXPECT_FALSE(CanUploadToAccount(*extension));
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS)
+
+// Test that extensions can no longer be uploaded by the user if they sign into
+// full sync mode.
+TEST_F(DeveloperPrivateApiTransportModeUnitTest, CannotUploadWithFullSync) {
+  // Test setup: Sign in and simulate an empty initial sync so the extension is
+  // uploadavble.
+  auto extension = LoadSyncableExtension("ext");
+  ItemStatePrefsChangedObserver test_observer =
+      StartListeningForEvent(extension->id());
+
+  // Sign the user in without full sync.
+  SimulateExplicitSignIn();
+
+  SimulateInitialSync({});
+  test_observer.WaitForEvent();
+
+  auto info = test_observer.event_info();
+  EXPECT_TRUE(info.can_upload_as_account_extension);
+  test_observer.Reset();
+
+  // Now sign into full sync. Since full sync mode automatically syncs any
+  // syncable extension, the extension cannot be uploaded anymore.
+  identity_test_env()->MakePrimaryAccountAvailable("testy@mctestface.com",
+                                                   signin::ConsentLevel::kSync);
+  test_observer.WaitForEvent();
+  info = test_observer.event_info();
+  EXPECT_FALSE(info.can_upload_as_account_extension);
+  EXPECT_FALSE(CanUploadToAccount(*extension));
+}
+
+// Test that extensions can no longer be uploaded if an update comes in
+// indicating that they're part of the user's sync data.
+TEST_F(DeveloperPrivateApiTransportModeUnitTest,
+       UploadUpdatedAfterIncomingSync) {
+  // Test setup: Sign in and simulate an empty initial sync so the extension is
+  // uploadavble.
+  auto extension = LoadSyncableExtension("ext");
+  ItemStatePrefsChangedObserver test_observer =
+      StartListeningForEvent(extension->id());
+
+  // Sign the user in without full sync.
+  SimulateExplicitSignIn();
+
+  SimulateInitialSync({});
+  test_observer.WaitForEvent();
+
+  auto info = test_observer.event_info();
+  EXPECT_TRUE(info.can_upload_as_account_extension);
+  test_observer.Reset();
+
+  // Simulate a later sync update where the same extension was installed on
+  // another device and the change is synced over.
+  ExtensionSyncData extension_installed_elsewhere(
+      *extension, true, extensions::disable_reason::DISABLE_NONE, false, false,
+      extension_urls::GetWebstoreUpdateUrl());
+  ExtensionSyncService::Get(profile())->ProcessSyncChanges(
+      FROM_HERE, {extension_installed_elsewhere.GetSyncChange(
+                     syncer::SyncChange::ACTION_UPDATE)});
+  test_observer.WaitForEvent();
+
+  // The extension should no longer be uploadable since it is now part of the
+  // user's sync data.
+  info = test_observer.event_info();
+  EXPECT_FALSE(info.can_upload_as_account_extension);
+  EXPECT_FALSE(CanUploadToAccount(*extension));
+}
+
 }  // namespace extensions
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 5eb5fe6..6cb10f3 100644
--- a/chrome/browser/extensions/api/developer_private/extension_info_generator.cc
+++ b/chrome/browser/extensions/api/developer_private/extension_info_generator.cc
@@ -414,10 +414,33 @@
       error_console_(ErrorConsole::Get(browser_context)),
       image_loader_(ImageLoader::Get(browser_context)),
       pending_image_loads_(0u) {
+  profile_observation_.Observe(Profile::FromBrowserContext(browser_context));
 }
 
 ExtensionInfoGenerator::~ExtensionInfoGenerator() = default;
 
+void ExtensionInfoGenerator::OnProfileWillBeDestroyed(Profile* profile) {
+  // Reset all references for keyed services in case this object outlives the
+  // profile or browser context.
+  profile_observation_.Reset();
+  browser_context_ = nullptr;
+  command_service_ = nullptr;
+  extension_system_ = nullptr;
+  extension_prefs_ = nullptr;
+  warning_service_ = nullptr;
+  error_console_ = nullptr;
+  image_loader_ = nullptr;
+
+  // Remove any WeakPtr to terminate any async tasks.
+  weak_factory_.InvalidateWeakPtrs();
+
+  // Flush the callback if there is one.
+  if (!callback_.is_null()) {
+    std::move(callback_).Run({});
+  }
+  // WARNING: `this` is possibly deleted after this line!
+}
+
 void ExtensionInfoGenerator::CreateExtensionInfo(
     const ExtensionId& id,
     ExtensionInfosCallback callback) {
diff --git a/chrome/browser/extensions/api/developer_private/extension_info_generator.h b/chrome/browser/extensions/api/developer_private/extension_info_generator.h
index 77ff756..0089308 100644
--- a/chrome/browser/extensions/api/developer_private/extension_info_generator.h
+++ b/chrome/browser/extensions/api/developer_private/extension_info_generator.h
@@ -12,9 +12,13 @@
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/profiles/profile_observer.h"
 #include "chrome/common/extensions/api/developer_private.h"
 #include "components/supervised_user/core/common/buildflags.h"
 #include "extensions/browser/blocklist_state.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_prefs_observer.h"
 #include "extensions/common/extension_id.h"
 #include "extensions/common/url_pattern.h"
 #include "extensions/common/url_pattern_set.h"
@@ -38,7 +42,7 @@
 
 // Generates the developerPrivate api's specification for ExtensionInfo.
 // This class is designed to only have one generation running at a time!
-class ExtensionInfoGenerator {
+class ExtensionInfoGenerator : public ProfileObserver {
  public:
   using ExtensionInfoList = std::vector<api::developer_private::ExtensionInfo>;
 
@@ -49,7 +53,13 @@
   ExtensionInfoGenerator(const ExtensionInfoGenerator&) = delete;
   ExtensionInfoGenerator& operator=(const ExtensionInfoGenerator&) = delete;
 
-  ~ExtensionInfoGenerator();
+  ~ExtensionInfoGenerator() override;
+
+  // ProfileObserver implementation.
+  // There's a chance that an instance of this class is owned by a task, which
+  // means it could outlive some of the systems cached that would be destroyed
+  // when the profile associated with the `browser_context_` is destroyed.
+  void OnProfileWillBeDestroyed(Profile* profile) override;
 
   // Creates and asynchronously returns an ExtensionInfo for the given
   // |extension_id|, if the extension can be found.
@@ -112,6 +122,8 @@
   // The callback to run once all infos have been created.
   ExtensionInfosCallback callback_;
 
+  base::ScopedObservation<Profile, ProfileObserver> profile_observation_{this};
+
   base::WeakPtrFactory<ExtensionInfoGenerator> weak_factory_{this};
 
   friend class ExtensionInfoGeneratorUnitTest;
diff --git a/chrome/browser/extensions/extension_sync_service.cc b/chrome/browser/extensions/extension_sync_service.cc
index 4204eff..b4bf5d4 100644
--- a/chrome/browser/extensions/extension_sync_service.cc
+++ b/chrome/browser/extensions/extension_sync_service.cc
@@ -195,6 +195,8 @@
     }
   }
 
+  AccountExtensionTracker::Get(profile_)->OnInitialExtensionsSyncDataReceived();
+
   // Now push the local state to sync.
   // Note: We'd like to only send out changes for extensions which have
   // NeedsSync set. However, we can't tell if our changes ever made it to the
diff --git a/chrome/browser/extensions/extension_sync_util.cc b/chrome/browser/extensions/extension_sync_util.cc
index 7544667..810fd0c 100644
--- a/chrome/browser/extensions/extension_sync_util.cc
+++ b/chrome/browser/extensions/extension_sync_util.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/common/extensions/sync_helper.h"
+#include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/base/signin_switches.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/sync/base/features.h"
@@ -42,6 +43,10 @@
 }
 
 bool IsSyncingExtensionsEnabled(Profile* profile) {
+  // TODO(crbug.com/388557898): If this method is called from
+  // IdentityManagerObserver::OnPrimaryAccountChanged, then it could return the
+  // wrong value since the sync service also piggybacks on that event to update
+  // which data types are syncing.
   syncer::SyncService* sync_service =
       SyncServiceFactory::GetForProfile(profile);
   return sync_service &&
@@ -50,9 +55,14 @@
 }
 
 bool IsSyncingExtensionsInTransportMode(Profile* profile) {
-  syncer::SyncService* sync_service =
-      SyncServiceFactory::GetForProfile(profile);
-  return IsSyncingExtensionsEnabled(profile) && !sync_service->HasSyncConsent();
+  // Prefer querying the IdentityManager for consent levels since it's the base
+  // source of truth, and something like sync_service->HasSyncConsent() is a bit
+  // slower to update since it observes IdentityManager.
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(profile);
+  return IsSyncingExtensionsEnabled(profile) &&
+         identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin) &&
+         !identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync);
 }
 
 bool IsExtensionsExplicitSigninEnabled() {
diff --git a/chrome/browser/extensions/global_shortcut_listener.cc b/chrome/browser/extensions/global_shortcut_listener.cc
index 8d574cf..d85ca38 100644
--- a/chrome/browser/extensions/global_shortcut_listener.cc
+++ b/chrome/browser/extensions/global_shortcut_listener.cc
@@ -48,12 +48,6 @@
     const ui::Accelerator& accelerator,
     Observer* observer) {
   CHECK(global_accelerator_listener_);
-
-  if (IsShortcutHandlingSuspended()) {
-    return false;
-  }
-
-  accelerators_.push_back(accelerator);
   return global_accelerator_listener_->RegisterAccelerator(accelerator,
                                                            observer);
 }
@@ -62,54 +56,23 @@
     const ui::Accelerator& accelerator,
     Observer* observer) {
   CHECK(global_accelerator_listener_);
-
-  if (IsShortcutHandlingSuspended()) {
-    return;
-  }
-
   global_accelerator_listener_->UnregisterAccelerator(accelerator, observer);
-  RemoveAccelerator(accelerator);
 }
 
 void GlobalShortcutListener::UnregisterAccelerators(Observer* observer) {
   CHECK(global_accelerator_listener_);
-
-  if (IsShortcutHandlingSuspended()) {
-    return;
-  }
-
-  std::vector<ui::Accelerator> removed =
-      global_accelerator_listener_->UnregisterAccelerators(observer);
-  for (const ui::Accelerator& accelerator : removed) {
-    RemoveAccelerator(accelerator);
-  }
+  global_accelerator_listener_->UnregisterAccelerators(observer);
 }
 
 void GlobalShortcutListener::SetShortcutHandlingSuspended(bool suspended) {
   CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   CHECK(global_accelerator_listener_);
-
-  if (shortcut_handling_suspended_ == suspended) {
-    return;
-  }
-
-  shortcut_handling_suspended_ = suspended;
-  for (auto& accelerator : accelerators_) {
-    // On Linux, when shortcut handling is suspended we cannot simply early
-    // return in NotifyKeyPressed (similar to what we do for non-global
-    // shortcuts) because we'd eat the keyboard event thereby preventing the
-    // user from setting the shortcut. Therefore we must unregister while
-    // handling is suspended and register when handling resumes.
-    if (shortcut_handling_suspended_) {
-      global_accelerator_listener_->StopListeningForAccelerator(accelerator);
-    } else {
-      global_accelerator_listener_->StartListeningForAccelerator(accelerator);
-    }
-  }
+  global_accelerator_listener_->SetShortcutHandlingSuspended(suspended);
 }
 
 bool GlobalShortcutListener::IsShortcutHandlingSuspended() const {
-  return shortcut_handling_suspended_;
+  CHECK(global_accelerator_listener_);
+  return global_accelerator_listener_->IsShortcutHandlingSuspended();
 }
 
 bool GlobalShortcutListener::IsRegistrationHandledExternally() const {
@@ -125,9 +88,4 @@
       accelerator_group_id, profile_id, commands, observer);
 }
 
-void GlobalShortcutListener::RemoveAccelerator(
-    const ui::Accelerator& accelerator) {
-  std::erase(accelerators_, accelerator);
-}
-
 }  // namespace extensions
diff --git a/chrome/browser/extensions/global_shortcut_listener.h b/chrome/browser/extensions/global_shortcut_listener.h
index 78804b1a..062b08a 100644
--- a/chrome/browser/extensions/global_shortcut_listener.h
+++ b/chrome/browser/extensions/global_shortcut_listener.h
@@ -79,19 +79,8 @@
       ui::GlobalAcceleratorListener* global_shortcut_listener);
 
  private:
-  // Removes an accelerator from the list of accelerators registered with this
-  // class.
-  void RemoveAccelerator(const ui::Accelerator& accelerator);
-
   // GlobalShortcutListener instance to which where most calls are delegated.
   raw_ptr<ui::GlobalAcceleratorListener> global_accelerator_listener_;
-
-  // Local tracking of accelerators that need to be registered or unregistered
-  // when shortcut handling is suspended.
-  std::vector<ui::Accelerator> accelerators_;
-
-  // Keeps track of whether shortcut handling is currently suspended.
-  bool shortcut_handling_suspended_ = false;
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 1cfdb9e2..aeb682c 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2202,7 +2202,7 @@
   },
   {
     "name": "eche-swa-check-android-network-info",
-    "owners": [ "dhnishi@chromium.org", "corakwue@google.com", "ftsui@google.com" ],
+    "owners": [ "corakwue@google.com", "ftsui@google.com" ],
     "expiry_milestone": 125
   },
   {
@@ -2310,6 +2310,11 @@
     "expiry_milestone": 145
   },
   {
+    "name": "enable-accessibility-manifest-v3-braille-ime",
+    "owners": [ "spectranaut@igalia.com", "akihiroota@chromium.org", "//ui/accessibility/OWNERS" ],
+    "expiry_milestone": 145
+  },
+  {
     "name": "enable-accessibility-manifest-v3-enhanced-network-tts",
     "owners": [ "spectranaut@igalia.com", "akihiroota@chromium.org", "//ui/accessibility/OWNERS" ],
     "expiry_milestone": 145
@@ -5585,6 +5590,11 @@
     "owners": ["cmyang@google.com", "ericappelbaum@google.com"],
     "expiry_milestone": 135
   },
+    {
+    "name": "lens-overlay-alternative-onboarding",
+    "owners": [ "stkhapugin@chromium.org", "christianxu@chromium.org", "lens-chrome@google.com" ],
+    "expiry_milestone": 135
+  },
   {
     "name": "lens-overlay-disable-price-insights",
     "owners": ["christianxu@google.com", "stkhapugin@chromium.org"],
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 5c952a3e..88a8e999 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -6577,6 +6577,11 @@
     "Experimental migration of accessibility features from extension manifest "
     "v2 to v3. Likely to break accessibility access while experimental.";
 
+const char kAccessibilityManifestV3BrailleImeName[] =
+    "Changes accessibility extension Braille IME manifest v2 to v3.";
+const char kAccessibilityManifestV3BrailleImeDescription[] =
+    "Experimental migration of Braille IME from extension manifest v2 to v3.";
+
 const char kAccessibilityManifestV3EnhancedNetworkTtsName[] =
     "Changes accessibility extension Enhanced Network TTS manifest v2 to v3.";
 const char kAccessibilityManifestV3EnhancedNetworkTtsDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index af1be358..97da391 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -3851,6 +3851,9 @@
 extern const char kExperimentalAccessibilityManifestV3Name[];
 extern const char kExperimentalAccessibilityManifestV3Description[];
 
+extern const char kAccessibilityManifestV3BrailleImeName[];
+extern const char kAccessibilityManifestV3BrailleImeDescription[];
+
 extern const char kAccessibilityManifestV3EnhancedNetworkTtsName[];
 extern const char kAccessibilityManifestV3EnhancedNetworkTtsDescription[];
 
diff --git a/chrome/browser/glic/glic_keyed_service.cc b/chrome/browser/glic/glic_keyed_service.cc
index 86cbb26..30f6027 100644
--- a/chrome/browser/glic/glic_keyed_service.cc
+++ b/chrome/browser/glic/glic_keyed_service.cc
@@ -34,6 +34,10 @@
 
 GlicKeyedService::~GlicKeyedService() = default;
 
+void GlicKeyedService::Shutdown() {
+  window_controller_.Shutdown();
+}
+
 void GlicKeyedService::LaunchUI(views::View* glic_button_view) {
   // Glic may be disabled for certain user profiles (the user is browsing in
   // incognito or guest mode, policy, etc). In those cases, the entry points to
@@ -93,6 +97,14 @@
   SetContextAccessIndicator(false);
 }
 
+void GlicKeyedService::AttachPanel() {
+  window_controller_.Attach();
+}
+
+void GlicKeyedService::DetachPanel() {
+  window_controller_.Detach();
+}
+
 std::optional<gfx::Size> GlicKeyedService::ResizePanel(const gfx::Size& size) {
   if (!window_controller_.Resize(size)) {
     return std::nullopt;
diff --git a/chrome/browser/glic/glic_keyed_service.h b/chrome/browser/glic/glic_keyed_service.h
index 3f6f3be..a9ccea6e 100644
--- a/chrome/browser/glic/glic_keyed_service.h
+++ b/chrome/browser/glic/glic_keyed_service.h
@@ -39,6 +39,9 @@
   GlicKeyedService& operator=(const GlicKeyedService&) = delete;
   ~GlicKeyedService() override;
 
+  // KeyedService
+  void Shutdown() override;
+
   // Launches the Glic UI anchored at the given View object. When started from
   // the launcher, no anchor view is provided.
   void LaunchUI(views::View* glic_button_view);
@@ -52,6 +55,8 @@
                  glic::mojom::WebClientHandler::CreateTabCallback callback);
   void OpenGlicSettingsPage();
   virtual void ClosePanel();
+  void AttachPanel();
+  void DetachPanel();
   std::optional<gfx::Size> ResizePanel(const gfx::Size& size);
   void SetPanelDraggableAreas(const std::vector<gfx::Rect>& draggable_areas);
   void SetContextAccessIndicator(bool show);
diff --git a/chrome/browser/glic/glic_profile_manager.cc b/chrome/browser/glic/glic_profile_manager.cc
index c016d0e..aca656f 100644
--- a/chrome/browser/glic/glic_profile_manager.cc
+++ b/chrome/browser/glic/glic_profile_manager.cc
@@ -13,20 +13,6 @@
 
 namespace glic {
 
-namespace {
-
-// Ensures that the window is closed early enough (if we don't do this, we
-// won't have cleaned up by the time that keyed services are destroyed).
-void OnAppTerminating() {
-  GlicProfileManager* mgr = GlicProfileManager::GetInstance();
-  if (!mgr) {
-    return;
-  }
-  mgr->CloseGlicWindow();
-}
-
-}  // namespace
-
 GlicProfileManager* GlicProfileManager::GetInstance() {
   return g_browser_process->GetFeatures()->glic_profile_manager();
 }
@@ -52,9 +38,7 @@
   active_glic_ = glic->GetWeakPtr();
 }
 
-GlicProfileManager::GlicProfileManager()
-    : termination_subscription_(browser_shutdown::AddAppTerminatingCallback(
-          base::BindOnce(&OnAppTerminating))) {}
+GlicProfileManager::GlicProfileManager() = default;
 
 GlicProfileManager::~GlicProfileManager() = default;
 
diff --git a/chrome/browser/glic/glic_profile_manager.h b/chrome/browser/glic/glic_profile_manager.h
index 3439002..b9722a1c 100644
--- a/chrome/browser/glic/glic_profile_manager.h
+++ b/chrome/browser/glic/glic_profile_manager.h
@@ -39,7 +39,6 @@
 
  private:
   base::WeakPtr<GlicKeyedService> active_glic_;
-  base::CallbackListSubscription termination_subscription_;
 };
 }  // namespace glic
 
diff --git a/chrome/browser/glic/glic_view.cc b/chrome/browser/glic/glic_view.cc
index 3ead4ed6..477e0c08 100644
--- a/chrome/browser/glic/glic_view.cc
+++ b/chrome/browser/glic/glic_view.cc
@@ -5,9 +5,6 @@
 #include "chrome/browser/glic/glic_view.h"
 
 #include "base/command_line.h"
-#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
-#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
-#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
@@ -28,15 +25,10 @@
 namespace glic {
 
 GlicView::GlicView(Profile* profile, const gfx::Size& initial_size) {
-  profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
-      profile, ProfileKeepAliveOrigin::kGlicView);
   SetProperty(views::kElementIdentifierKey, kGlicViewElementId);
   auto web_view = std::make_unique<views::WebView>(profile);
   web_view_ = web_view.get();
   web_view->SetSize(initial_size);
-  web_view->LoadInitialURL(GURL("chrome://glic"));
-  web_view->GetWebContents()->SetPageBaseBackgroundColor(SK_ColorTRANSPARENT);
-  web_view->GetWebContents()->SetDelegate(this);
   AddChildView(std::move(web_view));
 }
 
@@ -90,17 +82,4 @@
   bounds_change_animation_->Start();
 }
 
-bool GlicView::HandleKeyboardEvent(content::WebContents* source,
-                                   const input::NativeWebKeyboardEvent& event) {
-  return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
-      event, web_view()->GetFocusManager());
-}
-
-void GlicView::RequestMediaAccessPermission(
-    content::WebContents* web_contents,
-    const content::MediaStreamRequest& request,
-    content::MediaResponseCallback callback) {
-  MediaCaptureDevicesDispatcher::GetInstance()->ProcessMediaAccessRequest(
-      web_contents, request, std::move(callback), nullptr);
-}
 }  // namespace glic
diff --git a/chrome/browser/glic/glic_view.h b/chrome/browser/glic/glic_view.h
index 114f005..a641d11 100644
--- a/chrome/browser/glic/glic_view.h
+++ b/chrome/browser/glic/glic_view.h
@@ -5,9 +5,7 @@
 #ifndef CHROME_BROWSER_GLIC_GLIC_VIEW_H_
 #define CHROME_BROWSER_GLIC_GLIC_VIEW_H_
 
-#include "content/public/browser/web_contents_delegate.h"
 #include "ui/gfx/geometry/size.h"
-#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/unique_widget_ptr.h"
 
@@ -21,11 +19,10 @@
 
 class BrowserFrameBoundsChangeAnimation;
 class Profile;
-class ScopedProfileKeepAlive;
 
 namespace glic {
 
-class GlicView : public views::View, public content::WebContentsDelegate {
+class GlicView : public views::View {
  public:
   GlicView(Profile* profile, const gfx::Size& initial_size);
   GlicView(const GlicView&) = delete;
@@ -51,16 +48,7 @@
   void AnimateFrameBounds(const gfx::Rect& bounds);
 
  private:
-  // content::WebContentsDelegate:
-  bool HandleKeyboardEvent(content::WebContents* source,
-                           const input::NativeWebKeyboardEvent& event) override;
-  void RequestMediaAccessPermission(
-      content::WebContents* web_contents,
-      const content::MediaStreamRequest& request,
-      content::MediaResponseCallback callback) override;
-
   raw_ptr<views::WebView> web_view_;
-  views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
 
   // Defines the areas of the view from which it can be dragged. These areas can
   // be updated by the glic web client.
@@ -69,10 +57,6 @@
   // Animates programmatic changes to bounds (e.g. via `resizeTo()`
   // `resizeBy()` and `setContentsSize()` calls).
   std::unique_ptr<BrowserFrameBoundsChangeAnimation> bounds_change_animation_;
-
-  // Ensures that the profile associated with this view isn't destroyed while
-  // it is visible.
-  std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;
 };
 
 }  // namespace glic
diff --git a/chrome/browser/glic/glic_window_controller.cc b/chrome/browser/glic/glic_window_controller.cc
index 62394cc..ea5da1ae 100644
--- a/chrome/browser/glic/glic_window_controller.cc
+++ b/chrome/browser/glic/glic_window_controller.cc
@@ -7,6 +7,9 @@
 #include "base/check.h"
 #include "chrome/browser/glic/glic_view.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"
+#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -16,13 +19,17 @@
 #include "chrome/browser/ui/views/tabs/glic_button.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_action_container.h"
 #include "chrome/browser/ui/webui/glic/glic.mojom.h"
+#include "chrome/common/webui_url_constants.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
 #include "ui/display/screen.h"
 #include "ui/events/event_observer.h"
+#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/views/event_monitor.h"
 #include "ui/views/widget/widget_observer.h"
 
+namespace glic {
 namespace {
 // Default value for how close the top-right corner of the glic window must be
 // to a browser's glic button to attach to said browser.
@@ -32,6 +39,57 @@
 constexpr static int kWidgetHeight = 800;
 constexpr static int kWidgetTopBarHeight = 80;
 
+class ContentsAndProfileKeepAlive : public content::WebContentsDelegate {
+ public:
+  ContentsAndProfileKeepAlive(Profile* profile,
+                              GlicWindowController* glic_window_controller)
+      : profile_keep_alive_(profile, ProfileKeepAliveOrigin::kGlicView),
+        web_contents_(content::WebContents::Create(
+            content::WebContents::CreateParams(profile))),
+        glic_window_controller_(glic_window_controller) {
+    DCHECK(web_contents_);
+    web_contents_->SetDelegate(this);
+    web_contents_->SetPageBaseBackgroundColor(SK_ColorTRANSPARENT);
+    web_contents_->GetController().LoadURLWithParams(
+        content::NavigationController::LoadURLParams(
+            GURL{chrome::kChromeUIGlicURL}));
+  }
+
+  ~ContentsAndProfileKeepAlive() override { web_contents_->ClosePage(); }
+
+  ContentsAndProfileKeepAlive(const ContentsAndProfileKeepAlive&) = delete;
+  ContentsAndProfileKeepAlive& operator=(const ContentsAndProfileKeepAlive&) =
+      delete;
+
+  content::WebContents* web_contents() { return web_contents_.get(); }
+
+ private:
+  // content::WebContentsDelegate:
+  bool HandleKeyboardEvent(
+      content::WebContents* source,
+      const input::NativeWebKeyboardEvent& event) override {
+    GlicView* glic_view = glic_window_controller_->GetGlicView();
+    if (!glic_view) {
+      return false;
+    }
+    return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
+        event, glic_view->web_view()->GetFocusManager());
+  }
+  void RequestMediaAccessPermission(
+      content::WebContents* web_contents,
+      const content::MediaStreamRequest& request,
+      content::MediaResponseCallback callback) override {
+    MediaCaptureDevicesDispatcher::GetInstance()->ProcessMediaAccessRequest(
+        web_contents, request, std::move(callback), nullptr);
+  }
+
+  ScopedProfileKeepAlive profile_keep_alive_;
+  std::unique_ptr<content::WebContents> web_contents_;
+  views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
+  // Unowned
+  raw_ptr<GlicWindowController> glic_window_controller_;
+};
+
 // Helper class for observing mouse and key events from native window.
 class WindowEventObserver : public ui::EventObserver {
  public:
@@ -86,8 +144,6 @@
 
 }  // namespace
 
-namespace glic {
-
 GlicWindowController::GlicWindowController(Profile* profile)
     : profile_(profile) {}
 
@@ -124,6 +180,13 @@
   glic_window_widget_ = glic::GlicView::CreateWidget(
       profile_, {top_right_point.x() - kWidgetWidth - padding,
                  top_right_point.y() + padding, kWidgetWidth, kWidgetHeight});
+
+  GlicView* glic_view = GlicView::FromWidget(*glic_window_widget_);
+  if (!contents_) {
+    contents_ = std::make_unique<ContentsAndProfileKeepAlive>(profile_, this);
+  }
+  glic_view->web_view()->SetWebContents(contents_->web_contents());
+
   glic_window_widget_->AddObserver(this);
   glic_widget_observer_ =
       std::make_unique<GlicWidgetObserver>(this, glic_window_widget_.get());
@@ -532,6 +595,12 @@
   }
 }
 
+void GlicWindowController::Shutdown() {
+  // Hide first, then clean up.
+  Close();
+  contents_.reset();
+}
+
 GlicWindowController::GlicWidgetObserver::~GlicWidgetObserver() {
   if (widget_ && widget_->HasObserver(this)) {
     widget_->RemoveObserver(this);
diff --git a/chrome/browser/glic/glic_window_controller.h b/chrome/browser/glic/glic_window_controller.h
index fd5d0774..4858481 100644
--- a/chrome/browser/glic/glic_window_controller.h
+++ b/chrome/browser/glic/glic_window_controller.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/browser/ui/webui/glic/glic.mojom.h"
+#include "content/public/browser/web_contents.h"
 #include "ui/views/widget/unique_widget_ptr.h"
 
 class Browser;
@@ -20,12 +21,13 @@
 class Point;
 }  // namespace gfx
 
+namespace glic {
 namespace {
+class ContentsAndProfileKeepAlive;
 class GlicWidgetObserver;
 class WindowEventObserver;
 }  // namespace
 
-namespace glic {
 class GlicView;
 
 // Class for Glic window controller. Owned by the Glic profile keyed-service.
@@ -55,6 +57,9 @@
   // display.
   void Detach();
 
+  // Destroy the glic panel and its web contents.
+  void Shutdown();
+
   // Sets the size of the glic window to the specified dimensions. Returns true
   // if the operation succeeded.
   bool Resize(const gfx::Size& size);
@@ -65,7 +70,7 @@
   // Sets the areas of the view from which it should be draggable.
   void SetDraggableAreas(const std::vector<gfx::Rect>& draggable_areas);
 
-  // Called to notify the controller that the window was requested to be closed.
+  // Close the panel but keep the glic WebContents alive in the background.
   void Close();
 
   // Sets the audio ducking status.  Returns true if the operation succeeded.
@@ -191,6 +196,10 @@
   std::unique_ptr<views::Widget> holder_widget_;
 
   const raw_ptr<Profile> profile_;
+  // Keep profile alive as long as the glic web contents. This object should be
+  // destroyed when the profile needs to be destroyed.
+  std::unique_ptr<ContentsAndProfileKeepAlive> contents_;
+
   std::unique_ptr<views::Widget> glic_window_widget_;
   bool glic_window_widget_visible_ = false;
 
diff --git a/chrome/browser/glic/launcher/glic_background_mode_manager_browsertest.cc b/chrome/browser/glic/launcher/glic_background_mode_manager_browsertest.cc
index 416f80b..7db475df7 100644
--- a/chrome/browser/glic/launcher/glic_background_mode_manager_browsertest.cc
+++ b/chrome/browser/glic/launcher/glic_background_mode_manager_browsertest.cc
@@ -103,7 +103,8 @@
                                                true);
   GlicBackgroundModeManager* const manager =
       g_browser_process->GetFeatures()->glic_background_mode_manager();
-  EXPECT_EQ(ui::Accelerator(), manager->RegisteredHotkeyForTesting());
+  EXPECT_EQ(ui::Accelerator(ui::VKEY_G, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN),
+            manager->RegisteredHotkeyForTesting());
 
   ui::Accelerator updated_hotkey(ui::VKEY_A, ui::EF_SHIFT_DOWN);
   RegisterHotkey(updated_hotkey);
diff --git a/chrome/browser/glic/launcher/glic_launcher_configuration.cc b/chrome/browser/glic/launcher/glic_launcher_configuration.cc
index cbece99..5e66fc4e4 100644
--- a/chrome/browser/glic/launcher/glic_launcher_configuration.cc
+++ b/chrome/browser/glic/launcher/glic_launcher_configuration.cc
@@ -5,8 +5,10 @@
 #include "chrome/browser/glic/launcher/glic_launcher_configuration.h"
 
 #include "base/values.h"
+#include "base/version_info/channel.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/glic/glic_pref_names.h"
+#include "chrome/common/channel_info.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "ui/base/accelerators/accelerator.h"
@@ -36,12 +38,16 @@
 // static
 void GlicLauncherConfiguration::RegisterLocalStatePrefs(
     PrefRegistrySimple* registry) {
-  registry->RegisterBooleanPref(prefs::kGlicLauncherEnabled, false);
+  // TODO(crbug.com/379166397): Set the default value to false when FRE is
+  // implemented.
+  registry->RegisterBooleanPref(
+      prefs::kGlicLauncherEnabled,
+      chrome::GetChannel() == version_info::Channel::CANARY);
   registry->RegisterDictionaryPref(
       prefs::kGlicLauncherGlobalHotkey,
       base::Value::Dict()
-          .Set(kHotkeyKeyCode, ui::KeyboardCode::VKEY_UNKNOWN)
-          .Set(kHotkeyModifiers, ui::EF_NONE));
+          .Set(kHotkeyKeyCode, ui::KeyboardCode::VKEY_G)
+          .Set(kHotkeyModifiers, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN));
 }
 
 bool GlicLauncherConfiguration::IsEnabled() {
diff --git a/chrome/browser/glic/launcher/glic_status_icon.cc b/chrome/browser/glic/launcher/glic_status_icon.cc
index 5918c9e..9da6612 100644
--- a/chrome/browser/glic/launcher/glic_status_icon.cc
+++ b/chrome/browser/glic/launcher/glic_status_icon.cc
@@ -31,17 +31,16 @@
     : controller_(controller), status_tray_(status_tray) {
   // TODO(crbug.com/382287104): Use correct icon.
   // TODO(crbug.com/386839488): Chose color based on system theme.
-  gfx::ImageSkia status_tray_icon = gfx::CreateVectorIcon(kGlicButtonIcon,
-#if BUILDFLAG(IS_LINUX)
-                                                          SK_ColorWHITE
-#else
-                                                          SK_ColorBLACK
-#endif
-  );
+  gfx::ImageSkia status_tray_icon =
+      gfx::CreateVectorIcon(kGlicButtonIcon, SK_ColorBLACK);
 
   status_icon_ = status_tray_->CreateStatusIcon(
       StatusTray::GLIC_ICON, status_tray_icon,
       l10n_util::GetStringUTF16(IDS_GLIC_STATUS_ICON_TOOLTIP));
+#if BUILDFLAG(IS_LINUX)
+  //  Set a vector icon for proper themeing on Linux.
+  status_icon_->SetIcon(kGlicButtonIcon);
+#endif
 #if BUILDFLAG(IS_MAC)
   if (features::kGlicStatusIconOpenMenuWithSecondaryClick.Get()) {
     status_icon_->SetOpenMenuWithSecondaryClick(true);
diff --git a/chrome/browser/history/java/OWNERS b/chrome/browser/history/java/OWNERS
index 0edc952..932e9e4 100644
--- a/chrome/browser/history/java/OWNERS
+++ b/chrome/browser/history/java/OWNERS
@@ -1,2 +1 @@
 jinsukkim@chromium.org
-katzz@google.com
diff --git a/chrome/browser/metrics/chrome_metrics_service_accessor.h b/chrome/browser/metrics/chrome_metrics_service_accessor.h
index 25ae21fa..6ef032b 100644
--- a/chrome/browser/metrics/chrome_metrics_service_accessor.h
+++ b/chrome/browser/metrics/chrome_metrics_service_accessor.h
@@ -180,6 +180,7 @@
   friend class feed::WebFeedSubscriptionCoordinator;
   friend class HttpsFirstModeService;
   friend class ash::DemoSession;
+  friend class DataSharingUI;
   // Used to register synthetic trials for ongoing growth experiments.
   friend class CampaignsManagerClientImpl;
   friend class tpcd::experiment::ExperimentManagerImpl;
diff --git a/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
index 48adf1e..c8e29bdb 100644
--- a/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
+++ b/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
@@ -102,7 +102,8 @@
 
 }  // namespace
 
-IN_PROC_BROWSER_TEST_F(MetricIntegrationTest, LargestContentfulPaint) {
+// TODO(crbug.com/385580803): Flaky on all platforms
+IN_PROC_BROWSER_TEST_F(MetricIntegrationTest, DISABLED_LargestContentfulPaint) {
   auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
       web_contents());
   Start();
@@ -229,6 +230,7 @@
 }
 
 // TODO(crbug.com/40936591): This test is flaky on ChromeOS and Linux.
+// TODO(crbug.com/382573509): and flaky on Windows.
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
 #define MAYBE_LargestContentfulPaint_SubframeInput \
   DISABLED_LargestContentfulPaint_SubframeInput
@@ -368,7 +370,8 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(PageViewportInLCPTest, FullSizeImageInIframe) {
+// TODO(crbug.com/385580803): flaky on all platforms
+IN_PROC_BROWSER_TEST_F(PageViewportInLCPTest, DISABLED_FullSizeImageInIframe) {
   auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
       web_contents());
   waiter->AddSubFrameExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
@@ -1020,7 +1023,8 @@
   double epsilon_ = 8;
 };
 
-IN_PROC_BROWSER_TEST_F(LcpBreakdownTimingsTest, Subframe) {
+// TODO(crbug.com/385392162): flaky on all platforms
+IN_PROC_BROWSER_TEST_F(LcpBreakdownTimingsTest, DISABLED_Subframe) {
   std::string url = "/lcp_breakdown_timings_with_subframe.html";
   auto* resource_name = "lcp-256x256.png";
   RunTest(url, resource_name, 0, "addSameSiteSubframe()");
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
index 768c3ec..46225d4 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -3868,8 +3868,8 @@
     : public PageLoadMetricsBrowserTestTerminatedPage,
       public ::testing::WithParamInterface<const char*> {};
 
-// TODO(crbug.com/40280758): Very flaky on Lacros.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/376032466): flaky on Linux/Windows.
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
 #define MAYBE_UkmIsRecordedForCrashedTabPage \
   DISABLED_UkmIsRecordedForCrashedTabPage
 #else
diff --git a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java
index 85d0251..c6747ea 100644
--- a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java
+++ b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java
@@ -408,6 +408,7 @@
     @SmallTest
     @Feature({"RenderTest"})
     @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS)
+    @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_EQUALIZED_PROMPT_BUTTONS)
     public void testRenderEeaNoticeV2AdMeasurementDropdown() throws IOException {
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
@@ -457,6 +458,7 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS)
+    @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_EQUALIZED_PROMPT_BUTTONS)
     public void testEeaNoticeV2AckButton() throws IOException {
         mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_EEA);
         launchDialog();
@@ -881,7 +883,10 @@
 
     @Test
     @SmallTest
-    @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS)
+    @DisableFeatures({
+        ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS,
+        ChromeFeatureList.PRIVACY_SANDBOX_EQUALIZED_PROMPT_BUTTONS
+    })
     public void testControllerShowsEEANotice() throws IOException {
         mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_EEA);
         launchDialog();
diff --git a/chrome/browser/resources/ash/settings/lazy_load.ts b/chrome/browser/resources/ash/settings/lazy_load.ts
index ed6a517..804124e3 100644
--- a/chrome/browser/resources/ash/settings/lazy_load.ts
+++ b/chrome/browser/resources/ash/settings/lazy_load.ts
@@ -70,6 +70,12 @@
 import './os_files_page/office_page.js';
 import './os_files_page/one_drive_subpage.js';
 import './os_files_page/smb_shares_page.js';
+import './os_languages_page/app_languages_page.js';
+import './os_languages_page/input_method_options_page.js';
+import './os_languages_page/input_page.js';
+import './os_languages_page/os_edit_dictionary_page.js';
+import './os_languages_page/os_japanese_manage_user_dictionary_page.js';
+import './os_languages_page/os_languages_page_v2.js';
 import './os_search_page/google_assistant_subpage.js';
 import './os_search_page/search_subpage.js';
 import './os_people_page/fingerprint_list_subpage.js';
@@ -99,26 +105,11 @@
 import './crostini_page/crostini_port_forwarding_add_port_dialog.js';
 import './crostini_page/crostini_shared_usb_devices.js';
 import './crostini_page/crostini_subpage.js';
-import './date_time_page/timezone_selector.js';
 import './guest_os/guest_os_confirmation_dialog.js';
 import './guest_os/guest_os_container_select.js';
 import './guest_os/guest_os_shared_paths.js';
 import './guest_os/guest_os_shared_usb_devices.js';
 import './guest_os/guest_os_shared_usb_devices_add_dialog.js';
-import './keyboard_shortcut_banner/keyboard_shortcut_banner.js';
-import './os_languages_page/app_languages_page.js';
-import './os_languages_page/input_method_options_page.js';
-import './os_languages_page/input_page.js';
-import './os_languages_page/os_edit_dictionary_page.js';
-import './os_languages_page/os_japanese_manage_user_dictionary_page.js';
-import './os_languages_page/os_languages_page_v2.js';
-import './os_people_page/os_personalization_options.js';
-import './os_privacy_page/secure_dns.js';
-import './os_privacy_page/secure_dns_input.js';
-import './os_reset_page/os_powerwash_dialog.js';
-import './os_reset_page/os_powerwash_dialog_esim_item.js';
-import './os_reset_page/os_sanitize_dialog.js';
-import './os_search_page/magic_boost_review_terms_banner.js';
 
 export {ScreenAiInstallStatus} from '/shared/settings/a11y_page/ax_annotations_browser_proxy.js';
 export {CaptionsBrowserProxy, CaptionsBrowserProxyImpl, LiveCaptionLanguage, LiveCaptionLanguageList} from '/shared/settings/a11y_page/captions_browser_proxy.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/braille_ime/mv3/manifest.json b/chrome/browser/resources/chromeos/accessibility/braille_ime/mv3/manifest.json
new file mode 100644
index 0000000..053a37f
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/braille_ime/mv3/manifest.json
@@ -0,0 +1,24 @@
+{
+  "name": "Braille IME",
+  "description": "Braille Input Method Extension.",
+  "version": "1.0",
+  "background": {
+    "service_worker": "main.js",
+    "type": "module"
+  },
+  // chrome-extension://jddehjeebkoimngcbdkaahpobgicbffp
+  "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvDjqqYESDQe3OcI65JctUYLSlQ7RAd902VUw+RO/70fJ7SSkg8+2y+5paD6+g8f6wgFsgVsbTX2UM+tsmGKWR23bgSQxYhfZUZgP7qFdk72hGRUnKnXA+JOJ5maI4v+w18WPTWYOFJt2NOvat+GKKF0CAFQG+z2Ucn/sRZVfnrQIDAQAB",
+  "manifest_version": 3,
+  "permissions": [
+    "input"
+  ],
+  "ime_path": "chromeos/accessibility/braille_ime",
+  "input_components": [
+    {
+      "name": "Braille Keyboard",
+      "id": "braille",
+      "indicator": "\u2803\u2817\u2807",  // Unicode of 'brl' in English (and many other) braille codes.
+      "language": ["None"]
+    }
+  ]
+}
diff --git a/chrome/browser/resources/chromeos/emoji_picker/store.ts b/chrome/browser/resources/chromeos/emoji_picker/store.ts
index 5d6396f..9fef5ac5 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/store.ts
+++ b/chrome/browser/resources/chromeos/emoji_picker/store.ts
@@ -94,7 +94,7 @@
     const mergedHistory: EmojiHistoryItem[] =
         prefsHistory.history.map((item) => ({
                                    base: {string: item.emoji},
-                                   timestamp: item.timestamp.msec,
+                                   timestamp: item.timestamp.getTime(),
                                    alternates: [],
                                  }));
     for (const item of this.store.data.history) {
@@ -244,9 +244,7 @@
               .map((x) => ({
                      // Explicit cast here is safe due to filter above.
                      emoji: x.base.string!,
-                     timestamp: {
-                       msec: x.timestamp || 0,
-                     },
+                     timestamp: new Date(x.timestamp || 0),
                    })));
     }
   }
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index 8a234cc..7c8bac0 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -26,6 +26,8 @@
         <!-- Compiled TS -->
         <include name="IDR_BRAILLE_IME_JS" file="${root_gen_dir}/chrome/browser/resources/chromeos/accessibility/braille_ime/tsc/braille_ime.js" use_base_dir="false" resource_path="chromeos/accessibility/braille_ime/braille_ime.js" type="BINDATA" />
          <include name="IDR_BRAILLE_IME_MAIN_JS" file="${root_gen_dir}/chrome/browser/resources/chromeos/accessibility/braille_ime/tsc/main.js" use_base_dir="false" resource_path="chromeos/accessibility/braille_ime/main.js" type="BINDATA" />
+        <include name="IDR_BRAILLE_IME_MV3_JS" file="${root_gen_dir}/chrome/browser/resources/chromeos/accessibility/braille_ime/tsc/braille_ime.js" use_base_dir="false" resource_path="chromeos/accessibility/braille_ime/mv3/braille_ime.js" type="BINDATA" />
+         <include name="IDR_BRAILLE_IME_MV3_MAIN_JS" file="${root_gen_dir}/chrome/browser/resources/chromeos/accessibility/braille_ime/tsc/main.js" use_base_dir="false" resource_path="chromeos/accessibility/braille_ime/mv3/main.js" type="BINDATA" />
       </if>
 
       <!-- Hangout Services extension, included in Google Chrome builds only. -->
diff --git a/chrome/browser/resources/data_sharing/data_sharing_api.ts b/chrome/browser/resources/data_sharing/data_sharing_api.ts
index 2e19099..07361ce 100644
--- a/chrome/browser/resources/data_sharing/data_sharing_api.ts
+++ b/chrome/browser/resources/data_sharing/data_sharing_api.ts
@@ -7,9 +7,12 @@
 // </if>
 // <if expr="not _google_chrome">
 import './dummy_data_sharing_sdk.js';
-
 // </if>
 
+import '/strings.m.js';
+
+import {loadTimeData} from 'chrome-untrusted://resources/js/load_time_data.js';
+
 import type {BrowserProxy} from './browser_proxy.js';
 import {BrowserProxyImpl} from './browser_proxy.js';
 import type {DataSharingSdk} from './data_sharing_sdk_types.js';
@@ -22,6 +25,8 @@
 
 const browserProxy: BrowserProxy = BrowserProxyImpl.getInstance();
 
+dataSharingSdk.updateClearcut(
+    {enabled: loadTimeData.getBoolean('metricsReportingEnabled')});
 browserProxy.callbackRouter.onAccessTokenFetched.addListener(
     (accessToken: string) => {
       dataSharingSdk.setOauthAccessToken({accessToken});
diff --git a/chrome/browser/resources/data_sharing/data_sharing_app.ts b/chrome/browser/resources/data_sharing/data_sharing_app.ts
index 0f2f833e..f058af5a 100644
--- a/chrome/browser/resources/data_sharing/data_sharing_app.ts
+++ b/chrome/browser/resources/data_sharing/data_sharing_app.ts
@@ -244,6 +244,8 @@
 
   constructor() {
     super();
+    this.dataSharingSdk_.updateClearcut(
+        {enabled: loadTimeData.getBoolean('metricsReportingEnabled')});
     this.browserProxy_.callbackRouter.onAccessTokenFetched.addListener(
         (accessToken: string) => {
           this.dataSharingSdk_.setOauthAccessToken({accessToken});
diff --git a/chrome/browser/resources/glic/glic_api/glic_api.ts b/chrome/browser/resources/glic/glic_api/glic_api.ts
index a1df2b11..ef16ee2f 100644
--- a/chrome/browser/resources/glic/glic_api/glic_api.ts
+++ b/chrome/browser/resources/glic/glic_api/glic_api.ts
@@ -107,6 +107,14 @@
   // Requests the closing of the panel containing the web client.
   closePanel?(): Promise<void>;
 
+  // Requests that the web client's panel be attached/docked to a browser
+  // window.
+  attachPanel?(): void;
+
+  // Requests that the web client's panel be detached/undocked from a browser
+  // window (floats free).
+  detachPanel?(): void;
+
   // Triggers the change profile flow, which allows the user to switch which
   // profile is used. If a new profile is chosen, this web client will be
   // closed in favor of a new one.
diff --git a/chrome/browser/resources/glic/glic_api_impl/glic_api_client.ts b/chrome/browser/resources/glic/glic_api_impl/glic_api_client.ts
index 3dac8d7..65e5735 100644
--- a/chrome/browser/resources/glic/glic_api_impl/glic_api_client.ts
+++ b/chrome/browser/resources/glic/glic_api_impl/glic_api_client.ts
@@ -170,6 +170,14 @@
     return this.sender.requestWithResponse('glicBrowserClosePanel', {});
   }
 
+  attachPanel(): void {
+    return this.sender.requestNoResponse('glicBrowserAttachPanel', {});
+  }
+
+  detachPanel(): void {
+    return this.sender.requestNoResponse('glicBrowserDetachPanel', {});
+  }
+
   async getContextFromFocusedTab(options: {
     innerText?: boolean|undefined,
     viewportScreenshot?: boolean|undefined,
diff --git a/chrome/browser/resources/glic/glic_api_impl/glic_api_host.ts b/chrome/browser/resources/glic/glic_api_impl/glic_api_host.ts
index 4b8660a..d60fb190 100644
--- a/chrome/browser/resources/glic/glic_api_impl/glic_api_host.ts
+++ b/chrome/browser/resources/glic/glic_api_impl/glic_api_host.ts
@@ -153,6 +153,14 @@
     return this.handler.closePanel();
   }
 
+  glicBrowserAttachPanel(): void {
+    this.handler.attachPanel();
+  }
+
+  glicBrowserDetachPanel(): void {
+    this.handler.detachPanel();
+  }
+
   async glicBrowserGetContextFromFocusedTab(
       request: {
         options: {innerText?: boolean, viewportScreenshot?: boolean},
diff --git a/chrome/browser/resources/glic/glic_api_impl/request_types.ts b/chrome/browser/resources/glic/glic_api_impl/request_types.ts
index 35b3c013..89a9d8c 100644
--- a/chrome/browser/resources/glic/glic_api_impl/request_types.ts
+++ b/chrome/browser/resources/glic/glic_api_impl/request_types.ts
@@ -43,6 +43,9 @@
     request: {},
     response: void,
   };
+
+  // The messages that fullfill the GlicBrowserHost public API follow below.
+
   glicBrowserCreateTab: {
     request: {
       url: string,
@@ -130,6 +133,14 @@
       success: boolean,
     },
   };
+  glicBrowserAttachPanel: {
+    request: {},
+    response: void,
+  };
+  glicBrowserDetachPanel: {
+    request: {},
+    response: void,
+  };
 }
 
 // Types of requests to the GlicWebClient.
diff --git a/chrome/browser/resources/whats_new/whats_new_app.ts b/chrome/browser/resources/whats_new/whats_new_app.ts
index 02ec93d6..764778c 100644
--- a/chrome/browser/resources/whats_new/whats_new_app.ts
+++ b/chrome/browser/resources/whats_new/whats_new_app.ts
@@ -11,7 +11,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {isChromeOS} from 'chrome://resources/js/platform.js';
 import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
-import type {JSTime, TimeDelta} from 'chrome://resources/mojo/mojo/public/mojom/base/time.mojom-webui.js';
+import type {TimeDelta} from 'chrome://resources/mojo/mojo/public/mojom/base/time.mojom-webui.js';
 import type {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
 
 import {ModulePosition, ScrollDepth} from './whats_new.mojom-webui.js';
@@ -204,7 +204,7 @@
 
 function handlePageLoadMetric(data: PageLoadedMetric, isAutoOpen: boolean) {
   const {handler} = WhatsNewProxyImpl.getInstance();
-  const now: JSTime = {msec: Date.now()};
+  const now = new Date();
   handler.recordTimeToLoadContent(now);
 
   // Record initial scroll depth as 0%.
diff --git a/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc b/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
index 10bcea4d..1d9e200 100644
--- a/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
+++ b/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
@@ -464,6 +464,8 @@
   resource_request->url = PPAPIDownloadRequest::GetDownloadRequestUrl();
   resource_request->method = "POST";
   resource_request->load_flags = net::LOAD_DISABLE_CACHE;
+  resource_request->site_for_cookies =
+      net::SiteForCookies::FromUrl(resource_request->url);
 
   if (!access_token_.empty()) {
     LogAuthenticatedCookieResets(
diff --git a/chrome/browser/ui/android/ephemeraltab/java/src/org/chromium/chrome/browser/ephemeraltab/EphemeralTabMediator.java b/chrome/browser/ui/android/ephemeraltab/java/src/org/chromium/chrome/browser/ephemeraltab/EphemeralTabMediator.java
index b64a431..094bd89 100644
--- a/chrome/browser/ui/android/ephemeraltab/java/src/org/chromium/chrome/browser/ephemeraltab/EphemeralTabMediator.java
+++ b/chrome/browser/ui/android/ephemeraltab/java/src/org/chromium/chrome/browser/ephemeraltab/EphemeralTabMediator.java
@@ -173,7 +173,7 @@
                     public void didFinishNavigationInPrimaryMainFrame(NavigationHandle navigation) {
                         if (navigation.hasCommitted()) {
                             mIsOnErrorPage = navigation.isErrorPage();
-                            mSheetContent.updateURL(mWebContents.get().getVisibleUrl());
+                            mSheetContent.updateURL(getWebContents().getVisibleUrl());
                         } else if (navigation.isDownload()) {
                             // Not viewable contents such as download. Show a toast and close the
                             // tab.
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java
index 34aa2299..1ab86fa 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java
@@ -239,7 +239,7 @@
          *     navigating to is actually a SRP.
          */
         private void setReceivedUserGesture(GURL url) {
-            WebContents webContents = mWebContents.get();
+            WebContents webContents = getWebContents();
             if (webContents == null) return;
 
             RenderFrameHost renderFrameHost = webContents.getMainFrame();
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index f6a03cfe..ec7ec8b 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -1472,6 +1472,9 @@
   // Compare commands.
   command_updater_.UpdateCommandEnabled(IDC_COMPARE_MENU, true);
   command_updater_.UpdateCommandEnabled(IDC_SHOW_ALL_COMPARISON_TABLES, true);
+  command_updater_.UpdateCommandEnabled(IDC_ADD_TO_COMPARISON_TABLE_MENU, true);
+  command_updater_.UpdateCommandEnabled(
+      IDC_CREATE_NEW_COMPARISON_TABLE_WITH_TAB, true);
 
   // Initialize other commands whose state changes based on various conditions.
   UpdateCommandsForFullscreenMode();
@@ -1668,6 +1671,12 @@
   // Update the zoom commands when an active tab is selected.
   UpdateCommandsForZoomState();
   UpdateCommandsForTabKeyboardFocus(GetKeyboardFocusedTabIndex(browser_));
+
+  // Disable the add to comparison table menu when the page is not a standard
+  // webpage.
+  const GURL& current_url = current_web_contents->GetLastCommittedURL();
+  command_updater_.UpdateCommandEnabled(IDC_ADD_TO_COMPARISON_TABLE_MENU,
+                                        current_url.SchemeIsHTTPOrHTTPS());
 }
 
 void BrowserCommandController::UpdateCommandsForZoomState() {
diff --git a/chrome/browser/ui/browser_command_controller_browsertest.cc b/chrome/browser/ui/browser_command_controller_browsertest.cc
index d031cb9f..67567d4e 100644
--- a/chrome/browser/ui/browser_command_controller_browsertest.cc
+++ b/chrome/browser/ui/browser_command_controller_browsertest.cc
@@ -37,6 +37,7 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_ui.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/commerce/core/commerce_feature_list.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/sessions/core/tab_restore_service.h"
 #include "components/sessions/core/tab_restore_service_observer.h"
@@ -613,4 +614,52 @@
 
 #endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
 
+// Tests for comparison table submenu.
+class BrowserCommandControllerBrowserTestCompare
+    : public BrowserCommandControllerBrowserTest {
+ public:
+  BrowserCommandControllerBrowserTestCompare() {
+    scoped_feature_list_.InitWithFeatures(
+        {commerce::kProductSpecifications,
+         commerce::kCompareManagementInterface},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(BrowserCommandControllerBrowserTestCompare,
+                       AddToTableMenu_UrlSchemeHttp) {
+  GURL url = GURL("http://example.com");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  browser()->command_controller()->TabStateChanged();
+
+  EXPECT_TRUE(browser()->command_controller()->IsCommandEnabled(
+      IDC_ADD_TO_COMPARISON_TABLE_MENU));
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserCommandControllerBrowserTestCompare,
+                       AddToTableMenu_UrlSchemeHttps) {
+  GURL url = GURL("https://example.com");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  browser()->command_controller()->TabStateChanged();
+
+  EXPECT_TRUE(browser()->command_controller()->IsCommandEnabled(
+      IDC_ADD_TO_COMPARISON_TABLE_MENU));
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserCommandControllerBrowserTestCompare,
+                       AddToTableMenu_UrlSchemeNotHttpOrHttps) {
+  GURL url = GURL("chrome://history");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  browser()->command_controller()->TabStateChanged();
+
+  EXPECT_FALSE(browser()->command_controller()->IsCommandEnabled(
+      IDC_ADD_TO_COMPARISON_TABLE_MENU));
+}
+
 }  // namespace chrome
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 09afd18d76..af0e122 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -2310,11 +2310,6 @@
 void OpenCommerceProductSpecificationsTab(Browser* browser,
                                           const std::vector<GURL>& urls,
                                           const int position) {
-  if (static_cast<int>(urls.size()) <
-      commerce::kProductSpecificationsMinTabsCount) {
-    return;
-  }
-
   auto* prefs = browser->profile()->GetPrefs();
   // If user has not accepted the latest disclosure, show the disclosure dialog
   // first.
diff --git a/chrome/browser/ui/browser_element_identifiers.cc b/chrome/browser/ui/browser_element_identifiers.cc
index 687e3cb..e4e45d7 100644
--- a/chrome/browser/ui/browser_element_identifiers.cc
+++ b/chrome/browser/ui/browser_element_identifiers.cc
@@ -100,6 +100,7 @@
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kTabElementId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleCloseGroupButtonId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleDeleteGroupButtonId);
+DEFINE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleLeaveGroupButtonId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleManageSharedGroupButtonId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleShareGroupButtonId);
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleId);
diff --git a/chrome/browser/ui/browser_element_identifiers.h b/chrome/browser/ui/browser_element_identifiers.h
index f850178..abaf9f4 100644
--- a/chrome/browser/ui/browser_element_identifiers.h
+++ b/chrome/browser/ui/browser_element_identifiers.h
@@ -111,6 +111,7 @@
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kTabElementId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleCloseGroupButtonId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleDeleteGroupButtonId);
+DECLARE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleLeaveGroupButtonId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(
     kTabGroupEditorBubbleManageSharedGroupButtonId);
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kTabGroupEditorBubbleShareGroupButtonId);
diff --git a/chrome/browser/ui/commerce/BUILD.gn b/chrome/browser/ui/commerce/BUILD.gn
index cdec15c..8b675bb7 100644
--- a/chrome/browser/ui/commerce/BUILD.gn
+++ b/chrome/browser/ui/commerce/BUILD.gn
@@ -6,6 +6,7 @@
 
 source_set("commerce") {
   sources = [
+    "add_to_comparison_table_sub_menu_model.h",
     "commerce_page_action_controller.h",
     "commerce_ui_tab_helper.h",
     "compare_sub_menu_model.h",
@@ -35,6 +36,7 @@
 
 source_set("impl") {
   sources = [
+    "add_to_comparison_table_sub_menu_model.cc",
     "commerce_page_action_controller.cc",
     "commerce_ui_tab_helper.cc",
     "compare_sub_menu_model.cc",
@@ -50,6 +52,8 @@
     ":commerce",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/ui/browser_window",
+    "//chrome/browser/ui/toasts:toasts",
+    "//chrome/browser/ui/toasts/api:toasts",
     "//chrome/browser/ui/views/side_panel",
     "//chrome/common:constants",
     "//components/bookmarks/browser",
@@ -114,10 +118,14 @@
 source_set("browser_tests") {
   testonly = true
   defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-  sources = [ "product_specifications_entry_point_controller_browsertest.cc" ]
+  sources = [
+    "add_to_comparison_table_sub_menu_model_browsertest.cc",
+    "product_specifications_entry_point_controller_browsertest.cc",
+  ]
   deps = [
     ":commerce",
     "//chrome/browser/ui/browser_window:browser_window",
+    "//chrome/browser/ui/toasts:toasts",
     "//chrome/test:test_support",
     "//components/commerce/core:account_checker_test_support",
     "//components/commerce/core:shopping_service_test_support",
diff --git a/chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model.cc b/chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model.cc
new file mode 100644
index 0000000..0bec0c9
--- /dev/null
+++ b/chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model.cc
@@ -0,0 +1,143 @@
+// 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/ui/commerce/add_to_comparison_table_sub_menu_model.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/commerce/product_specifications/product_specifications_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
+#include "chrome/browser/ui/commerce/commerce_ui_tab_helper.h"
+#include "chrome/browser/ui/tabs/public/tab_features.h"
+#include "chrome/browser/ui/toasts/api/toast_id.h"
+#include "chrome/browser/ui/toasts/toast_controller.h"
+#include "chrome/browser/ui/toolbar/app_menu_model.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/commerce/core/commerce_feature_list.h"
+#include "components/commerce/core/product_specifications/product_specifications_service.h"
+#include "components/strings/grit/components_strings.h"
+#include "url/gurl.h"
+
+namespace commerce {
+
+AddToComparisonTableSubMenuModel::AddToComparisonTableSubMenuModel(
+    Browser* browser,
+    ProductSpecificationsService* product_specs_service)
+    : SimpleMenuModel(this),
+      browser_(browser),
+      product_specs_service_(product_specs_service),
+      next_menu_id_(AppMenuModel::kMinCompareCommandId) {
+  AddItemWithStringId(IDC_CREATE_NEW_COMPARISON_TABLE_WITH_TAB,
+                      IDS_COMPARE_NEW_COMPARISON_TABLE);
+
+  auto sets = product_specs_service_->GetAllProductSpecifications();
+  if (sets.empty()) {
+    return;
+  }
+
+  AddSeparator(ui::NORMAL_SEPARATOR);
+
+  auto& current_url =
+      browser_->GetActiveTabInterface()->GetContents()->GetLastCommittedURL();
+
+  for (const auto& set : sets) {
+    bool contains_current_url = false;
+    for (const auto& url_info : set.url_infos()) {
+      if (url_info.url == current_url) {
+        contains_current_url = true;
+        break;
+      }
+    }
+
+    const int command_id = GetAndIncrementNextMenuId();
+    command_id_to_table_info_[command_id] = {set.uuid(), contains_current_url};
+    AddItem(command_id, base::UTF8ToUTF16(set.name()));
+  }
+}
+
+AddToComparisonTableSubMenuModel::~AddToComparisonTableSubMenuModel() = default;
+
+void AddToComparisonTableSubMenuModel::ExecuteCommand(int command_id,
+                                                      int event_flags) {
+  auto& current_url =
+      browser_->GetActiveTabInterface()->GetContents()->GetLastCommittedURL();
+  if (command_id == IDC_CREATE_NEW_COMPARISON_TABLE_WITH_TAB) {
+    chrome::OpenCommerceProductSpecificationsTab(
+        browser_, {current_url},
+        browser_->GetTabStripModel()->GetIndexOfTab(
+            browser_->GetActiveTabInterface()));
+    return;
+  }
+
+  auto& title = browser_->GetActiveTabInterface()->GetContents()->GetTitle();
+  const UrlInfo url_info(current_url, title);
+
+  const auto table_info_for_uuid = command_id_to_table_info_.find(command_id);
+  AddUrlToSet(url_info, table_info_for_uuid->second.uuid);
+}
+
+bool AddToComparisonTableSubMenuModel::IsCommandIdEnabled(
+    int command_id) const {
+  if (command_id == IDC_CREATE_NEW_COMPARISON_TABLE_WITH_TAB) {
+    return true;
+  }
+
+  // Disable the table option if it already contains the current URL.
+  const auto table_info_for_uuid = command_id_to_table_info_.find(command_id);
+  return !table_info_for_uuid->second.contains_current_url;
+}
+
+int AddToComparisonTableSubMenuModel::GetAndIncrementNextMenuId() {
+  const int current_id = next_menu_id_;
+  next_menu_id_ += AppMenuModel::kNumUnboundedMenuTypes;
+  return current_id;
+}
+
+void AddToComparisonTableSubMenuModel::AddUrlToSet(const UrlInfo& url_info,
+                                                   base::Uuid set_uuid) {
+  std::optional<ProductSpecificationsSet> set =
+      product_specs_service_->GetSetByUuid(set_uuid);
+
+  // If the set was not found, then it was deleted while the menu was open.
+  if (!set.has_value()) {
+    return;
+  }
+
+  const GURL& url = url_info.url;
+  const std::u16string& title = url_info.title;
+
+  std::vector<UrlInfo> existing_url_infos = set->url_infos();
+  auto it = base::ranges::find_if(existing_url_infos,
+                                  [&url](const UrlInfo& query_url_info) {
+                                    return query_url_info.url == url;
+                                  });
+
+  // Add the URL to the set. If it is already in the set (because it was added
+  // after the menu was opened), then we still show the toast.
+  if (it == existing_url_infos.end()) {
+    existing_url_infos.emplace_back(url, title);
+    product_specs_service_->SetUrls(set_uuid, std::move(existing_url_infos));
+  }
+
+  ShowConfirmationToast(base::UTF8ToUTF16(set->name()));
+}
+
+void AddToComparisonTableSubMenuModel::ShowConfirmationToast(
+    std::u16string set_name) {
+  if (base::FeatureList::IsEnabled(kCompareConfirmationToast)) {
+    ToastController* const toast_controller =
+        browser_->GetFeatures().toast_controller();
+    if (toast_controller) {
+      ToastParams params = ToastParams(ToastId::kAddedToComparisonTable);
+
+      params.body_string_replacement_params = {set_name};
+      toast_controller->MaybeShowToast(ToastParams(std::move(params)));
+    }
+  }
+}
+
+}  // namespace commerce
diff --git a/chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model.h b/chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model.h
new file mode 100644
index 0000000..ad4cb9c2
--- /dev/null
+++ b/chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model.h
@@ -0,0 +1,59 @@
+// 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_UI_COMMERCE_ADD_TO_COMPARISON_TABLE_SUB_MENU_MODEL_H_
+#define CHROME_BROWSER_UI_COMMERCE_ADD_TO_COMPARISON_TABLE_SUB_MENU_MODEL_H_
+
+#include "base/uuid.h"
+#include "components/commerce/core/product_specifications/product_specifications_service.h"
+#include "ui/menus/simple_menu_model.h"
+#include "url/gurl.h"
+
+class Browser;
+
+namespace commerce {
+
+class AddToComparisonTableSubMenuModel : public ui::SimpleMenuModel,
+                                         public ui::SimpleMenuModel::Delegate {
+ public:
+  AddToComparisonTableSubMenuModel(
+      Browser* browser,
+      commerce::ProductSpecificationsService* product_specs_service);
+
+  AddToComparisonTableSubMenuModel(const AddToComparisonTableSubMenuModel&) =
+      delete;
+  AddToComparisonTableSubMenuModel& operator=(
+      const AddToComparisonTableSubMenuModel&) = delete;
+
+  ~AddToComparisonTableSubMenuModel() override;
+
+  // ui::SimpleMenuModel::Delegate:
+  void ExecuteCommand(int command_id, int event_flags) override;
+  bool IsCommandIdEnabled(int command_id) const override;
+
+ private:
+  struct TableInfo {
+    // UUID of the table.
+    base::Uuid uuid;
+
+    // Whether the current URL is already in the table.
+    bool contains_current_url;
+  };
+
+  int GetAndIncrementNextMenuId();
+
+  void AddUrlToSet(const UrlInfo& url, base::Uuid set_uuid);
+  void ShowConfirmationToast(std::u16string set_name);
+
+  raw_ptr<Browser> browser_;
+  raw_ptr<ProductSpecificationsService> product_specs_service_;
+
+  int next_menu_id_ = 0;
+
+  std::map<int, TableInfo> command_id_to_table_info_;
+};
+
+}  // namespace commerce
+
+#endif  // CHROME_BROWSER_UI_COMMERCE_ADD_TO_COMPARISON_TABLE_SUB_MENU_MODEL_H_
diff --git a/chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model_browsertest.cc b/chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model_browsertest.cc
new file mode 100644
index 0000000..63bd42a5
--- /dev/null
+++ b/chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model_browsertest.cc
@@ -0,0 +1,166 @@
+// 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/ui/commerce/add_to_comparison_table_sub_menu_model.h"
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
+#include "chrome/browser/ui/toasts/toast_controller.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/commerce/core/commerce_utils.h"
+#include "components/commerce/core/mojom/product_specifications.mojom.h"
+#include "components/commerce/core/pref_names.h"
+#include "components/commerce/core/product_specifications/mock_product_specifications_service.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/browser_test.h"
+
+namespace commerce {
+namespace {
+
+const char kTestUrl1[] = "https://example1.com";
+const char kTestUrl2[] = "https://example2.com";
+const char kTestUrl3[] = "https://example3.com";
+
+// One item for "New comparison table", one for the separator, and two for the
+// existing tables.
+const int kFullItemCount = 4;
+
+}  // namespace
+
+class AddToComparisonTableSubMenuModelBrowserTest
+    : public InProcessBrowserTest {
+ public:
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+
+    browser()->profile()->GetPrefs()->SetInteger(
+        commerce::kProductSpecificationsAcceptedDisclosureVersion,
+        static_cast<int>(
+            commerce::product_specifications::mojom::DisclosureVersion::kV1));
+
+    product_specs_service =
+        std::make_unique<MockProductSpecificationsService>();
+    ON_CALL(*product_specs_service, GetAllProductSpecifications())
+        .WillByDefault(testing::Return(kProductSpecsSets));
+  }
+
+  void NavigateToURL(const GURL& url) {
+    ui_test_utils::NavigateToURLWithDisposition(
+        browser(), url, WindowOpenDisposition::CURRENT_TAB,
+        ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+  }
+
+ protected:
+  std::unique_ptr<MockProductSpecificationsService> product_specs_service;
+
+  const std::vector<ProductSpecificationsSet> kProductSpecsSets = {
+      ProductSpecificationsSet(
+          base::Uuid::GenerateRandomV4().AsLowercaseString(),
+          0,
+          0,
+          {
+              GURL(kTestUrl1),
+          },
+          "Set 1"),
+      ProductSpecificationsSet(
+          base::Uuid::GenerateRandomV4().AsLowercaseString(),
+          0,
+          0,
+          {
+              GURL(kTestUrl1),
+              GURL(kTestUrl2),
+          },
+          "Set 2")};
+};
+
+IN_PROC_BROWSER_TEST_F(AddToComparisonTableSubMenuModelBrowserTest,
+                       ContainsMultipleSets) {
+  AddToComparisonTableSubMenuModel sub_menu_model(browser(),
+                                                  product_specs_service.get());
+
+  ASSERT_TRUE(sub_menu_model.GetItemCount() == kFullItemCount);
+
+  ASSERT_TRUE(sub_menu_model.GetLabelAt(2) == u"Set 1");
+  ASSERT_TRUE(sub_menu_model.GetLabelAt(3) == u"Set 2");
+
+  ASSERT_TRUE(
+      sub_menu_model.IsCommandIdEnabled(sub_menu_model.GetCommandIdAt(0)));
+  ASSERT_TRUE(
+      sub_menu_model.IsCommandIdEnabled(sub_menu_model.GetCommandIdAt(2)));
+  ASSERT_TRUE(
+      sub_menu_model.IsCommandIdEnabled(sub_menu_model.GetCommandIdAt(3)));
+}
+
+IN_PROC_BROWSER_TEST_F(AddToComparisonTableSubMenuModelBrowserTest,
+                       ContainsNoSets) {
+  ON_CALL(*product_specs_service, GetAllProductSpecifications())
+      .WillByDefault(testing::Return(std::vector<ProductSpecificationsSet>()));
+
+  AddToComparisonTableSubMenuModel sub_menu_model(browser(),
+                                                  product_specs_service.get());
+
+  // One item for "New comparison table".
+  ASSERT_TRUE(sub_menu_model.GetItemCount() == 1);
+
+  ASSERT_TRUE(
+      sub_menu_model.IsCommandIdEnabled(sub_menu_model.GetCommandIdAt(0)));
+}
+
+IN_PROC_BROWSER_TEST_F(AddToComparisonTableSubMenuModelBrowserTest,
+                       DisablesTablesContainingCurrentUrl) {
+  NavigateToURL(GURL(kTestUrl2));
+
+  AddToComparisonTableSubMenuModel sub_menu_model(browser(),
+                                                  product_specs_service.get());
+
+  ASSERT_TRUE(sub_menu_model.GetItemCount() == kFullItemCount);
+
+  // Set 2 should be disabled since it already contains the current URL.
+  ASSERT_TRUE(
+      sub_menu_model.IsCommandIdEnabled(sub_menu_model.GetCommandIdAt(2)));
+  ASSERT_FALSE(
+      sub_menu_model.IsCommandIdEnabled(sub_menu_model.GetCommandIdAt(3)));
+}
+
+IN_PROC_BROWSER_TEST_F(AddToComparisonTableSubMenuModelBrowserTest,
+                       UrlAddedToNewSet) {
+  NavigateToURL(GURL(kTestUrl1));
+
+  AddToComparisonTableSubMenuModel sub_menu_model(browser(),
+                                                  product_specs_service.get());
+
+  sub_menu_model.ExecuteCommand(sub_menu_model.GetCommandIdAt(0), 0);
+
+  // Check that a new tab was opened to create the table with the URL.
+  ASSERT_TRUE(browser()->tab_strip_model()->count() == 2);
+  ASSERT_TRUE(
+      browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL() ==
+      GetProductSpecsTabUrl({GURL(kTestUrl1)}));
+}
+
+IN_PROC_BROWSER_TEST_F(AddToComparisonTableSubMenuModelBrowserTest,
+                       UrlAddedToExistingSet) {
+  NavigateToURL(GURL(kTestUrl3));
+  const auto& title =
+      browser()->GetActiveTabInterface()->GetContents()->GetTitle();
+
+  AddToComparisonTableSubMenuModel sub_menu_model(browser(),
+                                                  product_specs_service.get());
+
+  ASSERT_TRUE(sub_menu_model.GetItemCount() == kFullItemCount);
+
+  ON_CALL(*product_specs_service, GetSetByUuid(kProductSpecsSets[0].uuid()))
+      .WillByDefault(testing::Return(kProductSpecsSets[0]));
+  EXPECT_CALL(*product_specs_service,
+              SetUrls(kProductSpecsSets[0].uuid(),
+                      testing::ElementsAre(UrlInfo(GURL(kTestUrl1), u""),
+                                           UrlInfo(GURL(kTestUrl3), title))))
+      .Times(1);
+
+  sub_menu_model.ExecuteCommand(sub_menu_model.GetCommandIdAt(2), 0);
+}
+
+}  // namespace commerce
diff --git a/chrome/browser/ui/commerce/compare_sub_menu_model.cc b/chrome/browser/ui/commerce/compare_sub_menu_model.cc
index 3c8eebc..63c3d0a 100644
--- a/chrome/browser/ui/commerce/compare_sub_menu_model.cc
+++ b/chrome/browser/ui/commerce/compare_sub_menu_model.cc
@@ -5,15 +5,31 @@
 #include "chrome/browser/ui/commerce/compare_sub_menu_model.h"
 
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/commerce/product_specifications/product_specifications_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model.h"
+#include "chrome/grit/generated_resources.h"
 #include "components/strings/grit/components_strings.h"
 
 namespace commerce {
 
 CompareSubMenuModel::CompareSubMenuModel(
-    ui::SimpleMenuModel::Delegate* delegate)
+    ui::SimpleMenuModel::Delegate* delegate,
+    Browser* browser)
     : SimpleMenuModel(delegate) {
+  add_to_comparison_table_sub_menu_model_ =
+      std::make_unique<AddToComparisonTableSubMenuModel>(
+          browser, ProductSpecificationsServiceFactory::GetForBrowserContext(
+                       browser->profile()));
+
+  AddSubMenuWithStringId(IDC_ADD_TO_COMPARISON_TABLE_MENU,
+                         IDS_COMPARE_ADD_TAB_TO_COMPARISON_TABLE,
+                         add_to_comparison_table_sub_menu_model_.get());
   AddItemWithStringId(IDC_SHOW_ALL_COMPARISON_TABLES,
                       IDS_COMPARE_SHOW_ALL_COMPARISON_TABLES);
 }
 
+CompareSubMenuModel::~CompareSubMenuModel() = default;
+
 }  // namespace commerce
diff --git a/chrome/browser/ui/commerce/compare_sub_menu_model.h b/chrome/browser/ui/commerce/compare_sub_menu_model.h
index 5b7939ad..a1e758b6 100644
--- a/chrome/browser/ui/commerce/compare_sub_menu_model.h
+++ b/chrome/browser/ui/commerce/compare_sub_menu_model.h
@@ -5,15 +5,24 @@
 #ifndef CHROME_BROWSER_UI_COMMERCE_COMPARE_SUB_MENU_MODEL_H_
 #define CHROME_BROWSER_UI_COMMERCE_COMPARE_SUB_MENU_MODEL_H_
 
+#include "chrome/browser/ui/commerce/add_to_comparison_table_sub_menu_model.h"
 #include "ui/menus/simple_menu_model.h"
 
+class Browser;
+
 namespace commerce {
 
 class CompareSubMenuModel : public ui::SimpleMenuModel {
  public:
-  explicit CompareSubMenuModel(ui::SimpleMenuModel::Delegate* delegate);
+  CompareSubMenuModel(ui::SimpleMenuModel::Delegate* delegate,
+                      Browser* browser);
   CompareSubMenuModel(const CompareSubMenuModel&) = delete;
   CompareSubMenuModel& operator=(const CompareSubMenuModel&) = delete;
+  ~CompareSubMenuModel() override;
+
+ private:
+  std::unique_ptr<AddToComparisonTableSubMenuModel>
+      add_to_comparison_table_sub_menu_model_;
 };
 
 }  // namespace commerce
diff --git a/chrome/browser/ui/commerce/product_specifications_page_action_controller.cc b/chrome/browser/ui/commerce/product_specifications_page_action_controller.cc
index 9a2485d..ee23ed9 100644
--- a/chrome/browser/ui/commerce/product_specifications_page_action_controller.cc
+++ b/chrome/browser/ui/commerce/product_specifications_page_action_controller.cc
@@ -117,14 +117,19 @@
     OnProductSpecificationsSetUpdate(
         const ProductSpecificationsSet& before_set,
         const ProductSpecificationsSet& after_set) {
+  bool is_in_set = base::Contains(after_set.urls(), current_url_);
   if (!product_group_for_page_.has_value()) {
+    if (is_in_set) {
+      most_recent_comparison_table_uuid_for_page_ = after_set.uuid();
+      NotifyHost();
+    }
     return;
   }
-  bool is_in_set = base::Contains(after_set.urls(), current_url_);
   // Hide the page action if the page has been added to a set that is not
   // recommended set.
   if (product_group_for_page_->uuid != after_set.uuid()) {
     if (is_in_set) {
+      most_recent_comparison_table_uuid_for_page_ = after_set.uuid();
       product_group_for_page_ = std::nullopt;
       is_in_recommended_set_ = false;
       NotifyHost();
@@ -212,9 +217,20 @@
 }
 
 GURL ProductSpecificationsPageActionController::GetComparisonTableURL() {
-  CHECK(product_group_for_page_.has_value());
-  return commerce::GetProductSpecsTabUrlForID(
-      product_group_for_page_.value().uuid);
+  CHECK(product_group_for_page_.has_value() ||
+        most_recent_comparison_table_uuid_for_page_.has_value());
+
+  if (most_recent_comparison_table_uuid_for_page_.has_value()) {
+    return commerce::GetProductSpecsTabUrlForID(
+        most_recent_comparison_table_uuid_for_page_.value());
+  }
+
+  if (product_group_for_page_.has_value()) {
+    return commerce::GetProductSpecsTabUrlForID(
+        product_group_for_page_.value().uuid);
+  }
+
+  NOTREACHED();
 }
 
 void ProductSpecificationsPageActionController::HandleProductInfoResponse(
diff --git a/chrome/browser/ui/commerce/product_specifications_page_action_controller.h b/chrome/browser/ui/commerce/product_specifications_page_action_controller.h
index 0af5105f..4b1f8da 100644
--- a/chrome/browser/ui/commerce/product_specifications_page_action_controller.h
+++ b/chrome/browser/ui/commerce/product_specifications_page_action_controller.h
@@ -70,6 +70,9 @@
   // The product group that current page can be added to if available.
   std::optional<ProductGroup> product_group_for_page_;
 
+  // The UUID of the comparison table the page was most recently added to.
+  std::optional<base::Uuid> most_recent_comparison_table_uuid_for_page_;
+
   // A bool to indicate whether the product has been added to the recommended
   // product specifications set. Please note that this will be false for pages
   // that are already in product specifications set on navigation, as there is
diff --git a/chrome/browser/ui/commerce/product_specifications_page_action_controller_unittest.cc b/chrome/browser/ui/commerce/product_specifications_page_action_controller_unittest.cc
index 3613068..e5c66178 100644
--- a/chrome/browser/ui/commerce/product_specifications_page_action_controller_unittest.cc
+++ b/chrome/browser/ui/commerce/product_specifications_page_action_controller_unittest.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/ui/toasts/toast_features.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/commerce/core/commerce_feature_list.h"
+#include "components/commerce/core/commerce_utils.h"
 #include "components/commerce/core/mock_account_checker.h"
 #include "components/commerce/core/mock_cluster_manager.h"
 #include "components/commerce/core/mock_shopping_service.h"
@@ -523,4 +524,32 @@
   }
 }
 
+TEST_F(ProductSpecificationsPageActionControllerUnittest,
+       OnProductSpecificationsSetUpdated_NoProductGroupOrProductInfo) {
+  // Page has no product group or product info.
+  mock_cluster_manager_->SetResponseForGetProductGroupForCandidateProduct(
+      std::nullopt);
+  shopping_service_->SetResponseForGetProductInfoForUrl(std::nullopt);
+
+  controller_->ResetForNewNavigation(GURL(kTestUrl1));
+  base::RunLoop().RunUntilIdle();
+
+  // Create a new set to add the page to.
+  const base::Uuid& uuid = base::Uuid::GenerateRandomV4();
+  ProductSpecificationsSet set1 = ProductSpecificationsSet(
+      uuid.AsLowercaseString(), 0, 0, {GURL(kTestUrl2)}, "Set 1");
+  ProductSpecificationsSet set2 =
+      ProductSpecificationsSet(uuid.AsLowercaseString(), 0, 0,
+                               {GURL(kTestUrl1), GURL(kTestUrl2)}, "Set 2");
+
+  // Notify the controller that the current page has been added to a set.
+  controller_->OnProductSpecificationsSetUpdate(set1, set2);
+  base::RunLoop().RunUntilIdle();
+
+  // Check that the comparison table URL is the one the page was added to, even
+  // though there is no recommended product group.
+  ASSERT_TRUE(controller_->GetComparisonTableURL() ==
+              GetProductSpecsTabUrlForID(uuid));
+}
+
 }  // namespace commerce
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc
index e456a53..28998b0 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -1614,7 +1614,22 @@
   std::move(callback).Run(bytes, lens::MimeType::kPdf, page_count);
 }
 
-void LensOverlayController::GetPartialPdfText(uint32_t page_count) {
+void LensOverlayController::FetchVisiblePageIndexAndGetPartialPdfText(
+    uint32_t page_count) {
+  pdf::PDFDocumentHelper* pdf_helper =
+      pdf::PDFDocumentHelper::MaybeGetForWebContents(tab_->GetContents());
+  if (!pdf_helper) {
+    return;
+  }
+
+  pdf_helper->GetMostVisiblePageIndex(
+      base::BindOnce(&LensOverlayController::GetPartialPdfText,
+                     weak_factory_.GetWeakPtr(), page_count));
+}
+
+void LensOverlayController::GetPartialPdfText(
+    uint32_t page_count,
+    std::optional<uint32_t> visible_page_index) {
   pdf::PDFDocumentHelper* pdf_helper =
       pdf::PDFDocumentHelper::MaybeGetForWebContents(tab_->GetContents());
   if (!pdf_helper ||
@@ -1623,6 +1638,8 @@
     return;
   }
 
+  // TODO(387306854): Add logic to grab page text form the visible page index.
+
   // Fetch the first page of text which will be then recursively fetch following
   // pages.
   initialization_data_->pdf_pages_text_.clear();
@@ -1788,7 +1805,7 @@
   // If the new page is a PDF, fetch the text from the page to be used as early
   // suggest signals.
   if (content_type == lens::MimeType::kPdf) {
-    GetPartialPdfText(page_count.value_or(0));
+    FetchVisiblePageIndexAndGetPartialPdfText(page_count.value_or(0));
   }
 
   lens_overlay_query_controller_->SendPageContentUpdateRequest(
@@ -2025,9 +2042,11 @@
 
   // If PDF content was extracted from the page, fetch the text from the PDF to
   // be used as early suggest signals.
-  if (initialization_data_->page_content_type_ == lens::MimeType::kPdf) {
+  if (initialization_data_->page_content_type_ == lens::MimeType::kPdf &&
+      !initialization_data_->page_content_bytes_.empty()) {
     CHECK(initialization_data_->pdf_page_count_.has_value());
-    GetPartialPdfText(initialization_data_->pdf_page_count_.value());
+    FetchVisiblePageIndexAndGetPartialPdfText(
+        initialization_data_->pdf_page_count_.value());
   }
 
   // If the StartQueryFlow optimization is enabled, the page contents will not
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.h b/chrome/browser/ui/lens/lens_overlay_controller.h
index 87839a81..ccf9b7e5 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.h
+++ b/chrome/browser/ui/lens/lens_overlay_controller.h
@@ -709,15 +709,21 @@
 
 #if BUILDFLAG(ENABLE_PDF)
   // Receives the PDF bytes from the IPC call to the PDF renderer and stores
-  // them in initialization data.
+  // them in initialization data. `pdf_page_count` is passed to the partial PDF
+  // text fetch to be used to determine when to stop fetching.
   void OnPdfBytesReceived(PageContentRetrievedCallback callback,
                           pdf::mojom::PdfListener::GetPdfBytesStatus status,
                           const std::vector<uint8_t>& bytes,
                           uint32_t pdf_page_count);
 
+  // Fetches the visible page index from the PDF renderer and then starts the
+  // process of fetching the text from the PDF to be used for suggest signals.
+  void FetchVisiblePageIndexAndGetPartialPdfText(uint32_t page_count);
+
   // Starts the process of fetching the text from the PDF to be used for suggest
   // signals.
-  void GetPartialPdfText(uint32_t total_page_count);
+  void GetPartialPdfText(uint32_t page_count,
+                         std::optional<uint32_t> visible_page_index);
 
   // Gets the partial text from the PDF to be used for suggest. Schedules for
   // the next page of text to be fetched, from the PDF in page order until
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
index e32bfe1..65120aed 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -597,6 +597,7 @@
            {"use-dynamic-theme-min-chroma", "3.0"}}},
          {lens::features::kLensOverlayContextualSearchbox,
           {
+              {"send-page-url-for-contextualization", "true"},
               {"use-inner-text-as-context", "true"},
               {"use-inner-html-as-context", "true"},
           }},
@@ -4625,7 +4626,9 @@
       const override {
     auto enabled = PDFExtensionTestBase::GetEnabledFeatures();
     enabled.push_back({lens::features::kLensOverlayContextualSearchbox,
-                       {{"use-pdfs-as-context", "true"},
+                       {{"send-page-url-for-contextualization", "true"},
+                        {"characters-per-page-heuristic", "1"},
+                        {"use-pdfs-as-context", "true"},
                         {"use-inner-html-as-context", "true"},
                         {"file-upload-limit-bytes",
                          base::NumberToString(kFileSizeLimitBytes)}}});
@@ -4697,10 +4700,12 @@
       static_cast<lens::TestLensOverlayQueryController*>(
           controller->get_lens_overlay_query_controller_for_testing());
   ASSERT_TRUE(base::test::RunUntil([&] {
-    return fake_query_controller->last_sent_partial_content().size() == 1;
+    return fake_query_controller->last_sent_partial_content().pages_size() == 1;
   }));
-  ASSERT_EQ(u"this is some text\r\nsome more text",
-            fake_query_controller->last_sent_partial_content()[0]);
+  ASSERT_EQ(
+      "this is some text\r\nsome more text",
+      fake_query_controller->last_sent_partial_content().pages(0).text_segments(
+          0));
 }
 
 IN_PROC_BROWSER_TEST_P(LensOverlayControllerBrowserPDFContextualizationTest,
@@ -5054,6 +5059,7 @@
     auto enabled = PDFExtensionTestBase::GetEnabledFeatures();
     enabled.push_back({lens::features::kLensOverlayContextualSearchbox,
                        {{"use-pdfs-as-context", "true"},
+                        {"characters-per-page-heuristic", "1"},
                         {"use-inner-html-as-context", "true"},
                         {"pdf-text-character-limit", "50"},
                         {"file-upload-limit-bytes",
@@ -5093,12 +5099,16 @@
           controller->get_lens_overlay_query_controller_for_testing());
 
   ASSERT_TRUE(base::test::RunUntil([&] {
-    return 2u == fake_query_controller->last_sent_partial_content().size();
+    return 2 == fake_query_controller->last_sent_partial_content().pages_size();
   }));
-  ASSERT_EQ(u"1 First Section\r\nThis is the first section.\r\n1",
-            fake_query_controller->last_sent_partial_content()[0]);
-  ASSERT_EQ(u"1.1 First Subsection\r\nThis is the first subsection.\r\n2",
-            fake_query_controller->last_sent_partial_content()[1]);
+  ASSERT_EQ(
+      "1 First Section\r\nThis is the first section.\r\n1",
+      fake_query_controller->last_sent_partial_content().pages(0).text_segments(
+          0));
+  ASSERT_EQ(
+      "1.1 First Subsection\r\nThis is the first subsection.\r\n2",
+      fake_query_controller->last_sent_partial_content().pages(1).text_segments(
+          0));
 }
 
 // TODO(crbug.com/40268279): Stop testing both modes after OOPIF PDF viewer
@@ -6170,6 +6180,7 @@
     feature_list_.InitAndEnableFeatureWithParameters(
         lens::features::kLensOverlayContextualSearchbox,
         {
+            {"send-page-url-for-contextualization", "true"},
             {"use-inner-text-as-context", "false"},
             {"use-inner-html-as-context", "true"},
         });
diff --git a/chrome/browser/ui/lens/test_lens_overlay_query_controller.cc b/chrome/browser/ui/lens/test_lens_overlay_query_controller.cc
index 5aa18aa..6651b47 100644
--- a/chrome/browser/ui/lens/test_lens_overlay_query_controller.cc
+++ b/chrome/browser/ui/lens/test_lens_overlay_query_controller.cc
@@ -4,10 +4,28 @@
 
 #include "test_lens_overlay_query_controller.h"
 
+#include "base/containers/span.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/lens/lens_overlay_mime_type.h"
 #include "google_apis/common/api_error_codes.h"
 
 namespace lens {
 
+constexpr char kPdfMimeType[] = "application/pdf";
+constexpr char kPlainTextMimeType[] = "text/plain";
+constexpr char kHtmlMimeType[] = "text/html";
+
+lens::MimeType StringToContentType(const std::string& content_type) {
+  if (content_type == kPdfMimeType) {
+    return lens::MimeType::kPdf;
+  } else if (content_type == kHtmlMimeType) {
+    return lens::MimeType::kHtml;
+  } else if (content_type == kPlainTextMimeType) {
+    return lens::MimeType::kPlainText;
+  }
+  return lens::MimeType::kUnknown;
+}
+
 FakeEndpointFetcher::FakeEndpointFetcher(EndpointResponse response)
     : EndpointFetcher(
           net::DefineNetworkTrafficAnnotation("lens_overlay_mock_fetcher",
@@ -132,34 +150,15 @@
       query_text, lens_selection_type, additional_search_query_params);
 }
 
-void TestLensOverlayQueryController::SendPageContentUpdateRequest(
-    base::span<const uint8_t> new_content_bytes,
-    lens::MimeType new_content_type,
-    GURL new_page_url) {
-  // TODO(crbug.com/378918804): Update these variables in the endpoint
-  // fetcher creator.
-  last_sent_underlying_content_bytes_ = new_content_bytes;
-  last_sent_underlying_content_type_ = new_content_type;
-  last_sent_page_url_ = new_page_url;
-
-  LensOverlayQueryController::SendPageContentUpdateRequest(
-      new_content_bytes, new_content_type, new_page_url);
-}
-
-void TestLensOverlayQueryController::SendPartialPageContentRequest(
-    base::span<const std::u16string> partial_content) {
-  last_sent_partial_content_ = partial_content;
-  LensOverlayQueryController::SendPartialPageContentRequest(partial_content);
-}
-
 void TestLensOverlayQueryController::ResetTestingState() {
   last_lens_selection_type_ = lens::UNKNOWN_SELECTION_TYPE;
   last_queried_region_.reset();
   last_queried_text_.clear();
   last_queried_region_bytes_ = std::nullopt;
   last_sent_underlying_content_bytes_ = base::span<const uint8_t>();
-  last_sent_partial_content_ = base::span<const std::u16string>();
+  last_sent_partial_content_ = lens::LensOverlayDocument();
   last_sent_underlying_content_type_ = lens::MimeType::kUnknown;
+  last_sent_page_content_data_.clear();
   last_sent_page_url_ = GURL();
   num_interaction_requests_sent_ = 0;
 }
@@ -195,6 +194,8 @@
     // The server doesn't send a response to this request, so no need to set
     // the response string to something meaningful.
     fake_server_response_string = "";
+    last_sent_partial_content_.CopyFrom(
+        request->objects_request().payload().partial_pdf_document());
   } else if (request->has_objects_request() &&
              !request->objects_request().has_image_data() &&
              request->objects_request().has_payload()) {
@@ -206,6 +207,13 @@
     fake_server_response_string = "";
     sent_page_content_request_id_.CopyFrom(
         request->objects_request().request_context().request_id());
+    last_sent_page_content_data_ =
+        std::string(request->objects_request().payload().content_data());
+    last_sent_underlying_content_bytes_ =
+        base::as_byte_span(last_sent_page_content_data_);
+    last_sent_underlying_content_type_ = StringToContentType(
+        request->objects_request().payload().content_type());
+    last_sent_page_url_ = GURL(request->objects_request().payload().page_url());
   } else if (request->has_objects_request()) {
     // Full image request.
     sent_full_image_objects_request_.CopyFrom(request->objects_request());
diff --git a/chrome/browser/ui/lens/test_lens_overlay_query_controller.h b/chrome/browser/ui/lens/test_lens_overlay_query_controller.h
index 58e5f07..b0db74c 100644
--- a/chrome/browser/ui/lens/test_lens_overlay_query_controller.h
+++ b/chrome/browser/ui/lens/test_lens_overlay_query_controller.h
@@ -128,7 +128,7 @@
     return last_sent_underlying_content_type_;
   }
 
-  base::span<const std::u16string> last_sent_partial_content() const {
+  const lens::LensOverlayDocument& last_sent_partial_content() const {
     return last_sent_partial_content_;
   }
 
@@ -202,13 +202,6 @@
       std::map<std::string, std::string> additional_search_query_params)
       override;
 
-  void SendPageContentUpdateRequest(base::span<const uint8_t> new_content_bytes,
-                                    lens::MimeType new_content_type,
-                                    GURL new_page_url) override;
-
-  void SendPartialPageContentRequest(
-      base::span<const std::u16string> partial_content) override;
-
   // Resets the test state.
   void ResetTestingState();
 
@@ -285,6 +278,10 @@
   // The last region bytes sent by the query controller.
   std::optional<SkBitmap> last_queried_region_bytes_;
 
+  // The last page content data sent by the query controller. Used to prevent
+  // dangling references by the underlying content bytes span.
+  std::string last_sent_page_content_data_;
+
   // The last underlying content bytes sent by the query controller.
   base::raw_span<const uint8_t> last_sent_underlying_content_bytes_;
 
@@ -292,7 +289,7 @@
   lens::MimeType last_sent_underlying_content_type_;
 
   // The last partial content sent by the query controller.
-  base::raw_span<const std::u16string> last_sent_partial_content_;
+  lens::LensOverlayDocument last_sent_partial_content_;
 
   // The last page url sent by the query controller.
   GURL last_sent_page_url_;
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_pref_names.cc b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_pref_names.cc
index 2780ecd..006ee3b 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_pref_names.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_pref_names.cc
@@ -14,6 +14,8 @@
   registry->RegisterBooleanPref(kTabGroupsDeletionSkipDialogOnUngroup, false);
   registry->RegisterBooleanPref(kTabGroupsDeletionSkipDialogOnRemoveTab, false);
   registry->RegisterBooleanPref(kTabGroupsDeletionSkipDialogOnCloseTab, false);
+  registry->RegisterBooleanPref(kTabGroupsDeletionSkipDialogOnLeaveGroup,
+                                false);
   registry->RegisterIntegerPref(kTabGroupLearnMoreFooterShownCount, 0);
 }
 
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_pref_names.h b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_pref_names.h
index 0490020..5235f61 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_pref_names.h
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_pref_names.h
@@ -32,6 +32,8 @@
     "tab_groups.deletion.skip_dialog_on_remove_tab";
 inline constexpr char kTabGroupsDeletionSkipDialogOnCloseTab[] =
     "tab_groups.deletion.skip_dialog_on_close_tab";
+inline constexpr char kTabGroupsDeletionSkipDialogOnLeaveGroup[] =
+    "tab_groups.deletion.skip_dialog_on_leave_group";
 
 // Integer that keep track of how many times the learn more footer in the
 // TabGroupEditorBubbleView has been seen by the user. Once this value reaches
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.cc b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.cc
index 4420948..dd013d3 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.cc
@@ -7,13 +7,18 @@
 #include <numeric>
 #include <unordered_set>
 
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/metrics/user_metrics.h"
 #include "base/not_fatal_until.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/uuid.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/collaboration/collaboration_service_factory.h"
+#include "chrome/browser/data_sharing/data_sharing_service_factory.h"
 #include "chrome/browser/favicon/favicon_utils.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/tab_group_sync/tab_group_sync_service_factory.h"
 #include "chrome/browser/tab_group_sync/tab_group_sync_utils.h"
@@ -35,11 +40,17 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/collaboration/public/collaboration_service.h"
+#include "components/data_sharing/public/data_sharing_service.h"
 #include "components/data_sharing/public/features.h"
+#include "components/data_sharing/public/group_data.h"
 #include "components/saved_tab_groups/public/features.h"
 #include "components/saved_tab_groups/public/pref_names.h"
 #include "components/saved_tab_groups/public/saved_tab_group_tab.h"
+#include "components/saved_tab_groups/public/tab_group_sync_service.h"
+#include "components/saved_tab_groups/public/types.h"
 #include "components/saved_tab_groups/public/utils.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/sync/base/user_selectable_type.h"
 #include "components/sync/service/sync_service.h"
 #include "components/sync/service/sync_user_settings.h"
@@ -208,6 +219,70 @@
   }
 }
 
+// static
+void SavedTabGroupUtils::LeaveSharedGroup(const Browser* browser,
+                                          const base::Uuid& saved_group_guid) {
+  TabGroupSyncService* tab_group_service =
+      SavedTabGroupUtils::GetServiceForProfile(browser->profile());
+  if (!tab_group_service) {
+    return;
+  }
+
+  const std::optional<SavedTabGroup> saved_group =
+      tab_group_service->GetGroup(saved_group_guid);
+  if (!saved_group) {
+    return;
+  }
+
+  if (!saved_group->collaboration_id()) {
+    return;
+  }
+
+  auto leave_callback = base::BindOnce(
+      [](const Browser* browser, const base::Uuid& saved_group_guid) {
+        TabGroupSyncService* tab_group_service =
+            SavedTabGroupUtils::GetServiceForProfile(browser->profile());
+        if (!tab_group_service) {
+          return;
+        }
+
+        const std::optional<SavedTabGroup> saved_group =
+            tab_group_service->GetGroup(saved_group_guid);
+        if (!saved_group) {
+          return;
+        }
+
+        if (!saved_group->collaboration_id()) {
+          return;
+        }
+
+        data_sharing::DataSharingService* data_sharing_service =
+            data_sharing::DataSharingServiceFactory::GetForProfile(
+                browser->profile());
+        if (!data_sharing_service) {
+          return;
+        }
+
+        if (saved_group->local_group_id()) {
+          SavedTabGroupUtils::RemoveGroupFromTabstrip(
+              nullptr, saved_group->local_group_id().value());
+        }
+
+        data_sharing_service->LeaveGroup(
+            data_sharing::GroupId(saved_group->collaboration_id()->value()),
+            base::DoNothing());
+      },
+      browser, saved_group_guid);
+  DeletionDialogController::DialogMetadata dialog_metadata(
+      DeletionDialogController::DialogType::LeaveGroup,
+      /*closing_group_count=*/1,
+      /*closing_multiple_tabs=*/saved_group->saved_tabs().size() > 1);
+  dialog_metadata.title_of_closing_group = saved_group->title();
+  browser->tab_group_deletion_dialog_controller()->MaybeShowDialog(
+      dialog_metadata, std::move(leave_callback));
+}
+
+// static
 void SavedTabGroupUtils::MaybeShowSavedTabGroupDeletionDialog(
     Browser* browser,
     DeletionDialogController::DialogType type,
@@ -675,11 +750,13 @@
   return elements[0];
 }
 
+// static
 bool SavedTabGroupUtils::ShouldAutoPinNewTabGroups(Profile* profile) {
   return profile->GetPrefs()->GetBoolean(
       tab_groups::prefs::kAutoPinNewTabGroups);
 }
 
+// static
 bool SavedTabGroupUtils::AreSavedTabGroupsSyncedForProfile(Profile* profile) {
   const syncer::SyncService* const sync_service =
       SyncServiceFactory::GetForProfile(profile);
@@ -692,6 +769,7 @@
       syncer::UserSelectableType::kSavedTabGroups);
 }
 
+// static
 bool SavedTabGroupUtils::SupportsSharedTabGroups() {
   return tab_groups::IsTabGroupsSaveV2Enabled() &&
          tab_groups::IsTabGroupSyncServiceDesktopMigrationEnabled() &&
@@ -699,4 +777,50 @@
              data_sharing::features::kDataSharingFeature);
 }
 
+// static
+bool SavedTabGroupUtils::IsOwnerOfSharedTabGroup(Profile* profile,
+                                                 const base::Uuid& sync_id) {
+  // TODO(380515575): Create a function to determine if the user is signed in or
+  // not instead of checking here.
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(profile);
+
+  CoreAccountInfo account =
+      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
+
+  if (account.IsEmpty()) {
+    return true;
+  }
+
+  TabGroupSyncService* tab_group_service =
+      SavedTabGroupUtils::GetServiceForProfile(profile);
+  if (!tab_group_service) {
+    return true;
+  }
+
+  const std::optional<SavedTabGroup> saved_group =
+      tab_group_service->GetGroup(sync_id);
+  if (!saved_group) {
+    return true;
+  }
+
+  std::optional<CollaborationId> collaboration_id =
+      saved_group->collaboration_id();
+  if (!collaboration_id) {
+    return true;
+  }
+
+  collaboration::CollaborationService* collaboration_service =
+      collaboration::CollaborationServiceFactory::GetForProfile(profile);
+  if (!collaboration_service) {
+    return true;
+  }
+
+  data_sharing::MemberRole member_role =
+      collaboration_service->GetCurrentUserRoleForGroup(
+          data_sharing::GroupId(collaboration_id->value()));
+
+  return data_sharing::MemberRole::kOwner == member_role;
+}
+
 }  // namespace tab_groups
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h
index 02e49fe..55a344ff 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h
+++ b/chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h
@@ -35,6 +35,7 @@
 class SavedTabGroupUtils {
  public:
   DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kDeleteGroupMenuItem);
+  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kLeaveGroupMenuItem);
   DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kMoveGroupToNewWindowMenuItem);
   DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kToggleGroupPinStateMenuItem);
   DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kTabsTitleItem);
@@ -62,6 +63,8 @@
                                 const base::Uuid& saved_group_guid);
   static void DeleteSavedGroup(const Browser* browser,
                                const base::Uuid& saved_group_guid);
+  static void LeaveSharedGroup(const Browser* browser,
+                               const base::Uuid& saved_group_guid);
 
   // Open the `url` to the end of `browser` tab strip as a new ungrouped tab.
   static void OpenUrlInNewUngroupedTab(Browser* browser, const GURL& url);
@@ -158,6 +161,10 @@
 
   // Returns true if shared tab groups are supported.
   static bool SupportsSharedTabGroups();
+
+  // Returns true if the user is the owner of the shared tab group.
+  static bool IsOwnerOfSharedTabGroup(Profile* profile,
+                                      const base::Uuid& sync_id);
 };
 
 }  // namespace tab_groups
diff --git a/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.cc b/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.cc
index c8b7180..d33a7ba 100644
--- a/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.cc
+++ b/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.cc
@@ -145,6 +145,24 @@
           base::i18n::MessageFormatter::FormatWithNumberedArgs(
               l10n_util::GetStringUTF16(kDeleteOkTextId), closing_group_count)};
     }
+
+    case DeletionDialogController::DialogType::LeaveGroup: {
+      const bool title_is_empty =
+          !dialog_metadata.title_of_closing_group.has_value() ||
+          dialog_metadata.title_of_closing_group->empty();
+      std::u16string body_text =
+          title_is_empty
+              ? l10n_util::GetStringUTF16(
+                    IDS_DATA_SHARING_LEAVE_DIALOG_BODY_NO_GROUP_TITLE)
+              : l10n_util::GetStringFUTF16(
+                    IDS_DATA_SHARING_LEAVE_DIALOG_BODY,
+                    dialog_metadata.title_of_closing_group.value());
+
+      return DialogText{
+          l10n_util::GetStringUTF16(IDS_DATA_SHARING_LEAVE_DIALOG_TITLE),
+          body_text,
+          l10n_util::GetStringUTF16(IDS_DATA_SHARING_LEAVE_DIALOG_CONFIRM)};
+    }
   }
 }
 
@@ -172,6 +190,10 @@
       return pref_service->GetBoolean(
           saved_tab_groups::prefs::kTabGroupsDeletionSkipDialogOnCloseTab);
     }
+    case DeletionDialogController::DialogType::LeaveGroup: {
+      return pref_service->GetBoolean(
+          saved_tab_groups::prefs::kTabGroupsDeletionSkipDialogOnLeaveGroup);
+    }
   }
 }
 
@@ -204,6 +226,11 @@
           saved_tab_groups::prefs::kTabGroupsDeletionSkipDialogOnCloseTab,
           new_value);
     }
+    case DeletionDialogController::DialogType::LeaveGroup: {
+      return pref_service->SetBoolean(
+          saved_tab_groups::prefs::kTabGroupsDeletionSkipDialogOnLeaveGroup,
+          new_value);
+    }
   }
 }
 
diff --git a/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h b/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h
index 8cf9416..85707db 100644
--- a/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h
+++ b/chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h
@@ -35,6 +35,7 @@
     UngroupSingle,
     RemoveTabAndDelete,
     CloseTabAndDelete,
+    LeaveGroup,
   };
 
   // Encapsulates metadata required to determine which strings should be
@@ -52,6 +53,7 @@
     DialogType type;
     int closing_group_count = 0;
     bool closing_multiple_tabs = false;
+    std::optional<std::u16string> title_of_closing_group;
   };
 
   // State object that represents the current dialog that is being shown.
diff --git a/chrome/browser/ui/toolbar/app_menu_model.h b/chrome/browser/ui/toolbar/app_menu_model.h
index 84787f3..d55b5582 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.h
+++ b/chrome/browser/ui/toolbar/app_menu_model.h
@@ -194,8 +194,9 @@
   // varies depending upon the underlying model. The command IDs for items in
   // these menus will be staggered and each increment by this value, so they
   // don't have conflicts. Currently, this accounts for the bookmarks, recent
-  // tabs menus, the profile submenu and tab groups submenu.
-  static constexpr int kNumUnboundedMenuTypes = 4;
+  // tabs menus, the profile submenu, tab groups submenu, and the comparison
+  // tables submenu.
+  static constexpr int kNumUnboundedMenuTypes = 5;
 
   // First command ID to use for each unbounded menu. These should be staggered,
   // and there should be kNumUnboundedMenuTypes of them.
@@ -203,6 +204,7 @@
   static constexpr int kMinRecentTabsCommandId = kMinBookmarksCommandId + 1;
   static constexpr int kMinOtherProfileCommandId = kMinRecentTabsCommandId + 1;
   static constexpr int kMinTabGroupsCommandId = kMinOtherProfileCommandId + 1;
+  static constexpr int kMinCompareCommandId = kMinTabGroupsCommandId + 1;
 
   // Creates an app menu model for the given browser. Init() must be called
   // before passing this to an AppMenu. |app_menu_icon_controller|, if provided,
diff --git a/chrome/browser/ui/toolbar/bookmark_sub_menu_model.cc b/chrome/browser/ui/toolbar/bookmark_sub_menu_model.cc
index 8d06cfc1..e14980b1 100644
--- a/chrome/browser/ui/toolbar/bookmark_sub_menu_model.cc
+++ b/chrome/browser/ui/toolbar/bookmark_sub_menu_model.cc
@@ -83,7 +83,7 @@
     AddSeparator(ui::NORMAL_SEPARATOR);
 
     compare_sub_menu_model_ =
-        std::make_unique<commerce::CompareSubMenuModel>(delegate());
+        std::make_unique<commerce::CompareSubMenuModel>(delegate(), browser);
     AddSubMenuWithStringIdAndIcon(
         IDC_COMPARE_MENU, IDS_COMPARE_MENU_LABEL, compare_sub_menu_model_.get(),
         ui::ImageModel::FromVectorIcon(kCompareIcon, ui::kColorMenuIcon, 16));
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/shared_tab_group_interactive_uitest.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/shared_tab_group_interactive_uitest.cc
index 19f1c1c..43d46554 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/shared_tab_group_interactive_uitest.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/shared_tab_group_interactive_uitest.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/data_sharing/data_sharing_service_factory.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/tab_group_sync/tab_group_sync_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
@@ -17,12 +19,19 @@
 #include "chrome/browser/ui/views/tabs/tab_group_header.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/test/interaction/interactive_browser_test.h"
+#include "components/data_sharing/public/data_sharing_service.h"
 #include "components/data_sharing/public/features.h"
+#include "components/data_sharing/public/group_data.h"
 #include "components/saved_tab_groups/internal/tab_group_sync_service_impl.h"
 #include "components/saved_tab_groups/public/features.h"
 #include "components/saved_tab_groups/public/tab_group_sync_service.h"
+#include "components/signin/public/base/consent_level.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/tab_groups/tab_group_id.h"
 #include "content/public/test/browser_test.h"
+#include "google_apis/gaia/core_account_id.h"
+#include "google_apis/gaia/gaia_id.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/interaction/interactive_test_internal.h"
 
@@ -95,10 +104,52 @@
     return browser()->tab_strip_model()->AddToNewGroup({0});
   }
 
-  void ShareTabGroup(TabGroupId group_id, std::string collaboration_id) {
+  void ShareTabGroup(TabGroupId group_id,
+                     std::string collaboration_id,
+                     data_sharing::MemberRole member_role,
+                     bool should_sign_in) {
     TabGroupSyncServiceImpl* service = static_cast<TabGroupSyncServiceImpl*>(
         TabGroupSyncServiceFactory::GetForProfile(browser()->profile()));
     service->MakeTabGroupSharedForTesting(group_id, collaboration_id);
+
+    // Additional Properties.
+    const std::string display_name = "Display Name";
+    const std::string email = "test@mail.com";
+    const GURL avatar_url = GURL("chrome://newtab");
+    const std::string given_name = "Given Name";
+    const std::string access_token = "fake_access_token";
+    const GaiaId gaia_id("fake_gaia_id");
+
+    GaiaId gaia_id_to_use = gaia_id;
+    if (should_sign_in) {
+      // Simulate a signed in primary account.
+      signin::IdentityManager* identity_manager =
+          IdentityManagerFactory::GetForProfile(browser()->profile());
+      signin::MakePrimaryAccountAvailable(identity_manager, email,
+                                          signin::ConsentLevel::kSignin);
+      signin::MakePrimaryAccountAvailable(identity_manager, email,
+                                          signin::ConsentLevel::kSync);
+      CoreAccountInfo account_info = identity_manager->GetPrimaryAccountInfo(
+          signin::ConsentLevel::kSignin);
+
+      gaia_id_to_use = account_info.gaia;
+    }
+
+    data_sharing::GroupMember group_member =
+        data_sharing::GroupMember(gaia_id_to_use, display_name, email,
+                                  member_role, avatar_url, given_name);
+    data_sharing::GroupData group_data =
+        data_sharing::GroupData(data_sharing::GroupId(collaboration_id),
+                                display_name, {group_member}, access_token);
+
+    data_sharing_service()->AddGroupDataForTesting(std::move(group_data));
+  }
+
+  data_sharing::DataSharingService* data_sharing_service() {
+    data_sharing::DataSharingService* data_sharing_service =
+        data_sharing::DataSharingServiceFactory::GetForProfile(
+            browser()->profile());
+    return data_sharing_service;
   }
 
  private:
@@ -109,7 +160,8 @@
 IN_PROC_BROWSER_TEST_F(SharedTabGroupInteractiveUiTest,
                        SharedTabGroupInAppMenu) {
   TabGroupId group_id = CreateNewTabGroup();
-  ShareTabGroup(group_id, "fake_collaboration_id");
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kOwner, /*should_sign_in=*/false);
 
   RunTestSequence(WaitForShow(kTabGroupHeaderElementId),
                   FinishTabstripAnimations(), ShowBookmarksBar(),
@@ -130,7 +182,8 @@
 IN_PROC_BROWSER_TEST_F(SharedTabGroupInteractiveUiTest,
                        SharedTabGroupInEverythingMenu) {
   TabGroupId group_id = CreateNewTabGroup();
-  ShareTabGroup(group_id, "fake_collaboration_id");
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kOwner, /*should_sign_in=*/false);
 
   RunTestSequence(
       WaitForShow(kTabGroupHeaderElementId), FinishTabstripAnimations(),
@@ -150,7 +203,8 @@
   const char kTabGroupHeaderToScreenshot[] = "Tab group header to hover";
 
   TabGroupId group_id = CreateNewTabGroup();
-  ShareTabGroup(group_id, "fake_collaboration_id");
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kOwner, /*should_sign_in=*/false);
 
   // TODO(crbug.com/380088920): Manually trigger a layout until we have a way to
   // know when the entity tracker is initialized.
@@ -174,7 +228,8 @@
   base::HistogramTester histogram_tester;
 
   TabGroupId group_id = CreateNewTabGroup();
-  ShareTabGroup(group_id, "fake_collaboration_id");
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kOwner, /*should_sign_in=*/false);
 
   RunTestSequence(WaitForShow(kTabGroupHeaderElementId),
                   FinishTabstripAnimations(), HoverTabGroupHeader(group_id),
@@ -196,7 +251,8 @@
   ::base::HistogramTester histogram_tester;
 
   TabGroupId group_id = CreateNewTabGroup();
-  ShareTabGroup(group_id, "fake_collaboration_id");
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kOwner, /*should_sign_in=*/false);
 
   // Close the tab group.
   browser()->tab_strip_model()->CloseAllTabsInGroup(group_id);
@@ -220,7 +276,8 @@
   ::base::HistogramTester histogram_tester;
 
   TabGroupId group_id = CreateNewTabGroup();
-  ShareTabGroup(group_id, "fake_collaboration_id");
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kOwner, /*should_sign_in=*/false);
 
   // Close the tab group.
   browser()->tab_strip_model()->CloseAllTabsInGroup(group_id);
@@ -246,7 +303,8 @@
   ::base::HistogramTester histogram_tester;
 
   TabGroupId group_id = CreateNewTabGroup();
-  ShareTabGroup(group_id, "fake_collaboration_id");
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kOwner, /*should_sign_in=*/false);
 
   // Close the tab group.
   browser()->tab_strip_model()->CloseAllTabsInGroup(group_id);
@@ -276,7 +334,6 @@
   ::base::HistogramTester histogram_tester;
 
   TabGroupId group_id = CreateNewTabGroup();
-  // ShareTabGroup(group_id, "fake_collaboration_id");
 
   RunTestSequence(
       WaitForShow(kTabGroupHeaderElementId), FinishTabstripAnimations(),
@@ -301,7 +358,8 @@
   ::base::HistogramTester histogram_tester;
 
   TabGroupId group_id = CreateNewTabGroup();
-  ShareTabGroup(group_id, "fake_collaboration_id");
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kOwner, /*should_sign_in=*/false);
 
   RunTestSequence(
       WaitForShow(kTabGroupHeaderElementId), FinishTabstripAnimations(),
@@ -326,7 +384,8 @@
   ::base::HistogramTester histogram_tester;
 
   TabGroupId group_id = CreateNewTabGroup();
-  ShareTabGroup(group_id, "fake_collaboration_id");
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kOwner, /*should_sign_in=*/false);
 
   RunTestSequence(
       WaitForShow(kTabGroupHeaderElementId), FinishTabstripAnimations(),
@@ -343,4 +402,22 @@
       1);
 }
 
+// Verify members see the leave group button instead of the delete button and
+// that pressing the leave group buttons deletes the group.
+IN_PROC_BROWSER_TEST_F(SharedTabGroupInteractiveUiTest, LeaveGroupPressed) {
+  TabGroupId group_id = CreateNewTabGroup();
+  ShareTabGroup(group_id, "fake_collaboration_id",
+                data_sharing::MemberRole::kMember, /*should_sign_in=*/true);
+
+  RunTestSequence(
+      WaitForShow(kTabGroupHeaderElementId), FinishTabstripAnimations(),
+      HoverTabGroupHeader(group_id), ClickMouse(ui_controls::RIGHT),
+      WaitForShow(kTabGroupEditorBubbleId),
+      PressButton(kTabGroupEditorBubbleLeaveGroupButtonId),
+      WaitForHide(kTabGroupEditorBubbleLeaveGroupButtonId),
+      WaitForShow(kDeletionDialogOkButtonId),
+      PressButton(kDeletionDialogOkButtonId),
+      WaitForHide(kTabGroupHeaderElementId), FinishTabstripAnimations());
+}
+
 }  // namespace tab_groups
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.cc b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
index 515a199..27394f18 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.cc
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
@@ -577,6 +577,9 @@
   // account.
   const auto border_insets = gfx::Insets::TLBR(top_inset, 0, bottom_inset, 0);
   if (tab_strip_combo_button_) {
+    if (tab_strip_action_container_) {
+      tab_strip_action_container_->UpdateButtonBorders(border_insets);
+    }
     tab_strip_combo_button_->new_tab_button()->SetBorder(
         views::CreateEmptyBorder(border_insets));
     tab_strip_combo_button_->tab_search_button()->SetBorder(
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/tab_strip_region_view_interactive_uitest.cc
index 1d13459..129d67fc 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view_interactive_uitest.cc
@@ -113,10 +113,10 @@
   EXPECT_TRUE(tab_2->HasFocus());
 
   move_forward_over_tab(tab_2);
-  EXPECT_TRUE(new_tab_button()->HasFocus());
+  EXPECT_TRUE(tab_search_button()->HasFocus());
 
   press_right();
-  EXPECT_TRUE(tab_search_button()->HasFocus());
+  EXPECT_TRUE(new_tab_button()->HasFocus());
 
   // Focus should cycle back around to tab_0.
   press_right();
@@ -153,10 +153,10 @@
 
   // Pressing left should immediately cycle back around to the last button.
   press_left();
-  EXPECT_TRUE(tab_search_button()->HasFocus());
+  EXPECT_TRUE(new_tab_button()->HasFocus());
 
   press_left();
-  EXPECT_TRUE(new_tab_button()->HasFocus());
+  EXPECT_TRUE(tab_search_button()->HasFocus());
 
   move_back_to_tab(tab_2);
   EXPECT_TRUE(tab_2->HasFocus());
@@ -186,12 +186,12 @@
 #if !BUILDFLAG(IS_WIN)
     EXPECT_TRUE(tab_strip_region_view()->AcceleratorPressed(
         tab_strip_region_view()->end_key()));
-    EXPECT_TRUE(new_tab_button()->HasFocus());
+    EXPECT_TRUE(tab_search_button()->HasFocus());
 #endif  // !BUILDFLAG(IS_WIN)
 
     EXPECT_TRUE(tab_strip_region_view()->AcceleratorPressed(
         tab_strip_region_view()->home_key()));
-    EXPECT_TRUE(tab_search_button()->HasFocus());
+    EXPECT_TRUE(new_tab_button()->HasFocus());
 
   } else {
     // The first tab should be active.
@@ -200,7 +200,7 @@
 #if !BUILDFLAG(IS_WIN)
     EXPECT_TRUE(tab_strip_region_view()->AcceleratorPressed(
         tab_strip_region_view()->end_key()));
-    EXPECT_TRUE(tab_search_button()->HasFocus());
+    EXPECT_TRUE(new_tab_button()->HasFocus());
 #endif  // !BUILDFLAG(IS_WIN)
 
     EXPECT_TRUE(tab_strip_region_view()->AcceleratorPressed(
diff --git a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc
index f8c74f6..1e740ad 100644
--- a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.cc
@@ -345,7 +345,12 @@
 
     menu_items_.push_back(AddChildView(BuildHideGroupButton()));
     AddChildView(BuildSeparator());
-    menu_items_.push_back(AddChildView(BuildDeleteGroupButton()));
+
+    if (OwnsGroup()) {
+      menu_items_.push_back(AddChildView(BuildDeleteGroupButton()));
+    } else {
+      menu_items_.push_back(AddChildView(BuildLeaveGroupButton()));
+    }
 
     if (ShouldShowSavedFooter()) {
       footer_ = AddChildView(std::make_unique<Footer>(browser_));
@@ -497,6 +502,27 @@
 }
 
 std::unique_ptr<views::LabelButton>
+TabGroupEditorBubbleView::BuildLeaveGroupButton() {
+  std::unique_ptr<views::LabelButton> leave_group_menu_item = CreateMenuItem(
+      TAB_GROUP_HEADER_CXMENU_LEAVE_GROUP,
+      l10n_util::GetStringUTF16(
+          IDS_DATA_SHARING_MEMBER_DELETE_LAST_TAB_CONFIRM),
+      base::BindRepeating(&TabGroupEditorBubbleView::LeaveGroupPressed,
+                          base::Unretained(this)),
+      ui::ImageModel::FromVectorIcon(kTrashCanRefreshIcon, ui::kColorMenuIcon,
+                                     kDefaultIconSize));
+
+  leave_group_menu_item->SetProperty(views::kElementIdentifierKey,
+                                     kTabGroupEditorBubbleLeaveGroupButtonId);
+  if (ShouldShowSavedFooter()) {
+    leave_group_menu_item->SetProperty(
+        views::kMarginsKey, gfx::Insets::TLBR(0, 0, kSeparatorPadding, 0));
+  }
+
+  return leave_group_menu_item;
+}
+
+std::unique_ptr<views::LabelButton>
 TabGroupEditorBubbleView::BuildMoveGroupToNewWindowButton() {
   return CreateMenuItem(
       TAB_GROUP_HEADER_CXMENU_MOVE_GROUP_TO_NEW_WINDOW,
@@ -635,22 +661,8 @@
     return true;
   }
 
-  std::optional<tab_groups::CollaborationId> collaboration_id =
-      maybe_saved_group->collaboration_id();
-  if (!collaboration_id) {
-    return true;
-  }
-
-  collaboration::CollaborationService* collaboration_service =
-      collaboration::CollaborationServiceFactory::GetForProfile(
-          browser_->profile());
-  if (!collaboration_service) {
-    return true;
-  }
-
-  data_sharing::GroupId group_id(collaboration_id->value());
-  return data_sharing::MemberRole::kOwner ==
-         collaboration_service->GetCurrentUserRoleForGroup(group_id);
+  return tab_groups::SavedTabGroupUtils::IsOwnerOfSharedTabGroup(
+      browser_->profile(), maybe_saved_group->saved_guid());
 }
 
 bool TabGroupEditorBubbleView::IsGroupSaved() const {
@@ -844,6 +856,23 @@
   GetWidget()->Close();
 }
 
+void TabGroupEditorBubbleView::LeaveGroupPressed() {
+  tab_groups::TabGroupSyncService* tab_group_service =
+      tab_groups::SavedTabGroupUtils::GetServiceForProfile(browser_->profile());
+  if (!tab_group_service) {
+    return;
+  }
+
+  std::optional<tab_groups::SavedTabGroup> saved_group =
+      tab_group_service->GetGroup(group_);
+  if (!saved_group) {
+    return;
+  }
+
+  tab_groups::SavedTabGroupUtils::LeaveSharedGroup(browser_,
+                                                   saved_group->saved_guid());
+}
+
 void TabGroupEditorBubbleView::DeleteGroupFromTabstrip() {
   TabStripModel* const model = browser_->tab_strip_model();
   const int num_tabs_in_group =
diff --git a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.h b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.h
index fcfe03d..7e0bdb9 100644
--- a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.h
+++ b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view.h
@@ -47,7 +47,8 @@
   static constexpr int TAB_GROUP_HEADER_CXMENU_SHARE = 5;
   static constexpr int TAB_GROUP_HEADER_CXMENU_CLOSE_GROUP = 6;
   static constexpr int TAB_GROUP_HEADER_CXMENU_DELETE_GROUP = 7;
-  static constexpr int TAB_GROUP_HEADER_CXMENU_MOVE_GROUP_TO_NEW_WINDOW = 8;
+  static constexpr int TAB_GROUP_HEADER_CXMENU_LEAVE_GROUP = 8;
+  static constexpr int TAB_GROUP_HEADER_CXMENU_MOVE_GROUP_TO_NEW_WINDOW = 9;
 
   using Colors =
       std::vector<std::pair<tab_groups::TabGroupColorId, std::u16string>>;
@@ -103,6 +104,7 @@
   std::unique_ptr<views::LabelButton> BuildUngroupButton();
   std::unique_ptr<views::LabelButton> BuildHideGroupButton();
   std::unique_ptr<views::LabelButton> BuildDeleteGroupButton();
+  std::unique_ptr<views::LabelButton> BuildLeaveGroupButton();
   std::unique_ptr<views::LabelButton> BuildMoveGroupToNewWindowButton();
   std::unique_ptr<views::LabelButton> BuildManageSharedGroupButton();
   std::unique_ptr<views::LabelButton> BuildShareGroupButton();
@@ -113,6 +115,7 @@
   void ShareOrManagePressed();
   void HideGroupPressed();
   void DeleteGroupPressed();
+  void LeaveGroupPressed();
   void MoveGroupToNewWindowPressed();
 
   // The action for moving a group to a new window is only enabled when the
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container.cc b/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
index 995b6c7..cf521fa1 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
@@ -610,5 +610,21 @@
   animation_session_->ApplyAnimationValue(animation);
 }
 
+void TabStripActionContainer::UpdateButtonBorders(
+    const gfx::Insets border_insets) {
+  if (auto_tab_group_button_) {
+    auto_tab_group_button_->SetBorder(views::CreateEmptyBorder(border_insets));
+  }
+  if (tab_declutter_button_) {
+    tab_declutter_button_->SetBorder(views::CreateEmptyBorder(border_insets));
+  }
+  if (product_specifications_button_) {
+    product_specifications_button_->SetBorder(
+        views::CreateEmptyBorder(border_insets));
+  }
+  if (glic_nudge_button_) {
+    glic_nudge_button_->SetBorder(views::CreateEmptyBorder(border_insets));
+  }
+}
 BEGIN_METADATA(TabStripActionContainer)
 END_METADATA
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container.h b/chrome/browser/ui/views/tabs/tab_strip_action_container.h
index ef61dc1..883ad38 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_action_container.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_action_container.h
@@ -19,6 +19,9 @@
 #include "ui/views/mouse_watcher.h"
 #include "ui/views/view.h"
 
+namespace gfx {
+class Insets;
+}
 namespace glic {
 class GlicButton;
 }
@@ -114,6 +117,8 @@
   // GlicNudgeObserver
   void OnTriggerGlicNudgeUI(std::string label) override;
 
+  void UpdateButtonBorders(gfx::Insets button_insets);
+
  private:
   friend class TabStripActionContainerBrowserTest;
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip_combo_button.cc b/chrome/browser/ui/views/tabs/tab_strip_combo_button.cc
index 7414003..1cade64 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_combo_button.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_combo_button.cc
@@ -49,7 +49,7 @@
                                          TabStrip* tab_strip) {
   Edge new_tab_button_flat_edge = Edge::kNone;
   if (features::HasTabstripComboButtonWithBackground()) {
-    new_tab_button_flat_edge = base::i18n::IsRTL() ? Edge::kLeft : Edge::kRight;
+    new_tab_button_flat_edge = base::i18n::IsRTL() ? Edge::kRight : Edge::kLeft;
   }
   std::unique_ptr<TabStripControlButton> new_tab_button =
       std::make_unique<TabStripControlButton>(
@@ -70,9 +70,9 @@
     new_tab_button->SetBackgroundFrameInactiveColorId(
         kColorNewTabButtonCRBackgroundFrameInactive);
   } else {
-    // Add a gap between the new tab button and tab search container.
+    // Add a gap between the new tab button and tab search button.
     new_tab_button->SetProperty(
-        views::kMarginsKey, gfx::Insets::TLBR(0, 0, 0, kButtonGapNoBackground));
+        views::kMarginsKey, gfx::Insets::TLBR(0, kButtonGapNoBackground, 0, 0));
   }
 
   new_tab_button->SetTooltipText(
@@ -106,7 +106,7 @@
   Edge tab_search_button_flat_edge = Edge::kNone;
   if (features::HasTabstripComboButtonWithBackground()) {
     tab_search_button_flat_edge =
-        base::i18n::IsRTL() ? Edge::kRight : Edge::kLeft;
+        base::i18n::IsRTL() ? Edge::kLeft : Edge::kRight;
   }
   std::unique_ptr<TabSearchButton> tab_search_button =
       std::make_unique<TabSearchButton>(tab_strip->controller(), browser,
@@ -128,9 +128,9 @@
       .SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
   separator_container->SetCanProcessEventsWithinSubtree(false);
 
-  new_tab_button_ = button_container->AddChildView(std::move(new_tab_button));
   tab_search_button_ =
       button_container->AddChildView(std::move(tab_search_button));
+  new_tab_button_ = button_container->AddChildView(std::move(new_tab_button));
   separator_ = separator_container->AddChildView(std::move(separator));
 
   SetLayoutManager(std::make_unique<views::FillLayout>());
diff --git a/chrome/browser/ui/webui/data_sharing/data_sharing_ui.cc b/chrome/browser/ui/webui/data_sharing/data_sharing_ui.cc
index 39872508..4419823 100644
--- a/chrome/browser/ui/webui/data_sharing/data_sharing_ui.cc
+++ b/chrome/browser/ui/webui/data_sharing/data_sharing_ui.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/data_sharing/data_sharing_ui.h"
 
+#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/data_sharing/data_sharing_page_handler.h"
 #include "chrome/common/webui_url_constants.h"
@@ -159,6 +160,9 @@
       {"ownerCannotShare", IDS_DATA_SHARING_OWNER_CANNOT_SHARE},
   };
   source->AddLocalizedStrings(kStrings);
+  source->AddBoolean(
+      "metricsReportingEnabled",
+      ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled());
 }
 
 DataSharingUI::~DataSharingUI() = default;
diff --git a/chrome/browser/ui/webui/glic/glic.mojom b/chrome/browser/ui/webui/glic/glic.mojom
index 85a6785..7a601ee0 100644
--- a/chrome/browser/ui/webui/glic/glic.mojom
+++ b/chrome/browser/ui/webui/glic/glic.mojom
@@ -85,6 +85,14 @@
   // Closes the Glic panel.
   ClosePanel();
 
+  // Requests that the web client's panel be attached/docked to a browser
+  // window.
+  AttachPanel();
+
+  // Requests that the web client's panel be detached/undocked from a browser
+  // window (floats free).
+  DetachPanel();
+
   // Returns the context from the currently active tab.
   // `tab_context_result` is null if tab content could not be captured.
   // This may fail if the tab is navigated while collecting data, or closed
diff --git a/chrome/browser/ui/webui/glic/glic_page_handler.cc b/chrome/browser/ui/webui/glic/glic_page_handler.cc
index 0568f6c..a09597cb 100644
--- a/chrome/browser/ui/webui/glic/glic_page_handler.cc
+++ b/chrome/browser/ui/webui/glic/glic_page_handler.cc
@@ -95,6 +95,10 @@
 
   void ClosePanel() override { glic_service_->ClosePanel(); }
 
+  void AttachPanel() override { glic_service_->AttachPanel(); }
+
+  void DetachPanel() override { glic_service_->DetachPanel(); }
+
   void ResizeWidget(const gfx::Size& size,
                     ResizeWidgetCallback callback) override {
     std::optional<gfx::Size> actual_size = glic_service_->ResizePanel(size);
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager_unittest.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager_unittest.cc
index 2b244e4..9c09df0 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager_unittest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager_unittest.cc
@@ -777,8 +777,16 @@
   AssertAppInstalledAtVersion(GetIwa2WebBundleId(), base::Version("2.2.0"));
 }
 
+// TODO(crbug.com/389137516): Flaky on ChromeOS MSAN.
+#if BUILDFLAG(IS_CHROMEOS) && defined(MEMORY_SANITIZER)
+#define MAYBE_StopsNonStartedUpdateDiscoveryTasksIfIwaIsUninstalled \
+  DISABLED_StopsNonStartedUpdateDiscoveryTasksIfIwaIsUninstalled
+#else
+#define MAYBE_StopsNonStartedUpdateDiscoveryTasksIfIwaIsUninstalled \
+  StopsNonStartedUpdateDiscoveryTasksIfIwaIsUninstalled
+#endif
 TEST_F(IsolatedWebAppUpdateManagerUpdateTest,
-       StopsNonStartedUpdateDiscoveryTasksIfIwaIsUninstalled) {
+       MAYBE_StopsNonStartedUpdateDiscoveryTasksIfIwaIsUninstalled) {
   url_loader_factory().ClearResponses();
 
   InitialIwaBundleForceInstall(CreateIwa1Bundle("1.0.0"));
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 390ccf3..c3ace056 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1736531933-228bc904ebf407e6f5265665948b4adbd3bdf254-ffa032aa746954106635699e59f2cf3b356a49fe.profdata
+chrome-android32-main-1736553506-c974b6e7ba1fc9665f6a6bc588a2e87d4e9ba2bd-3ea9782a31e18d48e1d9d373be610264a839a766.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index c0c1f871a..3b613b2 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1736531933-0bb86366eacf9bb8b754fea33cab7b57025fa8d3-ffa032aa746954106635699e59f2cf3b356a49fe.profdata
+chrome-android64-main-1736550699-b50c3195a8b0965cd68e8c7742e4092ed8987185-d9898f40a0d24fdeec60134cba73577be45aefd7.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 51114ea..554314d 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1736531933-6709b71c5ca7f5d7d4cb8ad775a7cd6c6d680236-ffa032aa746954106635699e59f2cf3b356a49fe.profdata
+chrome-mac-arm-main-1736553506-3ced6ef36e3d6c833df218c8d49af85bd10cbc41-3ea9782a31e18d48e1d9d373be610264a839a766.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index cdd1565..63bac15 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1736510306-f81a49cd21b328d70b48b3b859e44eafa159d030-92c2bb99b59079b19c3a40503b593091c01b9b4b.profdata
+chrome-win32-main-1736531933-d7160254cfc0d9ca0d91bf9244b84acfbae9319f-ffa032aa746954106635699e59f2cf3b356a49fe.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 051db7f..bf3b63a 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1736510306-05734b56a6442882d0cb2bd7c563c12dfe215f87-92c2bb99b59079b19c3a40503b593091c01b9b4b.profdata
+chrome-win64-main-1736531933-7b01ca2f5dff6eb381d060e00432958fd420ecc9-ffa032aa746954106635699e59f2cf3b356a49fe.profdata
diff --git a/chrome/common/extensions/api/file_browser_handlers/file_browser_handler_manifest_unittest.cc b/chrome/common/extensions/api/file_browser_handlers/file_browser_handler_manifest_unittest.cc
index 4f33388..99393b1 100644
--- a/chrome/common/extensions/api/file_browser_handlers/file_browser_handler_manifest_unittest.cc
+++ b/chrome/common/extensions/api/file_browser_handlers/file_browser_handler_manifest_unittest.cc
@@ -62,7 +62,7 @@
 }
 
 TEST_F(FileBrowserHandlerManifestTest, InvalidFileBrowserHandlers) {
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("filebrowser_invalid_access_permission.json",
                extensions::ErrorUtils::FormatErrorMessage(
                    errors::kInvalidFileAccessValue, base::NumberToString(1))),
@@ -88,7 +88,7 @@
       Testcase("filebrowser_invalid_file_filters_url.json",
                extensions::ErrorUtils::FormatErrorMessage(
                    errors::kInvalidURLPatternError, "http:*.html"))};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
   RunTestcase(Testcase("filebrowser_missing_permission.json",
                        errors::kInvalidFileBrowserHandlerMissingPermission),
               EXPECT_TYPE_WARNING);
diff --git a/chrome/common/extensions/api/speech/tts_engine_manifest_handler_unittest.cc b/chrome/common/extensions/api/speech/tts_engine_manifest_handler_unittest.cc
index 1a2b691..6eb5b1d 100644
--- a/chrome/common/extensions/api/speech/tts_engine_manifest_handler_unittest.cc
+++ b/chrome/common/extensions/api/speech/tts_engine_manifest_handler_unittest.cc
@@ -23,7 +23,7 @@
       base::StringPrintf(errors::kInvalidTtsBufferSizeRange, 1,
                          media::limits::kMaxSamplesPerPacket);
 
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("tts_engine_invalid_voices_1.json", errors::kInvalidTts),
       Testcase("tts_engine_invalid_voices_2.json", errors::kInvalidTtsVoices),
       Testcase("tts_engine_invalid_voices_3.json", errors::kInvalidTtsVoices),
@@ -56,7 +56,7 @@
       Testcase("tts_engine_invalid_buffer_size_4.json",
                errors::kInvalidTtsRequiresSampleRateAndBufferSize),
   };
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 
   LoadAndExpectSuccess("tts_engine_valid_voices.json");
   LoadAndExpectSuccess("tts_engine_valid_sample_rate_buffer_size.json");
diff --git a/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc b/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
index dda4bd5..441ca07 100644
--- a/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
+++ b/chrome/common/extensions/manifest_handlers/content_scripts_manifest_unittest.cc
@@ -26,7 +26,7 @@
 };
 
 TEST_F(ContentScriptsManifestTest, MatchPattern) {
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       // chrome:// urls are not allowed.
       Testcase("content_script_invalid_match_chrome_url.json",
                ErrorUtils::FormatErrorMessage(
@@ -56,7 +56,7 @@
                "Error at key 'content_scripts'. Parsing array failed at index "
                "0: Error at key 'matches': Parsing array failed at index 0: "
                "expected string, got integer")};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 
   LoadAndExpectSuccess("ports_in_content_scripts.json");
 }
diff --git a/chrome/common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc b/chrome/common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc
index e293ca5..5236aaf6 100644
--- a/chrome/common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc
+++ b/chrome/common/extensions/manifest_handlers/exclude_matches_manifest_unittest.cc
@@ -12,20 +12,18 @@
 };
 
 TEST_F(ExcludeMatchesManifestTest, ExcludeMatchPatterns) {
-  Testcase testcases[] = {
-    Testcase("exclude_matches.json"),
-    Testcase("exclude_matches_empty.json")
-  };
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_SUCCESS);
+  const Testcase testcases[] = {Testcase("exclude_matches.json"),
+                                Testcase("exclude_matches_empty.json")};
+  RunTestcases(testcases, EXPECT_TYPE_SUCCESS);
 
-  Testcase testcases2[] = {
+  const Testcase testcases2[] = {
       Testcase("exclude_matches_not_list.json",
                "Error at key 'content_scripts'. Parsing array failed at index "
                "0: 'exclude_matches': expected list, got string"),
       Testcase("exclude_matches_invalid_host.json",
                "Invalid value for 'content_scripts[0].exclude_matches[0]': "
                "Invalid host wildcard.")};
-  RunTestcases(testcases2, std::size(testcases2), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases2, EXPECT_TYPE_ERROR);
 }
 
 }  // namespace extensions
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
index 966e871..dfeb201 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
@@ -14,7 +14,7 @@
 using ContentSecurityPolicyManifestTest = ChromeManifestTest;
 
 TEST_F(ContentSecurityPolicyManifestTest, InsecureContentSecurityPolicy) {
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("insecure_contentsecuritypolicy_1.json",
                ErrorUtils::FormatErrorMessage(
                    errors::kInvalidCSPInsecureValueIgnored,
@@ -29,5 +29,5 @@
                ErrorUtils::FormatErrorMessage(
                    errors::kInvalidCSPMissingSecureSrc,
                    keys::kContentSecurityPolicy, "object-src"))};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_WARNING);
+  RunTestcases(testcases, EXPECT_TYPE_WARNING);
 }
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc
index 6eec22c..c9dfc7d6 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc
@@ -35,7 +35,7 @@
 
 TEST_F(InitValueManifestTest, InitFromValueInvalid) {
   SimpleFeature::ScopedThreadUnsafeAllowlistForTest allowlist(kAllowlistID);
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("init_invalid_version_missing.json", errors::kInvalidVersion),
       Testcase("init_invalid_version_invalid.json", errors::kInvalidVersion),
       Testcase("init_invalid_version_name_invalid.json",
@@ -109,7 +109,7 @@
       Testcase("init_invalid_short_name_type.json", errors::kInvalidShortName),
   };
 
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 TEST_F(InitValueManifestTest, InitFromValueValid) {
@@ -150,28 +150,27 @@
   EXPECT_EQ("1.0.0.0", extension->VersionString());
   EXPECT_EQ("1.0 alpha", extension->GetVersionForDisplay());
 
-  Testcase testcases[] = {
-    // Test with a minimum_chrome_version.
-    Testcase("init_valid_minimum_chrome.json"),
+  const Testcase testcases[] = {
+      // Test with a minimum_chrome_version.
+      Testcase("init_valid_minimum_chrome.json"),
 
-    // Test a hosted app with a minimum_chrome_version.
-    Testcase("init_valid_app_minimum_chrome.json"),
+      // Test a hosted app with a minimum_chrome_version.
+      Testcase("init_valid_app_minimum_chrome.json"),
 
-    // Test a hosted app with a requirements section.
-    Testcase("init_valid_app_requirements.json"),
+      // Test a hosted app with a requirements section.
+      Testcase("init_valid_app_requirements.json"),
 
-    // Test a theme with a minimum_chrome_version.
-    Testcase("init_valid_theme_minimum_chrome.json"),
+      // Test a theme with a minimum_chrome_version.
+      Testcase("init_valid_theme_minimum_chrome.json"),
 
-    // Verify empty permission settings are considered valid.
-    Testcase("init_valid_permissions_empty.json"),
+      // Verify empty permission settings are considered valid.
+      Testcase("init_valid_permissions_empty.json"),
 
-    // We allow unknown API permissions, so this will be valid until we better
-    // distinguish between API and host permissions.
-    Testcase("init_valid_permissions_unknown.json")
-  };
+      // We allow unknown API permissions, so this will be valid until we better
+      // distinguish between API and host permissions.
+      Testcase("init_valid_permissions_unknown.json")};
 
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_SUCCESS);
+  RunTestcases(testcases, EXPECT_TYPE_SUCCESS);
 }
 
 TEST_F(InitValueManifestTest, InitFromValueValidNameInRTL) {
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc
index 806ee69..bd15bda 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc
@@ -38,70 +38,57 @@
   extension = LoadAndExpectSuccess("launch_height.json");
   EXPECT_EQ(480, AppLaunchInfo::GetLaunchHeight(extension.get()));
 
-  Testcase testcases[] = {
-    Testcase("launch_window.json", errors::kInvalidLaunchContainer),
-    Testcase("launch_container_invalid_type.json",
-             errors::kInvalidLaunchContainer),
-    Testcase("launch_container_invalid_value.json",
-             errors::kInvalidLaunchContainer),
-    Testcase("launch_container_without_launch_url.json",
-             errors::kLaunchURLRequired),
-    Testcase("launch_width_invalid.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValueContainer,
-                 keys::kLaunchWidth)),
-    Testcase("launch_width_negative.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValue,
-                 keys::kLaunchWidth)),
-    Testcase("launch_height_invalid.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValueContainer,
-                 keys::kLaunchHeight)),
-    Testcase("launch_height_negative.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValue,
-                 keys::kLaunchHeight))
-  };
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  const Testcase testcases[] = {
+      Testcase("launch_window.json", errors::kInvalidLaunchContainer),
+      Testcase("launch_container_invalid_type.json",
+               errors::kInvalidLaunchContainer),
+      Testcase("launch_container_invalid_value.json",
+               errors::kInvalidLaunchContainer),
+      Testcase("launch_container_without_launch_url.json",
+               errors::kLaunchURLRequired),
+      Testcase("launch_width_invalid.json",
+               ErrorUtils::FormatErrorMessage(
+                   errors::kInvalidLaunchValueContainer, keys::kLaunchWidth)),
+      Testcase("launch_width_negative.json",
+               ErrorUtils::FormatErrorMessage(errors::kInvalidLaunchValue,
+                                              keys::kLaunchWidth)),
+      Testcase("launch_height_invalid.json",
+               ErrorUtils::FormatErrorMessage(
+                   errors::kInvalidLaunchValueContainer, keys::kLaunchHeight)),
+      Testcase("launch_height_negative.json",
+               ErrorUtils::FormatErrorMessage(errors::kInvalidLaunchValue,
+                                              keys::kLaunchHeight))};
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 TEST_F(AppLaunchManifestTest, AppLaunchURL) {
-  Testcase testcases[] = {
-    Testcase("launch_path_and_url.json",
-             errors::kLaunchPathAndURLAreExclusive),
-    Testcase("launch_path_and_extent.json",
-             errors::kLaunchPathAndExtentAreExclusive),
-    Testcase("launch_path_invalid_type.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValue,
-                 keys::kLaunchLocalPath)),
-    Testcase("launch_path_invalid_value.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValue,
-                 keys::kLaunchLocalPath)),
-    Testcase("launch_path_invalid_localized.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValue,
-                 keys::kLaunchLocalPath)),
-    Testcase("launch_url_invalid_type_1.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValue,
-                 keys::kLaunchWebURL)),
-    Testcase("launch_url_invalid_type_2.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValue,
-                 keys::kLaunchWebURL)),
-    Testcase("launch_url_invalid_type_3.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValue,
-                 keys::kLaunchWebURL)),
-    Testcase("launch_url_invalid_localized.json",
-             ErrorUtils::FormatErrorMessage(
-                 errors::kInvalidLaunchValue,
-                 keys::kLaunchWebURL))
-  };
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  const Testcase testcases[] = {
+      Testcase("launch_path_and_url.json",
+               errors::kLaunchPathAndURLAreExclusive),
+      Testcase("launch_path_and_extent.json",
+               errors::kLaunchPathAndExtentAreExclusive),
+      Testcase("launch_path_invalid_type.json",
+               ErrorUtils::FormatErrorMessage(errors::kInvalidLaunchValue,
+                                              keys::kLaunchLocalPath)),
+      Testcase("launch_path_invalid_value.json",
+               ErrorUtils::FormatErrorMessage(errors::kInvalidLaunchValue,
+                                              keys::kLaunchLocalPath)),
+      Testcase("launch_path_invalid_localized.json",
+               ErrorUtils::FormatErrorMessage(errors::kInvalidLaunchValue,
+                                              keys::kLaunchLocalPath)),
+      Testcase("launch_url_invalid_type_1.json",
+               ErrorUtils::FormatErrorMessage(errors::kInvalidLaunchValue,
+                                              keys::kLaunchWebURL)),
+      Testcase("launch_url_invalid_type_2.json",
+               ErrorUtils::FormatErrorMessage(errors::kInvalidLaunchValue,
+                                              keys::kLaunchWebURL)),
+      Testcase("launch_url_invalid_type_3.json",
+               ErrorUtils::FormatErrorMessage(errors::kInvalidLaunchValue,
+                                              keys::kLaunchWebURL)),
+      Testcase("launch_url_invalid_localized.json",
+               ErrorUtils::FormatErrorMessage(errors::kInvalidLaunchValue,
+                                              keys::kLaunchWebURL))};
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 
   scoped_refptr<Extension> extension =
       LoadAndExpectSuccess("launch_local_path.json");
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc
index 6bed579..c4a28c22 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc
@@ -75,7 +75,7 @@
   extension = LoadAndExpectSuccess("platform_app_with_options_page.json");
   EXPECT_TRUE(!OptionsPageInfo::HasOptionsPage(extension.get()));
 
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       // Forbid options page with relative URL in hosted apps.
       Testcase("hosted_app_relative_options.json",
                extensions::manifest_errors::kInvalidOptionsPageInHostedApp),
@@ -88,7 +88,7 @@
       Testcase(
           "packaged_app_absolute_options.json",
           extensions::manifest_errors::kInvalidOptionsPageExpectUrlInPackage)};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 // Tests for the options_ui.page manifest field.
@@ -107,9 +107,9 @@
                                extension->id().c_str()),
             OptionsPageInfo::GetOptionsPage(extension.get()).spec());
 
-  Testcase testcases[] = {Testcase("options_ui_page_bad_url.json",
-                                   "'page': expected page, got null")};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_WARNING);
+  RunTestcase(Testcase("options_ui_page_bad_url.json",
+                       "'page': expected page, got null"),
+              EXPECT_TYPE_WARNING);
 }
 
 // Runs TestOptionsUIChromeStyleAndOpenInTab with and without the
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
index 57e4149..f941be57 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
@@ -57,7 +57,7 @@
   extension = LoadAndExpectSuccess("incognito_valid_platform_app.json");
   EXPECT_FALSE(IncognitoInfo::IsSplitMode(extension.get()));
 
-  Testcase error_testcases[] = {
+  const Testcase error_testcases[] = {
       Testcase("init_invalid_platform_app_2.json",
                errors::kBackgroundRequiredForPlatformApps),
       Testcase("init_invalid_platform_app_3.json",
@@ -65,9 +65,9 @@
                    errors::kInvalidManifestVersionUnsupported, "either 2 or 3",
                    "apps")),
   };
-  RunTestcases(error_testcases, std::size(error_testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(error_testcases, EXPECT_TYPE_ERROR);
 
-  Testcase warning_testcases[] = {
+  const Testcase warning_testcases[] = {
       Testcase(
           "init_invalid_platform_app_1.json",
           "'app.launch' is only allowed for legacy packaged apps and hosted "
@@ -77,8 +77,7 @@
                "apps, "
                "but this is a packaged app."),
   };
-  RunTestcases(warning_testcases, std::size(warning_testcases),
-               EXPECT_TYPE_WARNING);
+  RunTestcases(warning_testcases, EXPECT_TYPE_WARNING);
 
   LoadAndExpectWarnings(
       "init_invalid_platform_app_4.json",
@@ -90,19 +89,17 @@
 
 TEST_F(PlatformAppsManifestTest, PlatformAppContentSecurityPolicy) {
   // Normal platform apps can't specify a CSP value.
-  Testcase warning_testcases[] = {
-    Testcase(
-        "init_platform_app_csp_warning_1.json",
-        "'content_security_policy' is only allowed for extensions, legacy "
-            "packaged apps, and login screen extensions, but this is a "
-            "packaged app."),
-    Testcase(
-        "init_platform_app_csp_warning_2.json",
-        "'app.content_security_policy' is not allowed for specified extension "
-            "ID.")
-  };
-  RunTestcases(warning_testcases, std::size(warning_testcases),
-               EXPECT_TYPE_WARNING);
+  const Testcase warning_testcases[] = {
+      Testcase(
+          "init_platform_app_csp_warning_1.json",
+          "'content_security_policy' is only allowed for extensions, legacy "
+          "packaged apps, and login screen extensions, but this is a "
+          "packaged app."),
+      Testcase("init_platform_app_csp_warning_2.json",
+               "'app.content_security_policy' is not allowed for specified "
+               "extension "
+               "ID.")};
+  RunTestcases(warning_testcases, EXPECT_TYPE_WARNING);
 
   // Allowlisted ones can (this is the ID corresponding to the base 64 encoded
   // key in the init_platform_app_csp.json manifest.)
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc
index 8089f0a..419b6f9 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc
@@ -15,7 +15,7 @@
 namespace errors = extensions::manifest_errors;
 
 TEST_F(ChromeManifestTest, AppWebUrls) {
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("web_urls_wrong_type.json", errors::kInvalidWebURLs),
       Testcase("web_urls_invalid_1.json",
                ErrorUtils::FormatErrorMessage(errors::kInvalidWebURL,
@@ -38,7 +38,7 @@
                ErrorUtils::FormatErrorMessage(
                    errors::kInvalidWebURL, base::NumberToString(1),
                    errors::kCannotClaimAllHostsInExtent))};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 
   LoadAndExpectSuccess("web_urls_has_port.json");
 
diff --git a/chrome/services/speech/soda/mock_soda_client.h b/chrome/services/speech/soda/mock_soda_client.h
index 0cd5c10..ffef5687 100644
--- a/chrome/services/speech/soda/mock_soda_client.h
+++ b/chrome/services/speech/soda/mock_soda_client.h
@@ -32,6 +32,10 @@
                int sample_rate,
                int channel_count),
               (override));
+  MOCK_METHOD(void,
+              UpdateRecognitionContext,
+              (const RecognitionContext context),
+              (override));
   MOCK_METHOD(bool, IsInitialized, (), (override));
   MOCK_METHOD(bool, BinaryLoadedSuccessfully, (), (override));
 };
diff --git a/chrome/services/speech/soda/soda_async_impl.h b/chrome/services/speech/soda/soda_async_impl.h
index ee2c726..614180a5 100644
--- a/chrome/services/speech/soda/soda_async_impl.h
+++ b/chrome/services/speech/soda/soda_async_impl.h
@@ -56,6 +56,14 @@
   void* callback_handle;
 } SerializedSodaConfig;
 
+typedef struct {
+  // A RecognitionContext serialized as a string.
+  const char* recognition_context;
+
+  // Length of char* in recognition_context.
+  int recognition_context_size;
+} RecognitionContext;
+
 void* CreateSoda(SerializedSodaConfig config);
 
 // Destroys the instance of SODA, called on the destruction of the SodaClient.
@@ -65,6 +73,10 @@
 void AddAudio(void* soda_async_handle,
               const char* audio_buffer,
               int audio_buffer_size);
+
+// Updates the recognition context.
+void UpdateRecognitionContext(void* soda_async_handle,
+                              RecognitionContext context);
 }
 
 #endif  // CHROME_SERVICES_SPEECH_SODA_SODA_ASYNC_IMPL_H_
diff --git a/chrome/services/speech/soda/soda_client.h b/chrome/services/speech/soda/soda_client.h
index 2bd5d7b..60f8293 100644
--- a/chrome/services/speech/soda/soda_client.h
+++ b/chrome/services/speech/soda/soda_client.h
@@ -43,6 +43,9 @@
                      int sample_rate,
                      int channel_count) = 0;
 
+  // Updates the recognition context for the current SODA instance.
+  virtual void UpdateRecognitionContext(const RecognitionContext context) = 0;
+
   // Returns a flag indicating whether the client has been initialized.
   virtual bool IsInitialized() = 0;
 
diff --git a/chrome/services/speech/soda/soda_client_impl.cc b/chrome/services/speech/soda/soda_client_impl.cc
index d356525e..cc1f413 100644
--- a/chrome/services/speech/soda/soda_client_impl.cc
+++ b/chrome/services/speech/soda/soda_client_impl.cc
@@ -24,7 +24,10 @@
       mark_done_func_(reinterpret_cast<MarkDoneFunction>(
           lib_.GetFunctionPointer("ExtendedSodaMarkDone"))),
       soda_start_func_(reinterpret_cast<SodaStartFunction>(
-          lib_.GetFunctionPointer("ExtendedSodaStart"))) {
+          lib_.GetFunctionPointer("ExtendedSodaStart"))),
+      update_recognition_context_func_(
+          reinterpret_cast<UpdateRecognitionContextFunction>(
+              lib_.GetFunctionPointer("UpdateRecognitionContext"))) {
   if (!lib_.is_valid()) {
     LOG(ERROR) << "SODA binary at " << library_path.value()
                << " could not be loaded.";
@@ -32,6 +35,8 @@
     DCHECK(false);
   }
 
+  // We do not need to check the |update_recognition_context_func_| since it is
+  // not available in old SODA versions.
   DCHECK(create_soda_func_);
   DCHECK(delete_soda_func_);
   DCHECK(add_audio_func_);
@@ -114,6 +119,16 @@
   soda_start_func_(soda_async_handle_);
 }
 
+NO_SANITIZE("cfi-icall")
+void SodaClientImpl::UpdateRecognitionContext(
+    const RecognitionContext context) {
+  if (load_soda_result_ != LoadSodaResultValue::kSuccess ||
+      !update_recognition_context_func_) {
+    return;
+  }
+  update_recognition_context_func_(soda_async_handle_, context);
+}
+
 bool SodaClientImpl::IsInitialized() {
   return is_initialized_;
 }
diff --git a/chrome/services/speech/soda/soda_client_impl.h b/chrome/services/speech/soda/soda_client_impl.h
index 054cc8b..d50e212d 100644
--- a/chrome/services/speech/soda/soda_client_impl.h
+++ b/chrome/services/speech/soda/soda_client_impl.h
@@ -30,6 +30,7 @@
   void Reset(const SerializedSodaConfig config,
              int sample_rate,
              int channel_count) override;
+  void UpdateRecognitionContext(const RecognitionContext context) override;
   bool IsInitialized() override;
   bool BinaryLoadedSuccessfully() override;
 
@@ -51,6 +52,9 @@
   typedef void (*SodaStartFunction)(void*);
   SodaStartFunction soda_start_func_;
 
+  typedef void (*UpdateRecognitionContextFunction)(void*, RecognitionContext);
+  UpdateRecognitionContextFunction update_recognition_context_func_;
+
   // An opaque handle to the SODA async instance. While this class owns this
   // handle, the handle is instantiated and deleted by the SODA library, so the
   // pointer may dangle after DeleteExtendedSodaAsync is called.
diff --git a/chrome/services/speech/speech_recognition_recognizer_impl.cc b/chrome/services/speech/speech_recognition_recognizer_impl.cc
index f97a5d3..3d0e9bcf 100644
--- a/chrome/services/speech/speech_recognition_recognizer_impl.cc
+++ b/chrome/services/speech/speech_recognition_recognizer_impl.cc
@@ -350,13 +350,34 @@
   soda_client_->MarkDone();
 }
 
+void SpeechRecognitionRecognizerImpl::UpdateRecognitionContext(
+    media::mojom::SpeechRecognitionRecognitionContextPtr recognition_context) {
+  soda::chrome::RecognitionContext context;
+  auto* context_input = context.add_context();
+  context_input->set_name(kContextInputName);
+  for (const auto& phrase : recognition_context->phrases) {
+    auto* p = context_input->mutable_phrases()->add_phrase();
+    p->set_phrase(phrase.phrase);
+    p->set_boost(phrase.boost);
+  }
+
+  auto serialized = context.SerializeAsString();
+  RecognitionContext serialized_recognition_context;
+  serialized_recognition_context.recognition_context = serialized.c_str();
+  serialized_recognition_context.recognition_context_size = serialized.size();
+  CHECK(soda_client_);
+  soda_client_->UpdateRecognitionContext(serialized_recognition_context);
+}
+
 void SpeechRecognitionRecognizerImpl::AddAudio(
     media::mojom::AudioDataS16Ptr buffer) {
   SendAudioToSpeechRecognitionService(std::move(buffer));
 }
+
 void SpeechRecognitionRecognizerImpl::OnAudioCaptureEnd() {
   MarkDone();
 }
+
 void SpeechRecognitionRecognizerImpl::OnAudioCaptureError() {
   OnSpeechRecognitionError();
 }
diff --git a/chrome/services/speech/speech_recognition_recognizer_impl.h b/chrome/services/speech/speech_recognition_recognizer_impl.h
index 0c7c35b8..decb734 100644
--- a/chrome/services/speech/speech_recognition_recognizer_impl.h
+++ b/chrome/services/speech/speech_recognition_recognizer_impl.h
@@ -102,6 +102,9 @@
 
   void MarkDone() override;
 
+  void UpdateRecognitionContext(
+      media::mojom::SpeechRecognitionRecognitionContextPtr recognition_context);
+
   // AudioSourceConsumer:
   void AddAudio(media::mojom::AudioDataS16Ptr buffer) override;
   void OnAudioCaptureEnd() override;
diff --git a/chrome/services/speech/speech_recognition_recognizer_impl_unittest.cc b/chrome/services/speech/speech_recognition_recognizer_impl_unittest.cc
index f2fa713..291639d 100644
--- a/chrome/services/speech/speech_recognition_recognizer_impl_unittest.cc
+++ b/chrome/services/speech/speech_recognition_recognizer_impl_unittest.cc
@@ -115,4 +115,17 @@
   EXPECT_EQ(2.0, phrase.boost());
 }
 
+TEST_F(SpeechRecognitionRecognizerImplTest, UpdateRecognitionContextTest) {
+  auto recognizer = CreateRecognizer(CreateOptions());
+  auto soda_client = std::make_unique<NiceMock<::soda::MockSodaClient>>();
+  auto* soda_client_ptr = soda_client.get();
+  recognizer->SetSodaClientForTesting(std::move(soda_client));
+
+  media::mojom::SpeechRecognitionRecognitionContextPtr context =
+      media::mojom::SpeechRecognitionRecognitionContext::New();
+  context->phrases.emplace_back("test phrase", 2.0);
+  EXPECT_CALL(*soda_client_ptr, UpdateRecognitionContext(_));
+  recognizer->UpdateRecognitionContext(std::move(context));
+}
+
 }  // namespace speech
diff --git a/chrome/test/data/webui/chromeos/boca_ui/client_delegate_impl_test.ts b/chrome/test/data/webui/chromeos/boca_ui/client_delegate_impl_test.ts
index b3bcf84..f63dbb1 100644
--- a/chrome/test/data/webui/chromeos/boca_ui/client_delegate_impl_test.ts
+++ b/chrome/test/data/webui/chromeos/boca_ui/client_delegate_impl_test.ts
@@ -113,9 +113,7 @@
           sessionDuration: {
             microseconds: 120000000n,
           },
-          sessionStartTime: {
-            msec: 1000000,
-          },
+          sessionStartTime: new Date(1000000),
           teacher: {
             id: '0',
             name: 'teacher',
@@ -410,9 +408,7 @@
           sessionDuration: {
             microseconds: 120000000n,
           },
-          sessionStartTime: {
-            msec: 1000000,
-          },
+          sessionStartTime: new Date(1000000),
           students: [],
           studentsJoinViaCode: [],
           onTaskConfig: {isLocked: false, tabs: []},
diff --git a/chrome/test/data/webui/data_sharing/BUILD.gn b/chrome/test/data/webui/data_sharing/BUILD.gn
index 90cae98..43b9a08 100644
--- a/chrome/test/data/webui/data_sharing/BUILD.gn
+++ b/chrome/test/data/webui/data_sharing/BUILD.gn
@@ -16,5 +16,8 @@
       [ "chrome-untrusted://data-sharing/*|" +
         rebase_path("$root_gen_dir/chrome/browser/resources/data_sharing/tsc/*",
                     target_gen_dir) ]
-  ts_deps = [ "//chrome/browser/resources/data_sharing:build_ts" ]
+  ts_deps = [
+    "//chrome/browser/resources/data_sharing:build_ts",
+    "//ui/webui/resources/js:build_ts",
+  ]
 }
diff --git a/chrome/test/data/webui/data_sharing/data_sharing_test.ts b/chrome/test/data/webui/data_sharing/data_sharing_test.ts
index 1b2bef4..3277c54 100644
--- a/chrome/test/data/webui/data_sharing/data_sharing_test.ts
+++ b/chrome/test/data/webui/data_sharing/data_sharing_test.ts
@@ -11,6 +11,7 @@
 import {DataSharingApp} from 'chrome-untrusted://data-sharing/data_sharing_app.js';
 import {Code} from 'chrome-untrusted://data-sharing/data_sharing_sdk_types.js';
 import {DataSharingSdkImpl} from 'chrome-untrusted://data-sharing/dummy_data_sharing_sdk.js';
+import {loadTimeData} from 'chrome-untrusted://resources/js/load_time_data.js';
 import {assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
 import {TestBrowserProxy} from 'chrome-untrusted://webui-test/test_browser_proxy.js';
 import {TestMock} from 'chrome-untrusted://webui-test/test_mock.js';
@@ -101,4 +102,19 @@
     assertEquals(1, testBrowserProxy.getCallCount('closeUi'));
     assertEquals(Code.OK, testBrowserProxy.getArgs('closeUi')[0]);
   });
+
+  test('Metrics reporting', async () => {
+    loadTimeData.overrideValues({
+      metricsReportingEnabled: true,
+    });
+    DataSharingApp.setUrlForTesting(
+        'chrome-untrusted://data-sharing?flow=share&tab_group_id=fake_id');
+    dataSharingApp = document.createElement('data-sharing-app');
+    testBrowserProxy.callbackRouterRemote.onAccessTokenFetched('fake_token');
+    document.body.appendChild(dataSharingApp);
+    await microtasksFinished();
+    assertEquals(1, testDataSharingSdk.getCallCount('updateClearcut'));
+    const arg = testDataSharingSdk.getArgs('updateClearcut')[0];
+    assertEquals(true, arg.enabled);
+  });
 });
diff --git a/chrome/updater/policy/service.cc b/chrome/updater/policy/service.cc
index 0e0fd00b..85ed18f 100644
--- a/chrome/updater/policy/service.cc
+++ b/chrome/updater/policy/service.cc
@@ -87,9 +87,9 @@
 }  // namespace
 
 std::vector<scoped_refptr<PolicyManagerInterface>> CreateManagers(
-    bool should_take_policy_critical_section,
     scoped_refptr<ExternalConstants> external_constants,
-    scoped_refptr<PolicyManagerInterface> dm_policy_manager) {
+    scoped_refptr<PolicyManagerInterface> dm_policy_manager,
+    scoped_refptr<PolicyManagerInterface> group_policy) {
   // The order of the policy managers:
   //   1) External constants policy manager (if present).
   //   2) Group policy manager (Windows only). **
@@ -107,9 +107,10 @@
                                external_constants->GroupPolicies())
                          : nullptr;
 #if BUILDFLAG(IS_WIN)
-  auto group_policy_manager = base::MakeRefCounted<GroupPolicyManager>(
-      should_take_policy_critical_section,
-      external_constants->IsMachineManaged());
+  auto group_policy_manager = group_policy
+                                  ? group_policy
+                                  : base::MakeRefCounted<GroupPolicyManager>(
+                                        external_constants->IsMachineManaged());
   if (CloudPolicyOverridesPlatformPolicy({dm_policy_manager,
                                           group_policy_manager,
                                           external_constants_policy_manager})) {
@@ -144,16 +145,11 @@
       persisted_data_(persisted_data),
       is_ceca_experiment_enabled_(false) {}
 
-// The policy managers are initialized without taking the Group Policy critical
-// section here, by passing `false` for `should_take_policy_critical_section`,
-// to avoid blocking the main sequence. Later in `FetchPoliciesDone`, the
-// policies are reloaded with the critical section lock.
 PolicyService::PolicyService(
     scoped_refptr<ExternalConstants> external_constants,
     scoped_refptr<PersistedData> persisted_data,
     bool is_ceca_experiment_enabled)
     : policy_managers_(SortManagers(CreateManagers(
-          /*should_take_policy_critical_section=*/false,
           external_constants,
           CreateDMPolicyManager(external_constants->IsMachineManaged())))),
       external_constants_(external_constants),
@@ -224,15 +220,18 @@
       FROM_HERE, {base::MayBlock(), base::WithBaseSyncPrimitives()},
       base::BindOnce(
           [](scoped_refptr<ExternalConstants> external_constants,
-             scoped_refptr<PolicyManagerInterface> dm_policy_manager) {
-            return CreateManagers(
-                /*should_take_policy_critical_section=*/true,
-                external_constants, dm_policy_manager);
+             scoped_refptr<PolicyManagerInterface> dm_policy_manager,
+             scoped_refptr<PolicyManagerInterface> group_policy_manager) {
+            return CreateManagers(external_constants, dm_policy_manager,
+                                  group_policy_manager);
           },
           external_constants_,
           dm_policy_manager ? dm_policy_manager
           : policy_managers_.manager_names.contains(kSourceDMPolicyManager)
               ? policy_managers_.manager_names[kSourceDMPolicyManager]
+              : nullptr,
+          policy_managers_.manager_names.contains(kSourceGroupPolicyManager)
+              ? policy_managers_.manager_names[kSourceGroupPolicyManager]
               : nullptr),
       base::BindOnce(&PolicyService::PolicyManagerLoaded,
                      base::WrapRefCounted(this), result));
diff --git a/chrome/updater/policy/service.h b/chrome/updater/policy/service.h
index b76cd33..832f7bb 100644
--- a/chrome/updater/policy/service.h
+++ b/chrome/updater/policy/service.h
@@ -240,9 +240,9 @@
 };
 
 std::vector<scoped_refptr<PolicyManagerInterface>> CreateManagers(
-    bool should_take_policy_critical_section,
     scoped_refptr<ExternalConstants> external_constants,
-    scoped_refptr<PolicyManagerInterface> dm_policy_manager);
+    scoped_refptr<PolicyManagerInterface> dm_policy_manager,
+    scoped_refptr<PolicyManagerInterface> group_policy_manager = {});
 
 }  // namespace updater
 
diff --git a/chrome/updater/policy/service_unittest.cc b/chrome/updater/policy/service_unittest.cc
index 6fa7f487..274b8ab 100644
--- a/chrome/updater/policy/service_unittest.cc
+++ b/chrome/updater/policy/service_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "base/values.h"
 #include "chrome/enterprise_companion/global_constants.h"
@@ -771,6 +772,7 @@
 
 #if BUILDFLAG(IS_WIN)
 TEST(PolicyService, CreateManagers) {
+  base::test::TaskEnvironment environment;
   registry_util::RegistryOverrideManager registry_overrides;
   ASSERT_NO_FATAL_FAILURE(
       registry_overrides.OverrideRegistry(HKEY_LOCAL_MACHINE));
@@ -780,7 +782,7 @@
                            OmahaSettingsClientProto>();
   auto dm_policy = base::MakeRefCounted<DMPolicyManager>(*omaha_settings, true);
   PolicyManagers managers =
-      CreateManagers(false, CreateExternalConstants(), dm_policy);
+      CreateManagers(CreateExternalConstants(), dm_policy);
   EXPECT_EQ(managers.size(), size_t{4});
   EXPECT_EQ(managers[0]->source(), "DictValuePolicy");
   EXPECT_EQ(managers[1]->source(), "Group Policy");
@@ -791,7 +793,7 @@
                         Wow6432(KEY_WRITE));
   EXPECT_EQ(ERROR_SUCCESS,
             key.WriteValue(L"CloudPolicyOverridesPlatformPolicy", 1));
-  managers = CreateManagers(false, CreateExternalConstants(), dm_policy);
+  managers = CreateManagers(CreateExternalConstants(), dm_policy);
   EXPECT_EQ(managers.size(), size_t{4});
   EXPECT_EQ(managers[0]->source(), "DictValuePolicy");
   EXPECT_EQ(managers[1]->source(), "Device Management");
@@ -805,7 +807,7 @@
                            OmahaSettingsClientProto>();
   auto dm_policy = base::MakeRefCounted<DMPolicyManager>(*omaha_settings, true);
   PolicyManagers managers =
-      CreateManagers(false, CreateExternalConstants(), dm_policy);
+      CreateManagers(CreateExternalConstants(), dm_policy);
   EXPECT_EQ(managers.size(), size_t{4});
   EXPECT_EQ(managers[0]->source(), "DictValuePolicy");
   EXPECT_EQ(managers[1]->source(), "Device Management");
@@ -819,7 +821,7 @@
                            OmahaSettingsClientProto>();
   auto dm_policy = base::MakeRefCounted<DMPolicyManager>(*omaha_settings, true);
   PolicyManagers managers =
-      CreateManagers(false, CreateExternalConstants(), dm_policy);
+      CreateManagers(CreateExternalConstants(), dm_policy);
   EXPECT_EQ(managers.size(), size_t{3});
   EXPECT_EQ(managers[0]->source(), "DictValuePolicy");
   EXPECT_EQ(managers[1]->source(), "Device Management");
diff --git a/chrome/updater/policy/win/group_policy_manager.cc b/chrome/updater/policy/win/group_policy_manager.cc
index 69af8e87..4c83efd8 100644
--- a/chrome/updater/policy/win/group_policy_manager.cc
+++ b/chrome/updater/policy/win/group_policy_manager.cc
@@ -56,10 +56,10 @@
   virtual ~PolicySectionEvents() = default;
 };
 
-base::Value::Dict LoadGroupPolicies(bool should_take_policy_critical_section) {
+base::Value::Dict LoadGroupPolicies() {
   base::ScopedClosureRunner leave_policy_section_closure;
 
-  if (should_take_policy_critical_section && base::IsManagedDevice()) {
+  if (base::IsManagedDevice()) {
     // Only for managed machines, a best effort is made to take the Group Policy
     // critical section. Lock acquisition can take a long time in the worst case
     // scenarios, hence a short timed wait is used.
@@ -122,9 +122,8 @@
 }  // namespace
 
 GroupPolicyManager::GroupPolicyManager(
-    bool should_take_policy_critical_section,
     std::optional<bool> override_is_managed_device)
-    : PolicyManager(LoadGroupPolicies(should_take_policy_critical_section)),
+    : PolicyManager(LoadGroupPolicies()),
       is_managed_device_(override_is_managed_device.value_or(
           base::IsManagedOrEnterpriseDevice())) {}
 
diff --git a/chrome/updater/policy/win/group_policy_manager.h b/chrome/updater/policy/win/group_policy_manager.h
index ca137e8..3aa1049f 100644
--- a/chrome/updater/policy/win/group_policy_manager.h
+++ b/chrome/updater/policy/win/group_policy_manager.h
@@ -15,9 +15,8 @@
 // The GroupPolicyManager returns policies for domain-joined machines.
 class GroupPolicyManager : public PolicyManager {
  public:
-  GroupPolicyManager(
-      bool should_take_policy_critical_section,
-      std::optional<bool> override_is_managed_device = std::nullopt);
+  explicit GroupPolicyManager(
+      std::optional<bool> override_is_managed_device = {});
   GroupPolicyManager(const GroupPolicyManager&) = delete;
   GroupPolicyManager& operator=(const GroupPolicyManager&) = delete;
 
diff --git a/chrome/updater/policy/win/group_policy_manager_unittest.cc b/chrome/updater/policy/win/group_policy_manager_unittest.cc
index 4ae01ecf..abc5591f 100644
--- a/chrome/updater/policy/win/group_policy_manager_unittest.cc
+++ b/chrome/updater/policy/win/group_policy_manager_unittest.cc
@@ -53,7 +53,7 @@
 }
 
 TEST_F(GroupPolicyManagerTests, NoPolicySet) {
-  auto policy_manager = base::MakeRefCounted<GroupPolicyManager>(true);
+  auto policy_manager = base::MakeRefCounted<GroupPolicyManager>();
   EXPECT_FALSE(policy_manager->HasActiveDevicePolicies());
 
   EXPECT_EQ(policy_manager->source(), "Group Policy");
@@ -125,7 +125,7 @@
   EXPECT_EQ(ERROR_SUCCESS,
             key.WriteValue(L"RollbackToTargetVersion" TEST_APP_ID, 1));
 
-  auto policy_manager = base::MakeRefCounted<GroupPolicyManager>(true);
+  auto policy_manager = base::MakeRefCounted<GroupPolicyManager>();
   EXPECT_EQ(policy_manager->HasActiveDevicePolicies(),
             base::win::IsEnrolledToDomain());
 
@@ -199,7 +199,8 @@
   EXPECT_EQ(ERROR_SUCCESS,
             key.WriteValue(L"RollbackToTargetVersion" TEST_APP_ID, L"1"));
 
-  auto policy_manager = base::MakeRefCounted<GroupPolicyManager>(true, true);
+  auto policy_manager = base::MakeRefCounted<GroupPolicyManager>(
+      /*override_is_managed_device=*/true);
   EXPECT_TRUE(policy_manager->HasActiveDevicePolicies());
 
   EXPECT_FALSE(policy_manager->CloudPolicyOverridesPlatformPolicy());
diff --git a/chromeos/ash/components/phonehub/OWNERS b/chromeos/ash/components/phonehub/OWNERS
index 4746e16..d5036cfe8 100644
--- a/chromeos/ash/components/phonehub/OWNERS
+++ b/chromeos/ash/components/phonehub/OWNERS
@@ -5,5 +5,4 @@
 pushi@google.com
 
 # For Eche
-dhnishi@chromium.org
 nayebi@google.com
diff --git a/clank b/clank
index de102b69..dbee480 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit de102b699470b4d92be37fc98f29f6c668e419f2
+Subproject commit dbee4805fa8597c96cfcf0e4f44e77ae9f50901c
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
index 1c02a9a..6413cc3 100644
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
+++ b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
@@ -316,6 +316,7 @@
 
         mToolbarHolder =
                 (TouchRestrictingFrameLayout) findViewById(R.id.bottom_sheet_toolbar_container);
+        mToolbarHolder.setBottomSheet(this);
 
         mBottomSheetContentContainer =
                 (TouchRestrictingFrameLayout) findViewById(R.id.bottom_sheet_content);
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java
index bf114fe9..b49ecfa7 100644
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java
+++ b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java
@@ -34,13 +34,13 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (isTouchDisabled()) return false;
+        if (isTouchDisabled()) return true;
         return super.onInterceptTouchEvent(event);
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (isTouchDisabled()) return false;
+        if (isTouchDisabled()) return true;
         return super.onTouchEvent(event);
     }
 }
diff --git a/components/browser_ui/display_cutout/android/java/src/org/chromium/components/browser_ui/display_cutout/DisplayCutoutController.java b/components/browser_ui/display_cutout/android/java/src/org/chromium/components/browser_ui/display_cutout/DisplayCutoutController.java
index d9962ea8..33ea65fb 100644
--- a/components/browser_ui/display_cutout/android/java/src/org/chromium/components/browser_ui/display_cutout/DisplayCutoutController.java
+++ b/components/browser_ui/display_cutout/android/java/src/org/chromium/components/browser_ui/display_cutout/DisplayCutoutController.java
@@ -152,28 +152,15 @@
 
     // Helper implementation to observe fullscreen changes and trigger re-layout.
     private class FullscreenWebContentsObserver extends WebContentsObserver {
-        private boolean mIsDestroyed;
-
         FullscreenWebContentsObserver(WebContents webContents) {
             super(webContents);
         }
 
-        @Nullable
-        WebContents getWebContents() {
-            return mIsDestroyed ? null : mWebContents.get();
-        }
-
         @Override
         public void didToggleFullscreenModeForTab(
                 boolean enteredFullscreen, boolean willCauseResize) {
             maybeUpdateLayout();
         }
-
-        @Override
-        public void destroy() {
-            mIsDestroyed = true;
-            super.destroy();
-        }
     }
 
     private final Delegate mDelegate;
@@ -277,21 +264,19 @@
     }
 
     private void updateWebContentObserver(@Nullable WebContents webContents) {
-        if (webContents == null) {
-            if (mWebContentsObserver != null) {
-                mWebContentsObserver.destroy();
-                mWebContentsObserver = null;
-            }
+        if (mWebContentsObserver != null
+                && webContents != null
+                && mWebContentsObserver.getWebContents() == webContents) {
             return;
         }
 
-        if (mWebContentsObserver != null && mWebContentsObserver.mIsDestroyed) {
-            if (webContents.equals(mWebContentsObserver.getWebContents())) {
-                return;
-            } else {
-                mWebContentsObserver.destroy();
-            }
+        if (mWebContentsObserver != null) {
+            mWebContentsObserver.destroy();
+            mWebContentsObserver = null;
         }
+
+        if (webContents == null) return;
+
         mWebContentsObserver = new FullscreenWebContentsObserver(webContents);
     }
 
diff --git a/components/collaboration/internal/collaboration_service_impl.h b/components/collaboration/internal/collaboration_service_impl.h
index 3c6dd7a8..38e06ad 100644
--- a/components/collaboration/internal/collaboration_service_impl.h
+++ b/components/collaboration/internal/collaboration_service_impl.h
@@ -29,7 +29,7 @@
 namespace collaboration {
 class CollaborationController;
 
-// The internal implementation of the CollborationService.
+// The internal implementation of the CollaborationService.
 class CollaborationServiceImpl : public CollaborationService,
                                  public syncer::SyncServiceObserver,
                                  public signin::IdentityManager::Observer {
diff --git a/components/collaboration/public/collaboration_controller_delegate.h b/components/collaboration/public/collaboration_controller_delegate.h
index efa073945..1ed255a 100644
--- a/components/collaboration/public/collaboration_controller_delegate.h
+++ b/components/collaboration/public/collaboration_controller_delegate.h
@@ -16,7 +16,7 @@
 namespace collaboration {
 
 // The class responsible for controlling actions on platform specific UI
-// elements. This delegate is required by the CollborationController.
+// elements. This delegate is required by the CollaborationController.
 class CollaborationControllerDelegate {
  public:
   struct ErrorInfo {
diff --git a/components/commerce_strings.grdp b/components/commerce_strings.grdp
index ccb5b27..cdcc437a 100644
--- a/components/commerce_strings.grdp
+++ b/components/commerce_strings.grdp
@@ -738,6 +738,12 @@
       <message name="IDS_COMPARE_SHOW_ALL_COMPARISON_TABLES" desc="The label for the menu option for showing all of a user's existing comparison tables.">
         Show All Comparison Tables
       </message>
+      <message name="IDS_COMPARE_ADD_TAB_TO_COMPARISON_TABLE" desc="The label for the menu option for adding the current tab to a comparison table.">
+        Add Tab to Comparison Table
+      </message>
+      <message name="IDS_COMPARE_NEW_COMPARISON_TABLE" desc="The label for the menu option for adding the current tab to a new comparison table.">
+        New Comparison Table
+      </message>
     </then>
     <else>
       <message name="IDS_COMPARE_MENU_LABEL" desc="The label for the comparison tables submenu item under the bookmarks and lists app menu.">
@@ -746,6 +752,12 @@
       <message name="IDS_COMPARE_SHOW_ALL_COMPARISON_TABLES" desc="The label for the menu option for showing all of a user's existing comparison tables.">
         Show all comparison tables
       </message>
+      <message name="IDS_COMPARE_ADD_TAB_TO_COMPARISON_TABLE" desc="The label for the menu option for adding the current tab to a comparison table.">
+        Add tab to comparison table
+      </message>
+      <message name="IDS_COMPARE_NEW_COMPARISON_TABLE" desc="The label for the menu option for adding the current tab to a new comparison table.">
+        New comparison table
+      </message>
     </else>
   </if> <!-- use_titlecase -->
 </grit-part>
diff --git a/components/commerce_strings_grdp/IDS_COMPARE_ADD_TAB_TO_COMPARISON_TABLE.png.sha1 b/components/commerce_strings_grdp/IDS_COMPARE_ADD_TAB_TO_COMPARISON_TABLE.png.sha1
new file mode 100644
index 0000000..21328611
--- /dev/null
+++ b/components/commerce_strings_grdp/IDS_COMPARE_ADD_TAB_TO_COMPARISON_TABLE.png.sha1
@@ -0,0 +1 @@
+ffc443d9c651d08964931f9de0cb885ef57c4c14
\ No newline at end of file
diff --git a/components/commerce_strings_grdp/IDS_COMPARE_NEW_COMPARISON_TABLE.png.sha1 b/components/commerce_strings_grdp/IDS_COMPARE_NEW_COMPARISON_TABLE.png.sha1
new file mode 100644
index 0000000..f467099b
--- /dev/null
+++ b/components/commerce_strings_grdp/IDS_COMPARE_NEW_COMPARISON_TABLE.png.sha1
@@ -0,0 +1 @@
+cd9af114cd9d4ae67e6587ff52b09c0508521b35
\ No newline at end of file
diff --git a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/configs/DataSharingUiConfig.java b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/configs/DataSharingUiConfig.java
index 9a207d8..5b748b6 100644
--- a/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/configs/DataSharingUiConfig.java
+++ b/components/data_sharing/public/android/java/src/org/chromium/components/data_sharing/configs/DataSharingUiConfig.java
@@ -27,11 +27,6 @@
 
     /** Callback interface for common data sharing UI events. */
     public interface DataSharingCallback {
-        // TODO (ritikagup) : Cleanup this method, once the overloaded method is fully integrated.
-        default void onLearnMoreAboutSharedTabGroupsClicked(GURL url) {}
-
-        default void onLearnMoreAboutSharedTabGroupsClicked(Context context, GURL url) {}
-
         default void onClickOpenChromeCustomTab(Context context, GURL url) {}
     }
 
diff --git a/components/exo/surface.cc b/components/exo/surface.cc
index 34f3a0d..4bc215c 100644
--- a/components/exo/surface.cc
+++ b/components/exo/surface.cc
@@ -14,6 +14,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/not_fatal_until.h"
 #include "base/notreached.h"
 #include "base/numerics/safe_conversions.h"
@@ -1701,6 +1702,7 @@
                                     bool needs_full_damage,
                                     std::optional<float> device_scale_factor,
                                     viz::CompositorFrame* frame) {
+  UMA_HISTOGRAM_BOOLEAN("Graphics.Exo.Surface.AppendContentsToFrame", true);
   const std::unique_ptr<viz::CompositorRenderPass>& render_pass =
       frame->render_pass_list.back();
   gfx::PointF parent_to_root_dp = gfx::ScalePoint(
@@ -1827,6 +1829,7 @@
     if (current_resource_.id) {
       frame->resource_list.push_back(current_resource_);
     }
+    UMA_HISTOGRAM_BOOLEAN("Graphics.Exo.Surface.Occluded", true);
     return;
   }
 
@@ -1871,6 +1874,7 @@
       // Draw quad is only needed if buffer is not fully transparent.
 
       if (requires_texture_draw_quad) {
+        UMA_HISTOGRAM_BOOLEAN("Graphics.Exo.Surface.TextureDrawQuad", true);
         viz::TextureDrawQuad* texture_quad =
             render_pass->CreateAndAppendDrawQuad<viz::TextureDrawQuad>();
         texture_quad->SetNew(quad_state, quad_rect, quad_rect,
@@ -1923,6 +1927,7 @@
           damage_rect_px = gfx::RectF();
         }
       } else {
+        UMA_HISTOGRAM_BOOLEAN("Graphics.Exo.Surface.TileDrawQuad", true);
         viz::TileDrawQuad* tile_quad =
             render_pass->CreateAndAppendDrawQuad<viz::TileDrawQuad>();
         // TODO(crbug.com/40229946): Support AA quads coming from exo.
@@ -1939,6 +1944,7 @@
     }
     frame->resource_list.push_back(current_resource_);
   } else if (state_.basic_state.alpha != 0.0f) {
+    UMA_HISTOGRAM_BOOLEAN("Graphics.Exo.Surface.SolidColorDrawQuad", true);
     const viz::SharedQuadState* quad_state = AppendOrCreateSharedQuadState(
         viz::DrawQuad::Material::kSolidColor, state_.basic_state.alpha,
         render_pass, quad_to_target_transform, quad_rect, msk, quad_clip_rect,
diff --git a/components/mirroring/service/openscreen_session_host.cc b/components/mirroring/service/openscreen_session_host.cc
index b23629c..94c11fad 100644
--- a/components/mirroring/service/openscreen_session_host.cc
+++ b/components/mirroring/service/openscreen_session_host.cc
@@ -546,9 +546,8 @@
                             weak_factory_.GetWeakPtr()),
         // This is safe since it is only called synchronously and we own
         // the video sender instance.
-        base::BindRepeating(&OpenscreenSessionHost::GetSuggestedVideoBitrate,
-                            base::Unretained(this), video_config->min_bitrate,
-                            video_config->max_bitrate),
+        base::BindRepeating(&OpenscreenSessionHost::GetVideoNetworkBandwidth,
+                            base::Unretained(this)),
         gpu_factories);
     video_stream_ = std::make_unique<VideoRtpStream>(
         std::move(video_sender), weak_factory_.GetWeakPtr(),
@@ -1015,19 +1014,9 @@
   }
 }
 
-int OpenscreenSessionHost::GetSuggestedVideoBitrate(int min_bitrate,
-                                                    int max_bitrate) const {
-  // First take the suggested bitrate based on the current bandwidth
-  // utilization.
-  int suggested = usable_bandwidth_;
-  if (audio_stream_) {
-    suggested -= audio_stream_->GetEncoderBitrate();
-  }
-
-  // Then limit it based on the frame sender configuration.
-  // TODO(crbug.com/40260069): we should also factor in device
-  // capability when determining which bitrate to use.
-  return std::clamp(suggested, min_bitrate, max_bitrate);
+int OpenscreenSessionHost::GetVideoNetworkBandwidth() const {
+  return audio_stream_ ? usable_bandwidth_ - audio_stream_->GetEncoderBitrate()
+                       : usable_bandwidth_;
 }
 
 void OpenscreenSessionHost::UpdateBandwidthEstimate() {
diff --git a/components/mirroring/service/openscreen_session_host.h b/components/mirroring/service/openscreen_session_host.h
index b87686d1..aae253c7 100644
--- a/components/mirroring/service/openscreen_session_host.h
+++ b/components/mirroring/service/openscreen_session_host.h
@@ -184,8 +184,8 @@
   // Callback by media::cast::VideoSender to report resource utilization.
   void ProcessFeedback(const media::VideoCaptureFeedback& feedback);
 
-  // Called by OpenscreenFrameSender to determine bitrate.
-  int GetSuggestedVideoBitrate(int min_bitrate, int max_bitrate) const;
+  // Called by media::cast::VideoSender to help determine the video bitrate.
+  int GetVideoNetworkBandwidth() const;
 
   // Called periodically to update the `bandwidth_estimate_`.
   void UpdateBandwidthEstimate();
diff --git a/components/mirroring/service/openscreen_session_host_unittest.cc b/components/mirroring/service/openscreen_session_host_unittest.cc
index ffd3df0..c96ca89 100644
--- a/components/mirroring/service/openscreen_session_host_unittest.cc
+++ b/components/mirroring/service/openscreen_session_host_unittest.cc
@@ -871,30 +871,25 @@
   constexpr int kMinVideoBitrate = 393216;
   constexpr int kMaxVideoBitrate = 1250000;
   // Default bitrate should be twice the minimum.
-  EXPECT_EQ(786432, session_host().GetSuggestedVideoBitrate(kMinVideoBitrate,
-                                                            kMaxVideoBitrate));
+  EXPECT_EQ(786432, session_host().GetVideoNetworkBandwidth());
 
   // If the estimate is below the minimum, it should stay at the minimum.
   session_host().forced_bandwidth_estimate_for_testing_ = 1000;
   session_host().UpdateBandwidthEstimate();
-  EXPECT_EQ(kMinVideoBitrate, session_host().GetSuggestedVideoBitrate(
-                                  kMinVideoBitrate, kMaxVideoBitrate));
+  EXPECT_EQ(kMinVideoBitrate, session_host().GetVideoNetworkBandwidth());
 
   // It should gradually reach the max bandwidth estimate when raised.
   session_host().forced_bandwidth_estimate_for_testing_ = 1000000;
   session_host().UpdateBandwidthEstimate();
-  EXPECT_EQ(432537, session_host().GetSuggestedVideoBitrate(kMinVideoBitrate,
-                                                            kMaxVideoBitrate));
+  EXPECT_EQ(432537, session_host().GetVideoNetworkBandwidth());
 
   session_host().UpdateBandwidthEstimate();
-  EXPECT_EQ(475790, session_host().GetSuggestedVideoBitrate(kMinVideoBitrate,
-                                                            kMaxVideoBitrate));
+  EXPECT_EQ(475790, session_host().GetVideoNetworkBandwidth());
   for (int i = 0; i < 20; ++i) {
     session_host().UpdateBandwidthEstimate();
   }
   // The max should be 80% of `forced_bandwidth_estimate_for_testing_`.
-  EXPECT_EQ(800000, session_host().GetSuggestedVideoBitrate(kMinVideoBitrate,
-                                                            kMaxVideoBitrate));
+  EXPECT_EQ(800000, session_host().GetVideoNetworkBandwidth());
 
   // The video bitrate should stay saturated at the cap when reached.
   session_host().forced_bandwidth_estimate_for_testing_ = kMaxVideoBitrate + 1;
@@ -902,8 +897,7 @@
     session_host().UpdateBandwidthEstimate();
   }
   // The max should be 80% of `kMaxVideoBitrate`.
-  EXPECT_EQ(1000000, session_host().GetSuggestedVideoBitrate(kMinVideoBitrate,
-                                                             kMaxVideoBitrate));
+  EXPECT_EQ(1000000, session_host().GetVideoNetworkBandwidth());
 
   StopSession();
 }
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index 0309dc9..ab1a8421 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -249,6 +249,10 @@
       "model_execution/model_execution_manager.h",
       "model_execution/model_execution_util.cc",
       "model_execution/model_execution_util.h",
+      "model_execution/on_device_context.cc",
+      "model_execution/on_device_context.h",
+      "model_execution/on_device_execution.cc",
+      "model_execution/on_device_execution.h",
       "model_execution/on_device_model_access_controller.cc",
       "model_execution/on_device_model_access_controller.h",
       "model_execution/on_device_model_adaptation_controller.cc",
diff --git a/components/optimization_guide/core/model_execution/on_device_context.cc b/components/optimization_guide/core/model_execution/on_device_context.cc
new file mode 100644
index 0000000..e2964e3e6
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/on_device_context.cc
@@ -0,0 +1,142 @@
+// 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 "components/optimization_guide/core/model_execution/on_device_context.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+
+namespace optimization_guide {
+
+OnDeviceOptions::Client::~Client() = default;
+
+OnDeviceOptions::OnDeviceOptions() = default;
+OnDeviceOptions::OnDeviceOptions(OnDeviceOptions&&) = default;
+OnDeviceOptions::~OnDeviceOptions() = default;
+
+OnDeviceOptions::OnDeviceOptions(const OnDeviceOptions& orig)
+    : model_client(orig.model_client->Clone()),
+      model_versions(orig.model_versions),
+      adapter(orig.adapter),
+      safety_checker(std::make_unique<SafetyChecker>(*orig.safety_checker)),
+      token_limits(orig.token_limits),
+      logger(orig.logger),
+      log_uploader(orig.log_uploader) {}
+
+bool OnDeviceOptions::ShouldUse() const {
+  return model_client->ShouldUse();
+}
+
+OnDeviceContext::OnDeviceContext(OnDeviceOptions opts,
+                                 ModelBasedCapabilityKey feature)
+    : opts_(std::move(opts)), feature_(feature) {}
+OnDeviceContext::~OnDeviceContext() = default;
+
+bool OnDeviceContext::SetInput(const google::protobuf::MessageLite& request) {
+  auto input =
+      opts_.adapter->ConstructInputString(request, /*want_input_context=*/true);
+  if (!input) {
+    return false;
+  }
+  session_.reset();
+  client_.reset();
+  input_ = std::move(input->input);
+  GetOrCreateSession();  // Start processing
+  return true;
+}
+
+mojo::Remote<on_device_model::mojom::Session>&
+OnDeviceContext::GetOrCreateSession() {
+  DCHECK(opts_.ShouldUse());
+  if (session_) {
+    return session_;
+  }
+  opts_.model_client->GetModelRemote()->StartSession(
+      session_.BindNewPipeAndPassReceiver());
+  session_.reset_on_disconnect();
+  progress_ = Progress{};
+  if (input_) {
+    AddContext(features::GetOnDeviceModelMinTokensForContext());
+  }
+  return session_;
+}
+
+void OnDeviceContext::CloneSession(
+    mojo::PendingReceiver<on_device_model::mojom::Session> clone,
+    proto::OnDeviceModelServiceRequest* logged_request,
+    bool ignore_context) {
+  auto& session = GetOrCreateSession();
+  CancelOptionalContext();
+  if (input_) {
+    base::UmaHistogramCounts10000(
+        base::StrCat({"OptimizationGuide.ModelExecution."
+                      "OnDeviceContextTokensProcessed.",
+                      GetStringNameForModelExecutionFeature(feature_)}),
+        progress_.tokens_processed_);
+    base::UmaHistogramBoolean(
+        base::StrCat({"OptimizationGuide.ModelExecution."
+                      "OnDeviceContextFinishedProcessing.",
+                      GetStringNameForModelExecutionFeature(feature_)}),
+        progress_.finished_processing_);
+    logged_request->set_input_context_num_tokens_processed(
+        progress_.tokens_processed_);
+    logged_request
+        ->set_time_from_input_context_processed_to_request_initiated_millis(
+            (progress_.cancelled_ - progress_.start_).InMilliseconds());
+    if (!ignore_context) {
+      logged_request->set_input_context_string(OnDeviceInputToString(*input_));
+    }
+  }
+  session->Clone(std::move(clone));
+}
+
+void OnDeviceContext::CancelOptionalContext() {
+  if (!progress_.cancelled_.is_null()) {
+    // Already cancelled.
+    return;
+  }
+  progress_.cancelled_ = base::Time::Now();
+  if (progress_.can_cancel_) {
+    client_.reset();
+  }
+}
+
+void OnDeviceContext::AddContext(uint32_t num_tokens) {
+  progress_.expected_tokens_ = num_tokens;
+  if (num_tokens == 0) {
+    // This only happens if MinTokens is 0, and we just consider the required
+    // chunk to be trivially complete, and move on to the next chunk.
+    OnComplete(0);
+    return;
+  }
+  auto options = on_device_model::mojom::InputOptions::New();
+  options->input = input_.Clone();
+  options->max_tokens = num_tokens;
+  options->token_offset = progress_.tokens_processed_;
+  session_->AddContext(std::move(options), client_.BindNewPipeAndPassRemote());
+}
+
+void OnDeviceContext::OnComplete(uint32_t tokens_processed) {
+  client_.reset();
+  progress_.tokens_processed_ += tokens_processed;
+
+  if (!progress_.cancelled_.is_null()) {
+    return;
+  }
+
+  // This means input has been fully processed.
+  if (tokens_processed < progress_.expected_tokens_) {
+    progress_.finished_processing_ = true;
+    return;
+  }
+
+  // Once the initial context is complete, we can cancel future context
+  // processing.
+  progress_.can_cancel_ = true;
+  if (progress_.tokens_processed_ < opts_.token_limits.max_context_tokens) {
+    AddContext(features::GetOnDeviceModelContextTokenChunkSize());
+  }
+}
+
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_execution/on_device_context.h b/components/optimization_guide/core/model_execution/on_device_context.h
new file mode 100644
index 0000000..2ed7988a
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/on_device_context.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 COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_CONTEXT_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_CONTEXT_H_
+
+#include <memory>
+
+#include "components/optimization_guide/core/model_execution/on_device_model_feature_adapter.h"
+#include "components/optimization_guide/core/model_execution/safety_checker.h"
+#include "components/optimization_guide/core/optimization_guide_logger.h"
+#include "components/optimization_guide/proto/model_quality_metadata.pb.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace optimization_guide {
+
+struct OnDeviceOptions final {
+  OnDeviceOptions();
+  OnDeviceOptions(const OnDeviceOptions&);
+  OnDeviceOptions(OnDeviceOptions&&);
+  ~OnDeviceOptions();
+
+  class Client {
+   public:
+    virtual ~Client() = 0;
+    // Create another client for the same model.
+    virtual std::unique_ptr<Client> Clone() const = 0;
+    // Called to check whether this client is still usable.
+    virtual bool ShouldUse() = 0;
+    // Called to retrieve connection the managed model.
+    virtual mojo::Remote<on_device_model::mojom::OnDeviceModel>&
+    GetModelRemote() = 0;
+    // Called to report a successful execution of the model.
+    virtual void OnResponseCompleted() = 0;
+  };
+
+  std::unique_ptr<Client> model_client;
+  proto::OnDeviceModelVersions model_versions;
+  scoped_refptr<const OnDeviceModelFeatureAdapter> adapter;
+  std::unique_ptr<SafetyChecker> safety_checker;
+  TokenLimits token_limits;
+
+  base::WeakPtr<OptimizationGuideLogger> logger;
+  base::WeakPtr<ModelQualityLogsUploaderService> log_uploader;
+
+  // Returns true if the on-device model may be used.
+  bool ShouldUse() const;
+};
+
+// Constructs an on-device session and populates it with input context.
+// Context is processed incrementally. After the min context size has been
+// processed, any pending context processing will be cancelled if an
+// CloneSession() call is made.
+class OnDeviceContext : public on_device_model::mojom::ContextClient {
+ public:
+  OnDeviceContext(OnDeviceOptions opts, ModelBasedCapabilityKey feature);
+  ~OnDeviceContext() override;
+
+  // Constructs the input context and begins processing it.
+  bool SetInput(const google::protobuf::MessageLite& request);
+
+  // Get the session that we've sent the input to, creating it if does not
+  // exist (e.g. due to a disconnect.)
+  mojo::Remote<on_device_model::mojom::Session>& GetOrCreateSession();
+
+  // Clones from the session to begin processing a request, terminating any
+  // optional processing, and logging data about the processing.
+  void CloneSession(
+      mojo::PendingReceiver<on_device_model::mojom::Session> clone,
+      proto::OnDeviceModelServiceRequest* logged_request,
+      bool ignore_context);
+
+  const OnDeviceOptions& opts() { return opts_; }
+
+  // Whether using this session is still allowed.
+  // This should be checked before called any other public methods.
+  bool CanUse() { return opts_.ShouldUse(); }
+
+ private:
+  void CancelOptionalContext();
+
+  void AddContext(uint32_t num_tokens);
+
+  // on_device_model::mojom::ContextClient:
+  void OnComplete(uint32_t tokens_processed) override;
+
+  struct Progress final {
+    // Whether the required (first) chunk has finished processing.
+    bool can_cancel_ = false;
+    // Whether all input has been fully processed.
+    bool finished_processing_ = false;
+    // Number of tokens in the chunk currently being processed.
+    uint32_t expected_tokens_ = 0;
+    // Total number of tokens processed so far.
+    uint32_t tokens_processed_ = 0;
+    // When processing began.
+    base::Time start_;
+    // When processing was cancelled to begin the first execution.
+    base::Time cancelled_;
+  };
+
+  OnDeviceOptions opts_;
+  ModelBasedCapabilityKey feature_;
+  mojo::Remote<on_device_model::mojom::Session> session_;
+  on_device_model::mojom::InputPtr input_;
+  Progress progress_;
+  mojo::Receiver<on_device_model::mojom::ContextClient> client_{this};
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_CONTEXT_H_
diff --git a/components/optimization_guide/core/model_execution/on_device_execution.cc b/components/optimization_guide/core/model_execution/on_device_execution.cc
new file mode 100644
index 0000000..45af30e
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/on_device_execution.cc
@@ -0,0 +1,629 @@
+// 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 "components/optimization_guide/core/model_execution/on_device_execution.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/stringprintf.h"
+#include "components/optimization_guide/core/model_execution/model_execution_util.h"
+#include "components/optimization_guide/core/model_execution/repetition_checker.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+
+namespace optimization_guide {
+
+namespace {
+
+using google::protobuf::RepeatedPtrField;
+using ModelExecutionError =
+    OptimizationGuideModelExecutionError::ModelExecutionError;
+
+void LogRequest(OptimizationGuideLogger* logger,
+                const proto::OnDeviceModelServiceRequest& logged_request) {
+  if (logger && logger->ShouldEnableDebugLogs()) {
+    OPTIMIZATION_GUIDE_LOGGER(
+        optimization_guide_common::mojom::LogSource::MODEL_EXECUTION, logger)
+        << "Executing model "
+        << (logged_request.input_context_string().empty()
+                ? ""
+                : base::StringPrintf(
+                      "with input context of %d tokens:\n%s\n",
+                      logged_request.input_context_num_tokens_processed(),
+                      logged_request.input_context_string().c_str()))
+        << "with string:\n"
+        << logged_request.execution_string();
+  }
+}
+
+void LogRawResponse(OptimizationGuideLogger* logger,
+                    ModelBasedCapabilityKey feature,
+                    const std::string& raw_response) {
+  if (logger && logger->ShouldEnableDebugLogs()) {
+    OPTIMIZATION_GUIDE_LOGGER(
+        optimization_guide_common::mojom::LogSource::MODEL_EXECUTION, logger)
+        << "Model generates raw response with "
+        << std::string(GetStringNameForModelExecutionFeature(feature)) << ":\n"
+        << raw_response;
+  }
+}
+
+void LogRepeatedResponse(OptimizationGuideLogger* logger,
+                         ModelBasedCapabilityKey feature,
+                         const std::string& repeated_response) {
+  if (logger && logger->ShouldEnableDebugLogs()) {
+    OPTIMIZATION_GUIDE_LOGGER(
+        optimization_guide_common::mojom::LogSource::MODEL_EXECUTION, logger)
+        << "Model generates repeated response with "
+        << std::string(GetStringNameForModelExecutionFeature(feature)) << ":\n"
+        << repeated_response;
+  }
+}
+
+void LogResponseHasRepeats(ModelBasedCapabilityKey feature, bool has_repeats) {
+  base::UmaHistogramBoolean(
+      base::StrCat(
+          {"OptimizationGuide.ModelExecution.OnDeviceResponseHasRepeats.",
+           GetStringNameForModelExecutionFeature(feature)}),
+      has_repeats);
+}
+
+void LogResponseCompleteTime(ModelBasedCapabilityKey feature,
+                             base::TimeDelta time_to_completion) {
+  base::UmaHistogramMediumTimes(
+      base::StrCat(
+          {"OptimizationGuide.ModelExecution.OnDeviceResponseCompleteTime.",
+           GetStringNameForModelExecutionFeature(feature)}),
+      time_to_completion);
+}
+
+void LogResponseCompleteTokens(ModelBasedCapabilityKey feature,
+                               uint32_t tokens) {
+  base::UmaHistogramCounts10000(
+      base::StrCat(
+          {"OptimizationGuide.ModelExecution.OnDeviceResponseCompleteTokens.",
+           GetStringNameForModelExecutionFeature(feature)}),
+      tokens);
+}
+
+std::string GenerateExecutionId() {
+  return "on-device:" + base::Uuid::GenerateRandomV4().AsLowercaseString();
+}
+
+}  // namespace
+
+void InvokeStreamingCallbackWithRemoteResult(
+    OptimizationGuideModelExecutionResultStreamingCallback callback,
+    OptimizationGuideModelExecutionResult result,
+    std::unique_ptr<ModelQualityLogEntry> log_entry) {
+  OptimizationGuideModelStreamingExecutionResult streaming_result;
+  if (log_entry && log_entry->log_ai_data_request() &&
+      log_entry->log_ai_data_request()->has_model_execution_info()) {
+    streaming_result.execution_info =
+        std::make_unique<proto::ModelExecutionInfo>(
+            log_entry->log_ai_data_request()->model_execution_info());
+  }
+  streaming_result.log_entry = std::move(log_entry);
+  if (result.response.has_value()) {
+    streaming_result.response = base::ok(
+        StreamingResponse{.response = *result.response, .is_complete = true});
+  } else {
+    streaming_result.response = base::unexpected(result.response.error());
+  }
+  callback.Run(std::move(streaming_result));
+}
+
+OnDeviceExecution::OnDeviceExecution(
+    ModelBasedCapabilityKey feature,
+    OnDeviceOptions opts,
+    ExecuteRemoteFn execute_remote_fn,
+    std::unique_ptr<google::protobuf::MessageLite> message,
+    std::unique_ptr<ResultLogger> logger,
+    OptimizationGuideModelExecutionResultStreamingCallback callback,
+    base::OnceCallback<void(bool)> cleanup_callback)
+    : feature_(feature),
+      opts_(std::move(opts)),
+      execute_remote_fn_(execute_remote_fn),
+      last_message_(std::move(message)),
+      histogram_logger_(std::move(logger)),
+      callback_(std::move(callback)),
+      cleanup_callback_(std::move(cleanup_callback)),
+      receiver_(this) {
+  log_.mutable_model_execution_info()
+      ->mutable_on_device_model_execution_info()
+      ->add_execution_infos();
+  start_ = base::TimeTicks::Now();
+  SetExecutionRequest(feature_, log_, *last_message_);
+  *(log_.mutable_model_execution_info()
+        ->mutable_on_device_model_execution_info()
+        ->mutable_model_versions()) = opts_.model_versions;
+  // Note: if on-device fails for some reason, the result will be changed.
+  histogram_logger_->set_result(Result::kUsedOnDevice);
+}
+
+OnDeviceExecution::~OnDeviceExecution() {
+  if (callback_) {
+    if (histogram_logger_) {
+      histogram_logger_->set_result(Result::kDestroyedWhileWaitingForResponse);
+    }
+    base::UmaHistogramMediumTimes(
+        base::StrCat({"OptimizationGuide.ModelExecution."
+                      "OnDeviceDestroyedWhileWaitingForResponseTime.",
+                      GetStringNameForModelExecutionFeature(feature_)}),
+        base::TimeTicks::Now() - start_);
+  }
+}
+
+proto::OnDeviceModelServiceRequest* OnDeviceExecution::MutableLoggedRequest() {
+  CHECK_GT(log_.model_execution_info()
+               .on_device_model_execution_info()
+               .execution_infos_size(),
+           0);
+  return log_.mutable_model_execution_info()
+      ->mutable_on_device_model_execution_info()
+      ->mutable_execution_infos(0)
+      ->mutable_request()
+      ->mutable_on_device_model_service_request();
+}
+
+proto::OnDeviceModelServiceResponse*
+OnDeviceExecution::MutableLoggedResponse() {
+  CHECK_GT(log_.model_execution_info()
+               .on_device_model_execution_info()
+               .execution_infos_size(),
+           0);
+  return log_.mutable_model_execution_info()
+      ->mutable_on_device_model_execution_info()
+      ->mutable_execution_infos(0)
+      ->mutable_response()
+      ->mutable_on_device_model_service_response();
+}
+
+void OnDeviceExecution::AddModelExecutionLogs(
+    google::protobuf::RepeatedPtrField<
+        proto::InternalOnDeviceModelExecutionInfo> logs) {
+  log_.mutable_model_execution_info()
+      ->mutable_on_device_model_execution_info()
+      ->mutable_execution_infos()
+      ->MergeFrom(std::move(logs));
+}
+
+void OnDeviceExecution::Cancel() {
+  CancelPendingResponse(Result::kCancelled);
+}
+
+void OnDeviceExecution::BeginExecution(OnDeviceContext& context,
+                                       const SamplingParams& sampling_params) {
+  auto input = opts_.adapter->ConstructInputString(
+      *last_message_, /*want_input_context=*/false);
+  if (!input) {
+    FallbackToRemote(Result::kFailedConstructingMessage);
+    return;
+  }
+
+  auto* logged_request = MutableLoggedRequest();
+
+  // Terminate optional context processing and log the context info.
+  context.CloneSession(session_.BindNewPipeAndPassReceiver(), logged_request,
+                       input->should_ignore_input_context);
+
+  logged_request->set_execution_string(input->ToString());
+  // TODO(crbug.com/302327957): Probably do some math to get the accurate number
+  // here.
+  logged_request->set_execution_num_tokens_processed(
+      opts_.token_limits.max_execute_tokens);
+  LogRequest(opts_.logger.get(), *logged_request);
+
+  auto options = on_device_model::mojom::InputOptions::New();
+  options->input = std::move(input->input);
+  options->max_tokens = opts_.token_limits.max_execute_tokens;
+  options->ignore_context = input->should_ignore_input_context;
+  options->max_output_tokens = opts_.token_limits.max_output_tokens;
+  options->top_k = sampling_params.top_k;
+  options->temperature = sampling_params.temperature;
+
+  opts_.safety_checker->RunRequestChecks(
+      *last_message_,
+      base::BindOnce(&OnDeviceExecution::OnRequestSafetyResult,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(options)));
+}
+
+void OnDeviceExecution::OnRequestSafetyResult(
+    on_device_model::mojom::InputOptionsPtr options,
+    SafetyChecker::Result safety_result) {
+  if (safety_result.failed_to_run) {
+    FallbackToRemote(Result::kFailedConstructingMessage);
+    return;
+  }
+  // Log the check executions.
+  AddModelExecutionLogs(std::move(safety_result.logs));
+
+  // Handle the result.
+  if (safety_result.is_unsafe || safety_result.is_unsupported_language) {
+    if (histogram_logger_) {
+      histogram_logger_->set_result(Result::kRequestUnsafe);
+    }
+    if (features::GetOnDeviceModelRetractUnsafeContent()) {
+      CancelPendingResponse(Result::kRequestUnsafe,
+                            safety_result.is_unsupported_language
+                                ? ModelExecutionError::kUnsupportedLanguage
+                                : ModelExecutionError::kFiltered);
+      return;
+    }
+  }
+  BeginRequestExecution(std::move(options));
+}
+
+void OnDeviceExecution::BeginRequestExecution(
+    on_device_model::mojom::InputOptionsPtr options) {
+  session_->Execute(std::move(options), receiver_.BindNewPipeAndPassRemote());
+  receiver_.set_disconnect_handler(base::BindOnce(
+      &OnDeviceExecution::OnResponderDisconnect, base::Unretained(this)));
+}
+
+// on_device_model::mojom::StreamingResponder:
+void OnDeviceExecution::OnResponse(
+    on_device_model::mojom::ResponseChunkPtr chunk) {
+  proto::OnDeviceModelServiceResponse* logged_response =
+      MutableLoggedResponse();
+
+  if (current_response_.empty()) {
+    base::TimeDelta time_to_first_response = base::TimeTicks::Now() - start_;
+    base::UmaHistogramMediumTimes(
+        base::StrCat(
+            {"OptimizationGuide.ModelExecution.OnDeviceFirstResponseTime.",
+             GetStringNameForModelExecutionFeature(feature_)}),
+        time_to_first_response);
+    logged_response->set_time_to_first_response_millis(
+        time_to_first_response.InMilliseconds());
+  }
+
+  current_response_ += chunk->text;
+  num_unchecked_response_tokens_++;
+  num_response_tokens_++;
+
+  if (HasRepeatingSuffix(current_response_)) {
+    // If a repeat is detected, halt the response, and cancel/finish early.
+    receiver_.reset();
+    logged_response->set_has_repeats(true);
+    if (features::GetOnDeviceModelRetractRepeats()) {
+      LogRepeatedResponse(opts_.logger.get(), feature_, current_response_);
+      logged_response->set_status(
+          proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED);
+      CancelPendingResponse(Result::kResponseHadRepeats,
+                            ModelExecutionError::kFiltered);
+      return;
+    }
+
+    // Artificially send the OnComplete event to finish processing.
+    OnComplete(on_device_model::mojom::ResponseSummary::New());
+    return;
+  }
+
+  if (!opts_.safety_checker->safety_cfg().CanCheckPartialOutput(
+          num_response_tokens_, num_unchecked_response_tokens_)) {
+    // Not enough new data to be worth re-evaluating yet.
+    return;
+  }
+
+  num_unchecked_response_tokens_ = 0;
+  RunRawOutputSafetyCheck(ResponseCompleteness::kPartial);
+}
+
+void OnDeviceExecution::OnComplete(
+    on_device_model::mojom::ResponseSummaryPtr summary) {
+  receiver_.reset();  // Suppress expected disconnect
+
+  proto::OnDeviceModelServiceResponse* logged_response =
+      MutableLoggedResponse();
+  LogResponseHasRepeats(feature_, logged_response->has_repeats());
+  LogResponseCompleteTokens(feature_, num_response_tokens_);
+  base::TimeDelta time_to_completion = base::TimeTicks::Now() - start_;
+  LogResponseCompleteTime(feature_, time_to_completion);
+  logged_response->set_time_to_completion_millis(
+      time_to_completion.InMilliseconds());
+
+  opts_.model_client->OnResponseCompleted();
+
+  RunRawOutputSafetyCheck(ResponseCompleteness::kComplete);
+}
+
+void OnDeviceExecution::OnResponderDisconnect() {
+  // OnComplete resets the receiver, so this implies that the response is
+  // incomplete and there was either a service crash or model eviction.
+  receiver_.reset();
+  if (features::GetOnDeviceFallbackToServerOnDisconnect()) {
+    FallbackToRemote(Result::kDisconnectAndMaybeFallback);
+  } else {
+    CancelPendingResponse(Result::kDisconnectAndCancel);
+  }
+}
+
+void OnDeviceExecution::RunRawOutputSafetyCheck(
+    ResponseCompleteness completeness) {
+  opts_.safety_checker->RunRawOutputCheck(
+      current_response_, completeness,
+      base::BindOnce(&OnDeviceExecution::OnRawOutputSafetyResult,
+                     weak_ptr_factory_.GetWeakPtr(), current_response_.size(),
+                     completeness));
+}
+
+void OnDeviceExecution::OnRawOutputSafetyResult(
+    size_t raw_output_size,
+    ResponseCompleteness completeness,
+    SafetyChecker::Result safety_result) {
+  if (safety_result.failed_to_run) {
+    FallbackToRemote(Result::kFailedConstructingMessage);
+    return;
+  }
+  if (safety_result.is_unsafe || safety_result.is_unsupported_language) {
+    if (histogram_logger_) {
+      histogram_logger_->set_result(Result::kUsedOnDeviceOutputUnsafe);
+    }
+    AddModelExecutionLogs(std::move(safety_result.logs));
+    if (features::GetOnDeviceModelRetractUnsafeContent()) {
+      CancelPendingResponse(Result::kUsedOnDeviceOutputUnsafe,
+                            safety_result.is_unsupported_language
+                                ? ModelExecutionError::kUnsupportedLanguage
+                                : ModelExecutionError::kFiltered);
+
+      return;
+    }
+  }
+  if (completeness == ResponseCompleteness::kComplete) {
+    AddModelExecutionLogs(std::move(safety_result.logs));
+  }
+  latest_safe_raw_output_.length = raw_output_size;
+  MaybeParseResponse(completeness);
+}
+
+void OnDeviceExecution::MaybeParseResponse(ResponseCompleteness completeness) {
+  if (completeness == ResponseCompleteness::kPartial &&
+      features::ShouldUseTextSafetyRemoteFallbackForEligibleFeatures()) {
+    // We don't send streaming responses in this mode.
+    return;
+  }
+
+  if (!opts_.adapter->ShouldParseResponse(completeness)) {
+    return;
+  }
+
+  std::string safe_response =
+      current_response_.substr(0, latest_safe_raw_output_.length);
+  LogRawResponse(opts_.logger.get(), feature_, safe_response);
+  MutableLoggedResponse()->set_output_string(safe_response);
+  size_t previous_response_pos = latest_response_pos_;
+  latest_response_pos_ = latest_safe_raw_output_.length;
+  opts_.adapter->ParseResponse(
+      *last_message_, safe_response, previous_response_pos,
+      base::BindOnce(&OnDeviceExecution::OnParsedResponse,
+                     weak_ptr_factory_.GetWeakPtr(), completeness));
+}
+
+void OnDeviceExecution::OnParsedResponse(
+    ResponseCompleteness completeness,
+    base::expected<proto::Any, ResponseParsingError> output) {
+  if (!output.has_value()) {
+    switch (output.error()) {
+      case ResponseParsingError::kRejectedPii:
+        MutableLoggedResponse()->set_status(
+            proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED);
+        CancelPendingResponse(Result::kContainedPII,
+                              ModelExecutionError::kFiltered);
+        return;
+      case ResponseParsingError::kFailed:
+        CancelPendingResponse(Result::kFailedConstructingResponseMessage,
+                              ModelExecutionError::kGenericFailure);
+        return;
+    }
+  }
+  opts_.safety_checker->RunResponseChecks(
+      *last_message_, *output, completeness,
+      base::BindOnce(&OnDeviceExecution::OnResponseSafetyResult,
+                     weak_ptr_factory_.GetWeakPtr(), completeness, *output));
+}
+
+void OnDeviceExecution::OnResponseSafetyResult(
+    ResponseCompleteness completeness,
+    proto::Any output,
+    SafetyChecker::Result safety_result) {
+  if (safety_result.failed_to_run) {
+    FallbackToRemote(Result::kFailedConstructingMessage);
+    return;
+  }
+  if (completeness == ResponseCompleteness::kComplete ||
+      safety_result.is_unsafe || safety_result.is_unsupported_language) {
+    AddModelExecutionLogs(std::move(safety_result.logs));
+  }
+  if (safety_result.is_unsafe || safety_result.is_unsupported_language) {
+    if (histogram_logger_) {
+      histogram_logger_->set_result(Result::kUsedOnDeviceOutputUnsafe);
+    }
+    if (features::GetOnDeviceModelRetractUnsafeContent()) {
+      CancelPendingResponse(Result::kUsedOnDeviceOutputUnsafe,
+                            safety_result.is_unsupported_language
+                                ? ModelExecutionError::kUnsupportedLanguage
+                                : ModelExecutionError::kFiltered);
+
+      return;
+    }
+  }
+  if (completeness == ResponseCompleteness::kPartial) {
+    SendPartialResponseCallback(output);
+    return;
+  }
+
+  if (features::ShouldUseTextSafetyRemoteFallbackForEligibleFeatures()) {
+    RunTextSafetyRemoteFallback(std::move(output));
+    return;
+  }
+
+  SendSuccessCompletionCallback(output);
+}
+
+void OnDeviceExecution::RunTextSafetyRemoteFallback(
+    proto::Any success_response_metadata) {
+  auto ts_request = opts_.adapter->ConstructTextSafetyRequest(
+      *last_message_, current_response_);
+  if (!ts_request) {
+    CancelPendingResponse(Result::kFailedConstructingRemoteTextSafetyRequest,
+                          ModelExecutionError::kGenericFailure);
+    return;
+  }
+
+  proto::InternalOnDeviceModelExecutionInfo remote_ts_model_execution_info;
+  auto* ts_request_log = remote_ts_model_execution_info.mutable_request()
+                             ->mutable_text_safety_model_request();
+  ts_request_log->set_text(ts_request->text());
+  ts_request_log->set_url(ts_request->url());
+
+  execute_remote_fn_.Run(
+      ModelBasedCapabilityKey::kTextSafety, *ts_request, std::nullopt,
+      /*log_ai_data_request=*/nullptr,
+      base::BindOnce(&OnDeviceExecution::OnTextSafetyRemoteResponse,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     std::move(remote_ts_model_execution_info),
+                     std::move(success_response_metadata)));
+}
+
+void OnDeviceExecution::OnTextSafetyRemoteResponse(
+    proto::InternalOnDeviceModelExecutionInfo remote_ts_model_execution_info,
+    proto::Any success_response_metadata,
+    OptimizationGuideModelExecutionResult result,
+    std::unique_ptr<ModelQualityLogEntry> remote_log_entry) {
+  bool is_unsafe =
+      !result.response.has_value() &&
+      result.response.error().error() ==
+          OptimizationGuideModelExecutionError::ModelExecutionError::kFiltered;
+  if (remote_log_entry) {
+    auto* ts_response_log = remote_ts_model_execution_info.mutable_response()
+                                ->mutable_text_safety_model_response();
+    ts_response_log->set_server_execution_id(
+        remote_log_entry->model_execution_id());
+    ts_response_log->set_is_unsafe(is_unsafe);
+  }
+  *(log_.mutable_model_execution_info()
+        ->mutable_on_device_model_execution_info()
+        ->add_execution_infos()) = remote_ts_model_execution_info;
+
+  if (is_unsafe) {
+    CancelPendingResponse(Result::kUsedOnDeviceOutputUnsafe,
+                          ModelExecutionError::kFiltered);
+    return;
+  }
+
+  if (!result.response.has_value()) {
+    CancelPendingResponse(Result::kTextSafetyRemoteRequestFailed,
+                          ModelExecutionError::kGenericFailure);
+    return;
+  }
+
+  SendSuccessCompletionCallback(success_response_metadata);
+}
+
+void OnDeviceExecution::FallbackToRemote(Result result) {
+  if (histogram_logger_) {
+    histogram_logger_->set_result(result);
+  }
+  auto self = weak_ptr_factory_.GetWeakPtr();
+  execute_remote_fn_.Run(
+      feature_, *last_message_, std::nullopt,
+      std::make_unique<proto::LogAiDataRequest>(std::move(log_)),
+      base::BindOnce(&InvokeStreamingCallbackWithRemoteResult,
+                     std::move(callback_)));
+  if (self) {
+    self->Cleanup(/*healthy=*/false);
+  }
+}
+
+void OnDeviceExecution::CancelPendingResponse(Result result,
+                                              ModelExecutionError error) {
+  if (!callback_) {
+    return;
+  }
+  if (histogram_logger_) {
+    histogram_logger_->set_result(result);
+  }
+  OptimizationGuideModelExecutionError og_error =
+      OptimizationGuideModelExecutionError::FromModelExecutionError(error);
+  std::unique_ptr<ModelQualityLogEntry> log_entry;
+  std::unique_ptr<proto::ModelExecutionInfo> model_execution_info;
+  if (og_error.ShouldLogModelQuality()) {
+    log_entry = std::make_unique<ModelQualityLogEntry>(opts_.log_uploader);
+    log_entry->log_ai_data_request()->MergeFrom(log_);
+    std::string model_execution_id = GenerateExecutionId();
+    log_entry->set_model_execution_id(model_execution_id);
+    model_execution_info = std::make_unique<proto::ModelExecutionInfo>(
+        log_entry->log_ai_data_request()->model_execution_info());
+    model_execution_info->set_execution_id(model_execution_id);
+    model_execution_info->set_model_execution_error_enum(
+        static_cast<uint32_t>(og_error.error()));
+  }
+  auto self = weak_ptr_factory_.GetWeakPtr();
+  std::move(callback_).Run(OptimizationGuideModelStreamingExecutionResult(
+      base::unexpected(og_error), /*provided_by_on_device=*/true,
+      std::move(log_entry), std::move(model_execution_info)));
+  if (self) {
+    self->Cleanup(/*healthy=*/true);
+  }
+}
+
+void OnDeviceExecution::SendPartialResponseCallback(
+    const proto::Any& success_response_metadata) {
+  callback_.Run(OptimizationGuideModelStreamingExecutionResult(
+      base::ok(StreamingResponse{.response = success_response_metadata,
+                                 .is_complete = false}),
+      /*provided_by_on_device=*/true, /*log_entry=*/nullptr));
+}
+
+void OnDeviceExecution::SendSuccessCompletionCallback(
+    const proto::Any& success_response_metadata) {
+  // Complete the log entry and promise it to the ModelQualityUploaderService.
+  std::unique_ptr<ModelQualityLogEntry> log_entry;
+  std::unique_ptr<proto::ModelExecutionInfo> model_execution_info;
+  SetExecutionResponse(feature_, log_, success_response_metadata);
+  MutableLoggedResponse()->set_status(
+      proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_SUCCESS);
+  log_entry = std::make_unique<ModelQualityLogEntry>(opts_.log_uploader);
+  log_entry->log_ai_data_request()->MergeFrom(log_);
+  std::string model_execution_id = GenerateExecutionId();
+  log_entry->set_model_execution_id(model_execution_id);
+  model_execution_info =
+      std::make_unique<proto::ModelExecutionInfo>(log_.model_execution_info());
+  model_execution_info->set_execution_id(model_execution_id);
+  log_.Clear();
+
+  // Return the execution response.
+  auto self = weak_ptr_factory_.GetWeakPtr();
+  std::move(callback_).Run(OptimizationGuideModelStreamingExecutionResult(
+      base::ok(StreamingResponse{.response = success_response_metadata,
+                                 .is_complete = true}),
+      /*provided_by_on_device=*/true, std::move(log_entry),
+      std::move(model_execution_info)));
+  if (self) {
+    self->Cleanup(/*healthy=*/true);
+  }
+}
+
+void OnDeviceExecution::Cleanup(bool healthy) {
+  weak_ptr_factory_.InvalidateWeakPtrs();
+  session_.reset();
+  receiver_.reset();
+  callback_.Reset();
+  log_.Clear();
+  current_response_.clear();
+  histogram_logger_.reset();
+  std::move(cleanup_callback_).Run(healthy);
+}
+
+OnDeviceExecution::SafeRawOutput::SafeRawOutput() = default;
+OnDeviceExecution::SafeRawOutput::~SafeRawOutput() = default;
+
+OnDeviceExecution::ResultLogger::~ResultLogger() {
+  base::UmaHistogramEnumeration(
+      base::StrCat(
+          {"OptimizationGuide.ModelExecution.OnDeviceExecuteModelResult.",
+           GetStringNameForModelExecutionFeature(feature_)}),
+      result_);
+}
+
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_execution/on_device_execution.h b/components/optimization_guide/core/model_execution/on_device_execution.h
new file mode 100644
index 0000000..5be081c2
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/on_device_execution.h
@@ -0,0 +1,271 @@
+// 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 COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_EXECUTION_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_EXECUTION_H_
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "base/functional/callback_forward.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "components/optimization_guide/core/model_execution/feature_keys.h"
+#include "components/optimization_guide/core/model_execution/on_device_context.h"
+#include "components/optimization_guide/core/model_execution/on_device_model_feature_adapter.h"
+#include "components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h"
+#include "components/optimization_guide/core/model_execution/safety_checker.h"
+#include "components/optimization_guide/core/model_execution/substitution.h"
+#include "components/optimization_guide/core/model_quality/model_quality_logs_uploader_service.h"
+#include "components/optimization_guide/core/optimization_guide_model_executor.h"
+#include "components/optimization_guide/proto/model_quality_metadata.pb.h"
+#include "components/optimization_guide/proto/model_quality_service.pb.h"
+#include "components/optimization_guide/proto/text_safety_model_metadata.pb.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/on_device_model/public/mojom/on_device_model.mojom.h"
+
+namespace optimization_guide {
+
+using ExecuteRemoteFn = base::RepeatingCallback<void(
+    ModelBasedCapabilityKey feature,
+    const google::protobuf::MessageLite&,
+    std::optional<base::TimeDelta> timeout,
+    std::unique_ptr<proto::LogAiDataRequest>,
+    OptimizationGuideModelExecutionResultCallback)>;
+
+void InvokeStreamingCallbackWithRemoteResult(
+    OptimizationGuideModelExecutionResultStreamingCallback callback,
+    OptimizationGuideModelExecutionResult result,
+    std::unique_ptr<ModelQualityLogEntry> log_entry);
+
+// The state for an ongoing ExecuteModel() call.
+class OnDeviceExecution final
+    : public on_device_model::mojom::StreamingResponder {
+ public:
+  // Possible outcomes of ExecuteModel().
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class Result {
+    // On-device was not used.
+    kOnDeviceNotUsed = 0,
+    // On-device was used, and it completed successfully.
+    kUsedOnDevice = 1,
+    // Failed constructing message, and used server.
+    kFailedConstructingMessage = 2,
+    // Got a response from on-device, but failed constructing the message.
+    kFailedConstructingResponseMessage = 3,
+    // Timed out and used server.
+    kTimedOut = 4,
+    // Received a disconnect while waiting for response. This may trigger
+    // fallback to another model, e.g. on the server, if configured.
+    kDisconnectAndMaybeFallback = 5,
+    // Received a disconnect while waiting for response and cancelled.
+    kDisconnectAndCancel = 6,
+    // Response was cancelled because ExecuteModel() was called while waiting
+    // for response.
+    kCancelled = 7,
+    // SessionImpl was destroyed while waiting for a response.
+    kDestroyedWhileWaitingForResponse = 8,
+    // On-device was used, it completed successfully, but the output is
+    // considered unsafe.
+    kUsedOnDeviceOutputUnsafe = 9,
+    // On-device was used, but the output was rejected (because contained PII).
+    kContainedPII = 10,
+    // On-device was used, but the output was rejected because it had repeats.
+    kResponseHadRepeats = 11,
+    // On-device was used and the output was complete but the output was
+    // rejected since it did not have the required safety scores.
+    kResponseCompleteButNoRequiredSafetyScores = 12,
+    // On-device was used and completed successfully, but the output was not in
+    // a language that could be reliably evaluated for safety.
+    kUsedOnDeviceOutputUnsupportedLanguage = 13,
+    // On-device was used and completed successfully, but failed constructing
+    // the text safety remote request.
+    kFailedConstructingRemoteTextSafetyRequest = 14,
+    // On-device was used and completed successfully, but the text safety remote
+    // request failed for some reason.
+    kTextSafetyRemoteRequestFailed = 15,
+    // On-device was used, but the request was considered unsafe.
+    kRequestUnsafe = 16,
+
+    // Please update OptimizationGuideOnDeviceResult in
+    // optimization/enums.xml.
+    kMaxValue = kRequestUnsafe,
+  };
+
+  // Used to log the result of ExecuteModel.
+  class ResultLogger {
+   public:
+    explicit ResultLogger(ModelBasedCapabilityKey feature)
+        : feature_(feature) {}
+    ~ResultLogger();
+
+    void set_result(Result result) { result_ = result; }
+
+   private:
+    const ModelBasedCapabilityKey feature_;
+    Result result_ = Result::kOnDeviceNotUsed;
+  };
+
+  explicit OnDeviceExecution(
+      ModelBasedCapabilityKey feature,
+      OnDeviceOptions opts,
+      ExecuteRemoteFn execute_remote_fn,
+      std::unique_ptr<google::protobuf::MessageLite> message,
+      std::unique_ptr<ResultLogger> logger,
+      OptimizationGuideModelExecutionResultStreamingCallback callback,
+      base::OnceCallback<void(bool)> cleanup_callback);
+  ~OnDeviceExecution() final;
+
+  // Begin processing the request.
+  void BeginExecution(OnDeviceContext& context,
+                      const SamplingParams& sampling_params);
+
+  // Cancels the execution.
+  void Cancel();
+
+ private:
+  // Returns the mutable on-device model service request for logging.
+  proto::OnDeviceModelServiceRequest* MutableLoggedRequest();
+
+  // Returns the mutable on-device model service response for logging.
+  proto::OnDeviceModelServiceResponse* MutableLoggedResponse();
+
+  // Adds a collection of model execution logs to the request log.
+  void AddModelExecutionLogs(google::protobuf::RepeatedPtrField<
+                             proto::InternalOnDeviceModelExecutionInfo> logs);
+
+  // Callback invoked with RequestSafetyCheck result.
+  // Calls BeginRequestExecution if safety checks pass.
+  void OnRequestSafetyResult(on_device_model::mojom::InputOptionsPtr options,
+                             SafetyChecker::Result safety_result);
+
+  // Begins request execution (leads to OnResponse/OnComplete, which will
+  // call RunRawOutputSafetyCheck).
+  void BeginRequestExecution(on_device_model::mojom::InputOptionsPtr options);
+
+  // on_device_model::mojom::StreamingResponder:
+  void OnResponse(on_device_model::mojom::ResponseChunkPtr chunk) override;
+  void OnComplete(on_device_model::mojom::ResponseSummaryPtr summary) override;
+  void OnResponderDisconnect();
+
+  // Evaluates raw output safety (leads to OnRawOutputSafetyResult).
+  void RunRawOutputSafetyCheck(ResponseCompleteness completeness);
+
+  // Called when output safety check completes.
+  // Calls MaybeParseResponse when there is more safe output.
+  void OnRawOutputSafetyResult(size_t raw_output_size,
+                               ResponseCompleteness completeness,
+                               SafetyChecker::Result safety_result);
+
+  // Called to parse the latest safe raw output.
+  // Leads to OnParsedResponse.
+  void MaybeParseResponse(ResponseCompleteness completeness);
+
+  // Called when a response has finished parsing.
+  // Begins response safety evaluation, leads to OnResponseSafetyResult.
+  void OnParsedResponse(
+      ResponseCompleteness completeness,
+      base::expected<proto::Any, ResponseParsingError> output);
+
+  // Called when response safety check completes.
+  // Either fails, sends the result or calls RunTextSafetyRemoteFallback.
+  void OnResponseSafetyResult(ResponseCompleteness completeness,
+                              proto::Any output,
+                              SafetyChecker::Result safety_result);
+
+  // Called to run the text safety remote fallback. Will invoke
+  // OnTextSafetyRemoteResponse when done.
+  void RunTextSafetyRemoteFallback(proto::Any success_response_metadata);
+
+  // Callback invoked when the text safety remote fallback response comes
+  // back. Will invoke the session's completion callback and destroy state.
+  void OnTextSafetyRemoteResponse(
+      proto::InternalOnDeviceModelExecutionInfo remote_ts_model_execution_info,
+      proto::Any success_response_metadata,
+      OptimizationGuideModelExecutionResult result,
+      std::unique_ptr<ModelQualityLogEntry> remote_log_entry);
+
+  // Terminates on-device processing as unhealthy and falls back to remote
+  // execution to provide the result to the caller.
+  void FallbackToRemote(Result result);
+
+  // Sends an error result and terminates on-device processing as healthy.
+  void CancelPendingResponse(
+      Result result,
+      OptimizationGuideModelExecutionError::ModelExecutionError error =
+          OptimizationGuideModelExecutionError::ModelExecutionError::
+              kCancelled);
+
+  // Sends the partial response callback, and does NOT terminate processing.
+  void SendPartialResponseCallback(const proto::Any& success_response_metadata);
+
+  // Sends a successful result and terminates on-device processing as healthy.
+  void SendSuccessCompletionCallback(
+      const proto::Any& success_response_metadata);
+
+  // Called after terminating to release all held resources and notify owner
+  // that this object is safe to destroy.
+  void Cleanup(bool healthy);
+
+  const ModelBasedCapabilityKey feature_;
+  const OnDeviceOptions opts_;
+  ExecuteRemoteFn execute_remote_fn_;
+
+  mojo::Remote<on_device_model::mojom::Session> session_;
+
+  // The request message.
+  std::unique_ptr<google::protobuf::MessageLite> last_message_;
+  // Time ExecuteModel() was called.
+  base::TimeTicks start_;
+  // Used to log the result of ExecuteModel().
+  std::unique_ptr<ResultLogger> histogram_logger_;
+  // Used to log execution information for the request.
+  proto::LogAiDataRequest log_;
+
+  // Response received so far.
+  std::string current_response_;
+
+  // How many tokens (response chunks) have been added.
+  size_t num_response_tokens_ = 0;
+  // How many tokens (response chunks) have been added since the last safety
+  // evaluation was requested.
+  size_t num_unchecked_response_tokens_ = 0;
+
+  struct SafeRawOutput {
+    SafeRawOutput();
+    ~SafeRawOutput();
+    // How much of 'current_response' was checked.
+    size_t length = 0;
+  };
+  // The longest response that has passed the raw output text safety check.
+  SafeRawOutput latest_safe_raw_output_;
+
+  // The last position in the response that has been streamed to the
+  // responder.
+  size_t latest_response_pos_ = 0;
+
+  // Callback to provide the execution result.
+  OptimizationGuideModelExecutionResultStreamingCallback callback_;
+
+  // Callback to notify the owning session that on-device execution has
+  // terminated, and that this object is safe to destroy.
+  // Should pass true to indicate healthy completion, or false if unhealthy.
+  base::OnceCallback<void(bool)> cleanup_callback_;
+
+  mojo::Receiver<on_device_model::mojom::StreamingResponder> receiver_;
+
+  // Factory for weak pointers related to this session that are invalidated
+  // with the request state.
+  base::WeakPtrFactory<OnDeviceExecution> weak_ptr_factory_{this};
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_EXECUTION_H_
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
index 2211633..72c0fbf 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
@@ -177,7 +177,7 @@
   auto* adaptation_metadata = GetFeatureMetadata(feature);
   CHECK(adaptation_metadata);
 
-  SessionImpl::OnDeviceOptions opts;
+  OnDeviceOptions opts;
   opts.model_client = std::make_unique<OnDeviceModelClient>(
       feature, weak_ptr_factory_.GetWeakPtr(), model_paths,
       base::OptionalFromPtr(adaptation_metadata->asset_paths()));
@@ -445,7 +445,7 @@
 OnDeviceModelServiceController::OnDeviceModelClient::~OnDeviceModelClient() =
     default;
 
-std::unique_ptr<SessionImpl::OnDeviceModelClient>
+std::unique_ptr<OnDeviceOptions::Client>
 OnDeviceModelServiceController::OnDeviceModelClient::Clone() const {
   return std::make_unique<OnDeviceModelServiceController::OnDeviceModelClient>(
       feature_, controller_, model_paths_, adaptation_assets_);
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller.h b/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
index ee90e780..3dcf0e2 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
@@ -143,7 +143,7 @@
   }
 
  private:
-  class OnDeviceModelClient final : public SessionImpl::OnDeviceModelClient {
+  class OnDeviceModelClient final : public OnDeviceOptions::Client {
    public:
     OnDeviceModelClient(
         ModelBasedCapabilityKey feature,
@@ -152,7 +152,7 @@
         base::optional_ref<const on_device_model::AdaptationAssetPaths>
             adaptation_assets);
     ~OnDeviceModelClient() override;
-    std::unique_ptr<SessionImpl::OnDeviceModelClient> Clone() const override;
+    std::unique_ptr<OnDeviceOptions::Client> Clone() const override;
     bool ShouldUse() override;
     mojo::Remote<on_device_model::mojom::OnDeviceModel>& GetModelRemote()
         override;
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
index 2b6ab58..170ab09d 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
@@ -26,6 +26,7 @@
 #include "components/optimization_guide/core/model_execution/feature_keys.h"
 #include "components/optimization_guide/core/model_execution/model_execution_features.h"
 #include "components/optimization_guide/core/model_execution/model_execution_prefs.h"
+#include "components/optimization_guide/core/model_execution/on_device_execution.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_access_controller.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_adaptation_loader.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_execution_proto_value_utils.h"
@@ -65,7 +66,7 @@
 namespace {
 
 using ::on_device_model::mojom::LoadModelResult;
-using ExecuteModelResult = SessionImpl::ExecuteModelResult;
+using ExecuteModelResult = ::optimization_guide::OnDeviceExecution::Result;
 
 using ::testing::AllOf;
 using ::testing::ElementsAre;
@@ -353,7 +354,7 @@
   OptimizationGuideLogger logger_;
 };
 
-TEST_F(OnDeviceModelServiceControllerTest, ScoreNullBeforeContext) {
+TEST_F(OnDeviceModelServiceControllerTest, ScoreBeforeContext) {
   Initialize(standard_assets_);
 
   base::HistogramTester histogram_tester;
@@ -361,7 +362,7 @@
   EXPECT_TRUE(session);
   base::test::TestFuture<std::optional<float>> score_future;
   session->Score("token", score_future.GetCallback());
-  EXPECT_EQ(score_future.Get(), std::nullopt);
+  EXPECT_NE(score_future.Get(), std::nullopt);
 }
 
 TEST_F(OnDeviceModelServiceControllerTest, ScorePresentAfterContext) {
@@ -378,7 +379,7 @@
   EXPECT_EQ(score_future.Get(), 0.5);
 }
 
-TEST_F(OnDeviceModelServiceControllerTest, ScoreNullAfterExecute) {
+TEST_F(OnDeviceModelServiceControllerTest, ScoreAfterExecute) {
   Initialize(standard_assets_);
 
   base::HistogramTester histogram_tester;
@@ -391,7 +392,7 @@
 
   base::test::TestFuture<std::optional<float>> score_future;
   session->Score("token", score_future.GetCallback());
-  EXPECT_EQ(score_future.Get(), std::nullopt);
+  EXPECT_NE(score_future.Get(), std::nullopt);
 }
 
 TEST_F(OnDeviceModelServiceControllerTest, BaseModelExecutionSuccess) {
@@ -2071,9 +2072,7 @@
       "OptimizationGuide.ModelExecution.OnDeviceExecuteModelResult.Compose",
       ExecuteModelResult::kFailedConstructingMessage, 1);
   EXPECT_TRUE(remote_execute_called_);
-  // We never actually executed the request on-device so it is expected to not
-  // have created a log entry.
-  EXPECT_FALSE(log_ai_data_request_passed_to_remote_);
+  EXPECT_FALSE(log_ai_data_request_passed_to_remote_->compose().has_response());
 }
 
 TEST_F(OnDeviceModelServiceControllerTest,
diff --git a/components/optimization_guide/core/model_execution/session_impl.cc b/components/optimization_guide/core/model_execution/session_impl.cc
index 5428c4e..efc74cf4 100644
--- a/components/optimization_guide/core/model_execution/session_impl.cc
+++ b/components/optimization_guide/core/model_execution/session_impl.cc
@@ -9,14 +9,17 @@
 #include <string>
 
 #include "base/containers/contains.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/stringprintf.h"
+#include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
 #include "base/token.h"
 #include "base/uuid.h"
 #include "components/optimization_guide/core/model_execution/feature_keys.h"
 #include "components/optimization_guide/core/model_execution/model_execution_util.h"
+#include "components/optimization_guide/core/model_execution/on_device_execution.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_access_controller.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_feature_adapter.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_service_controller.h"
@@ -30,6 +33,7 @@
 #include "components/optimization_guide/core/optimization_guide_model_executor.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/proto/model_quality_metadata.pb.h"
+#include "components/optimization_guide/proto/model_quality_service.pb.h"
 #include "components/optimization_guide/proto/string_value.pb.h"
 #include "components/optimization_guide/proto/text_safety_model_metadata.pb.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
@@ -52,101 +56,9 @@
   }
 }
 
-void LogRequest(OptimizationGuideLogger* logger,
-                const proto::OnDeviceModelServiceRequest& logged_request) {
-  if (logger && logger->ShouldEnableDebugLogs()) {
-    OPTIMIZATION_GUIDE_LOGGER(
-        optimization_guide_common::mojom::LogSource::MODEL_EXECUTION, logger)
-        << "Executing model "
-        << (logged_request.input_context_string().empty()
-                ? ""
-                : base::StringPrintf(
-                      "with input context of %d tokens:\n%s\n",
-                      logged_request.input_context_num_tokens_processed(),
-                      logged_request.input_context_string().c_str()))
-        << "with string:\n"
-        << logged_request.execution_string();
-  }
-}
-
-void LogRawResponse(OptimizationGuideLogger* logger,
-                    ModelBasedCapabilityKey feature,
-                    const std::string& raw_response) {
-  if (logger && logger->ShouldEnableDebugLogs()) {
-    OPTIMIZATION_GUIDE_LOGGER(
-        optimization_guide_common::mojom::LogSource::MODEL_EXECUTION, logger)
-        << "Model generates raw response with "
-        << std::string(GetStringNameForModelExecutionFeature(feature)) << ":\n"
-        << raw_response;
-  }
-}
-
-void LogRepeatedResponse(OptimizationGuideLogger* logger,
-                         ModelBasedCapabilityKey feature,
-                         const std::string& repeated_response) {
-  if (logger && logger->ShouldEnableDebugLogs()) {
-    OPTIMIZATION_GUIDE_LOGGER(
-        optimization_guide_common::mojom::LogSource::MODEL_EXECUTION, logger)
-        << "Model generates repeated response with "
-        << std::string(GetStringNameForModelExecutionFeature(feature)) << ":\n"
-        << repeated_response;
-  }
-}
-
-void LogResponseHasRepeats(ModelBasedCapabilityKey feature, bool has_repeats) {
-  base::UmaHistogramBoolean(
-      base::StrCat(
-          {"OptimizationGuide.ModelExecution.OnDeviceResponseHasRepeats.",
-           GetStringNameForModelExecutionFeature(feature)}),
-      has_repeats);
-}
-
-void LogResponseCompleteTime(ModelBasedCapabilityKey feature,
-                             base::TimeDelta time_to_completion) {
-  base::UmaHistogramMediumTimes(
-      base::StrCat(
-          {"OptimizationGuide.ModelExecution.OnDeviceResponseCompleteTime.",
-           GetStringNameForModelExecutionFeature(feature)}),
-      time_to_completion);
-}
-
-void LogResponseCompleteTokens(ModelBasedCapabilityKey feature,
-                               uint32_t tokens) {
-  base::UmaHistogramCounts10000(
-      base::StrCat(
-          {"OptimizationGuide.ModelExecution.OnDeviceResponseCompleteTokens.",
-           GetStringNameForModelExecutionFeature(feature)}),
-      tokens);
-}
-
-std::string GenerateExecutionId() {
-  return "on-device:" + base::Uuid::GenerateRandomV4().AsLowercaseString();
-}
-
-void InvokeStreamingCallbackWithRemoteResult(
-    OptimizationGuideModelExecutionResultStreamingCallback callback,
-    OptimizationGuideModelExecutionResult result,
-    std::unique_ptr<ModelQualityLogEntry> log_entry) {
-  OptimizationGuideModelStreamingExecutionResult streaming_result;
-  if (log_entry && log_entry->log_ai_data_request() &&
-      log_entry->log_ai_data_request()->has_model_execution_info()) {
-    streaming_result.execution_info =
-        std::make_unique<proto::ModelExecutionInfo>(
-            log_entry->log_ai_data_request()->model_execution_info());
-  }
-  streaming_result.log_entry = std::move(log_entry);
-  if (result.response.has_value()) {
-    streaming_result.response = base::ok(
-        StreamingResponse{.response = *result.response, .is_complete = true});
-  } else {
-    streaming_result.response = base::unexpected(result.response.error());
-  }
-  callback.Run(std::move(streaming_result));
-}
-
 SamplingParams ResolveSamplingParams(
     const std::optional<SessionConfigParams>& config_params,
-    const std::optional<SessionImpl::OnDeviceOptions>& on_device_opts) {
+    const std::optional<OnDeviceOptions>& on_device_opts) {
   if (config_params && config_params->sampling_params) {
     return config_params->sampling_params.value();
   }
@@ -168,103 +80,6 @@
 
 }  // namespace
 
-// Handles incrementally processing context. After the min context size has been
-// processed, any pending context processing will be cancelled if an
-// ExecuteModel() call is made.
-class SessionImpl::ContextProcessor
-    : public on_device_model::mojom::ContextClient {
- public:
-  ContextProcessor(SessionImpl& session, on_device_model::mojom::InputPtr input)
-      : session_(session), input_(std::move(input)) {
-    int min_context = features::GetOnDeviceModelMinTokensForContext();
-    if (min_context > 0) {
-      AddContext(min_context);
-    } else {
-      // If no min context is required, start processing the context as
-      // optional.
-      OnComplete(0);
-    }
-  }
-
-  // on_device_model::mojom::ContextClient:
-  void OnComplete(uint32_t tokens_processed) override {
-    tokens_processed_ += tokens_processed;
-
-    if (has_cancelled_) {
-      return;
-    }
-
-    // This means input has been fully processed.
-    if (tokens_processed < expected_tokens_) {
-      finished_processing_ = true;
-      return;
-    }
-
-    // Once the initial context is complete, we can cancel future context
-    // processing.
-    can_cancel_ = true;
-    if (tokens_processed_ < session_->GetTokenLimits().max_context_tokens) {
-      AddContext(features::GetOnDeviceModelContextTokenChunkSize());
-    }
-  }
-
-  // Returns whether the full context was processed.
-  bool MaybeCancelProcessing() {
-    has_cancelled_ = true;
-    if (can_cancel_) {
-      client_.reset();
-    }
-    return finished_processing_;
-  }
-
-  std::string input() { return OnDeviceInputToString(*input_); }
-
-  uint32_t tokens_processed() const { return tokens_processed_; }
-
- private:
-  void AddContext(uint32_t num_tokens) {
-    expected_tokens_ = num_tokens;
-    client_.reset();
-    if (!session_->ShouldUseOnDeviceModel()) {
-      return;
-    }
-    auto options = on_device_model::mojom::InputOptions::New();
-    options->input = input_.Clone();
-    options->max_tokens = num_tokens;
-    options->token_offset = tokens_processed_;
-    session_->GetOrCreateSession().AddContext(
-        std::move(options), client_.BindNewPipeAndPassRemote());
-  }
-
-  raw_ref<SessionImpl> session_;
-  on_device_model::mojom::InputPtr input_;
-  bool finished_processing_ = false;
-  uint32_t expected_tokens_ = 0;
-  uint32_t tokens_processed_ = 0;
-  bool can_cancel_ = false;
-  bool has_cancelled_ = false;
-  mojo::Receiver<on_device_model::mojom::ContextClient> client_{this};
-};
-
-SessionImpl::OnDeviceModelClient::~OnDeviceModelClient() = default;
-
-SessionImpl::OnDeviceOptions::OnDeviceOptions() = default;
-SessionImpl::OnDeviceOptions::OnDeviceOptions(OnDeviceOptions&&) = default;
-SessionImpl::OnDeviceOptions::~OnDeviceOptions() = default;
-
-SessionImpl::OnDeviceOptions::OnDeviceOptions(const OnDeviceOptions& orig)
-    : model_client(orig.model_client->Clone()),
-      model_versions(orig.model_versions),
-      adapter(orig.adapter),
-      safety_checker(std::make_unique<SafetyChecker>(*orig.safety_checker)),
-      token_limits(orig.token_limits),
-      logger(orig.logger),
-      log_uploader(orig.log_uploader) {}
-
-bool SessionImpl::OnDeviceOptions::ShouldUse() const {
-  return model_client->ShouldUse();
-}
-
 SessionImpl::SessionImpl(
     ModelBasedCapabilityKey feature,
     std::optional<OnDeviceOptions> on_device_opts,
@@ -274,34 +89,22 @@
       execute_remote_fn_(std::move(execute_remote_fn)),
       sampling_params_(ResolveSamplingParams(config_params, on_device_opts)) {
   if (on_device_opts && on_device_opts->ShouldUse()) {
-    on_device_state_.emplace(std::move(*on_device_opts), this);
+    LogSessionCreation(on_device_opts->logger.get(), feature_);
+    on_device_context_ =
+        std::make_unique<OnDeviceContext>(std::move(*on_device_opts), feature_);
     // Prewarm the initial session to make sure the service is started.
-    GetOrCreateSession();
-    LogSessionCreation(on_device_state_->opts.logger.get(), feature_);
+    on_device_context_->GetOrCreateSession();
   }
 }
 
-SessionImpl::~SessionImpl() {
-  if (on_device_state_ &&
-      on_device_state_->did_execute_and_waiting_for_on_complete()) {
-    if (on_device_state_->histogram_logger) {
-      on_device_state_->histogram_logger->set_result(
-          ExecuteModelResult::kDestroyedWhileWaitingForResponse);
-    }
-    base::UmaHistogramMediumTimes(
-        base::StrCat({"OptimizationGuide.ModelExecution."
-                      "OnDeviceDestroyedWhileWaitingForResponseTime.",
-                      GetStringNameForModelExecutionFeature(feature_)}),
-        base::TimeTicks::Now() - on_device_state_->start);
-  }
-}
+SessionImpl::~SessionImpl() {}
 
 const TokenLimits& SessionImpl::GetTokenLimits() const {
-  if (!on_device_state_) {
+  if (!on_device_context_) {
     static const TokenLimits null_limits{};
     return null_limits;
   }
-  return on_device_state_->opts.token_limits;
+  return on_device_context_->opts().token_limits;
 }
 
 void SessionImpl::AddContext(
@@ -321,47 +124,32 @@
   context_start_time_ = base::TimeTicks::Now();
 
   // Cancel any pending response.
-  CancelPendingResponse(ExecuteModelResult::kCancelled);
+  if (on_device_execution_) {
+    on_device_execution_->Cancel();
+  }
 
   if (!ShouldUseOnDeviceModel()) {
     DestroyOnDeviceState();
     return AddContextResult::kUsingServer;
   }
 
-  on_device_state_->add_context_before_execute = false;
-  auto input = on_device_state_->opts.adapter->ConstructInputString(
-      *context_, /*want_input_context=*/true);
-  if (!input) {
+  if (!on_device_context_->SetInput(*context_)) {
     // Use server if can't construct input.
     DestroyOnDeviceState();
     return AddContextResult::kFailedConstructingInput;
   }
 
-  // Only the latest context is used, so restart the mojo session here.
-  on_device_state_->session.reset();
-
-  // As the session was just destroyed, clear the contextprocessor as
-  // it will be using the wrong session, and we don't care about old context
-  // at this point.
-  on_device_state_->context_processor.reset();
-
-  on_device_state_->context_processor =
-      std::make_unique<ContextProcessor>(*this, std::move(input->input));
   return AddContextResult::kUsingOnDevice;
 }
 
 void SessionImpl::Score(const std::string& text,
                         OptimizationGuideModelScoreCallback callback) {
   // Fail if not using on device, or no session was started yet.
-  if (!on_device_state_ || !on_device_state_->session ||
-      // Fail if context is incomplete
-      on_device_state_->add_context_before_execute ||
-      // Fail if execute was called
-      context_start_time_ == base::TimeTicks()) {
+  if (!on_device_context_ || !on_device_context_->CanUse()) {
     std::move(callback).Run(std::nullopt);
     return;
   }
-  on_device_state_->session->Score(
+  on_device_context_->GetOrCreateSession()->Score(
       text,
       base::BindOnce([](float score) { return std::optional<float>(score); })
           .Then(mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback),
@@ -372,489 +160,63 @@
     const google::protobuf::MessageLite& request_metadata,
     optimization_guide::OptimizationGuideModelExecutionResultStreamingCallback
         callback) {
-  std::unique_ptr<ExecuteModelHistogramLogger> logger =
-      std::make_unique<ExecuteModelHistogramLogger>(feature_);
-  last_message_ = MergeContext(request_metadata);
+  auto logger = std::make_unique<OnDeviceExecution::ResultLogger>(feature_);
 
-  auto log_ai_data_request = std::make_unique<proto::LogAiDataRequest>();
-  SetExecutionRequest(feature_, *log_ai_data_request, *last_message_);
-  proto::OnDeviceModelServiceRequest* logged_request =
-      log_ai_data_request->mutable_model_execution_info()
-          ->mutable_on_device_model_execution_info()
-          ->add_execution_infos()
-          ->mutable_request()
-          ->mutable_on_device_model_service_request();
-
+  // Compute the amount of time context would have for processing assuming
+  // on-device execution.
+  base::TimeDelta context_start_to_execution = base::TimeDelta::Min();
   if (context_start_time_ != base::TimeTicks()) {
-    base::TimeDelta context_start_to_execution =
-        base::TimeTicks::Now() - context_start_time_;
+    context_start_to_execution = base::TimeTicks::Now() - context_start_time_;
     base::UmaHistogramLongTimes(
         base::StrCat(
             {"OptimizationGuide.ModelExecution.ContextStartToExecutionTime.",
              GetStringNameForModelExecutionFeature(feature_)}),
         context_start_to_execution);
-    logged_request
-        ->set_time_from_input_context_processed_to_request_initiated_millis(
-            context_start_to_execution.InMilliseconds());
     // Only interested in logging the first request after adding context.
     context_start_time_ = base::TimeTicks();
   }
 
+  std::unique_ptr<google::protobuf::MessageLite> merged_request =
+      MergeContext(request_metadata);
+
   if (!ShouldUseOnDeviceModel()) {
-    CancelPendingResponse(ExecuteModelResult::kCancelled);
     DestroyOnDeviceState();
     execute_remote_fn_.Run(
-        feature_, *last_message_, std::nullopt,
+        feature_, *merged_request, std::nullopt,
         /*log_ai_data_request=*/nullptr,
         base::BindOnce(&InvokeStreamingCallbackWithRemoteResult,
                        std::move(callback)));
     return;
   }
 
-  *(log_ai_data_request->mutable_model_execution_info()
-        ->mutable_on_device_model_execution_info()
-        ->mutable_model_versions()) = on_device_state_->opts.model_versions;
-
-  if (on_device_state_->add_context_before_execute) {
-    CHECK(context_);
-    std::unique_ptr<google::protobuf::MessageLite> context =
-        std::move(context_);
-    // Note that this will CancelPendingResponse, so it must be called before
-    // switching to the new pending response below.
-    AddContext(*context);
-    CHECK(!on_device_state_->add_context_before_execute);
+  if (on_device_execution_) {
+    on_device_execution_->Cancel();
   }
+  on_device_execution_.reset();
 
-  // Make sure to cancel any pending response.
-  CancelPendingResponse(ExecuteModelResult::kCancelled);
   // Set new pending response.
-  on_device_state_->histogram_logger = std::move(logger);
-  on_device_state_->callback = std::move(callback);
+  on_device_execution_.emplace(
+      feature_, on_device_context_->opts(), execute_remote_fn_,
+      std::move(merged_request), std::move(logger), std::move(callback),
+      base::BindOnce(&SessionImpl::OnDeviceExecutionTerminated,
+                     weak_ptr_factory_.GetWeakPtr()));
 
-  auto input = on_device_state_->opts.adapter->ConstructInputString(
-      *last_message_, /*want_input_context=*/false);
-  if (!input) {
-    // Use server if can't construct input.
-    DestroyOnDeviceStateAndFallbackToRemote(
-        ExecuteModelResult::kFailedConstructingMessage);
-    return;
-  }
-
-  // Cancel any optional context still processing.
-  if (on_device_state_->context_processor) {
-    bool finished_processing =
-        on_device_state_->context_processor->MaybeCancelProcessing();
-    base::UmaHistogramCounts10000(
-        base::StrCat(
-            {"OptimizationGuide.ModelExecution.OnDeviceContextTokensProcessed.",
-             GetStringNameForModelExecutionFeature(feature_)}),
-        on_device_state_->context_processor->tokens_processed());
-    base::UmaHistogramBoolean(
-        base::StrCat({"OptimizationGuide.ModelExecution."
-                      "OnDeviceContextFinishedProcessing.",
-                      GetStringNameForModelExecutionFeature(feature_)}),
-        finished_processing);
-    logged_request->set_input_context_num_tokens_processed(
-        on_device_state_->context_processor->tokens_processed());
-  }
-
-  // Note: if on-device fails for some reason, the result will be changed.
-  on_device_state_->histogram_logger->set_result(
-      ExecuteModelResult::kUsedOnDevice);
-
-  if (!input->should_ignore_input_context &&
-      on_device_state_->context_processor) {
-    logged_request->set_input_context_string(
-        on_device_state_->context_processor->input());
-  }
-  logged_request->set_execution_string(input->ToString());
-  // TODO(b/302327957): Probably do some math to get the accurate number here.
-  logged_request->set_execution_num_tokens_processed(
-      on_device_state_->opts.token_limits.max_execute_tokens);
-  LogRequest(on_device_state_->opts.logger.get(), *logged_request);
-
-  on_device_state_->log_ai_data_request = std::move(log_ai_data_request);
-  on_device_state_->start = base::TimeTicks::Now();
-
-  auto options = on_device_model::mojom::InputOptions::New();
-  options->input = std::move(input->input);
-  options->max_tokens = on_device_state_->opts.token_limits.max_execute_tokens;
-  options->ignore_context = input->should_ignore_input_context;
-  options->max_output_tokens =
-      on_device_state_->opts.token_limits.max_output_tokens;
-  options->top_k = sampling_params_.top_k;
-  options->temperature = sampling_params_.temperature;
-
-  on_device_state_->opts.safety_checker->RunRequestChecks(
-      *last_message_,
-      base::BindOnce(&SessionImpl::OnRequestSafetyResult,
-                     on_device_state_->session_weak_ptr_factory_.GetWeakPtr(),
-                     std::move(options)));
+  on_device_execution_->BeginExecution(*on_device_context_, sampling_params_);
 }
 
-void SessionImpl::OnRequestSafetyResult(
-    on_device_model::mojom::InputOptionsPtr options,
-    SafetyChecker::Result safety_result) {
-  if (safety_result.failed_to_run) {
-    DestroyOnDeviceStateAndFallbackToRemote(
-        ExecuteModelResult::kFailedConstructingMessage);
-    return;
+void SessionImpl::OnDeviceExecutionTerminated(bool healthy) {
+  on_device_execution_.reset();
+  if (!healthy) {
+    DestroyOnDeviceState();
   }
-  // Log the check executions.
-  on_device_state_->AddModelExecutionLogs(std::move(safety_result.logs));
-
-  // Handle the result.
-  if (safety_result.is_unsafe || safety_result.is_unsupported_language) {
-    if (on_device_state_->histogram_logger) {
-      on_device_state_->histogram_logger->set_result(
-          ExecuteModelResult::kRequestUnsafe);
-    }
-    if (features::GetOnDeviceModelRetractUnsafeContent()) {
-      CancelPendingResponse(ExecuteModelResult::kRequestUnsafe,
-                            safety_result.is_unsupported_language
-                                ? ModelExecutionError::kUnsupportedLanguage
-                                : ModelExecutionError::kFiltered);
-      return;
-    }
-  }
-  BeginRequestExecution(std::move(options));
-}
-
-void SessionImpl::BeginRequestExecution(
-    on_device_model::mojom::InputOptionsPtr options) {
-  GetOrCreateSession().Execute(
-      std::move(options),
-      on_device_state_->receiver.BindNewPipeAndPassRemote());
-  on_device_state_->receiver.set_disconnect_handler(base::BindOnce(
-      &SessionImpl::OnResponderDisconnect, base::Unretained(this)));
-}
-
-void SessionImpl::OnResponderDisconnect() {
-  // OnComplete resets the receiver, so this implies that the response is
-  // incomplete and there was either a service crash or model eviction.
-  on_device_state_->receiver.reset();
-  if (features::GetOnDeviceFallbackToServerOnDisconnect()) {
-    DestroyOnDeviceStateAndFallbackToRemote(
-        ExecuteModelResult::kDisconnectAndMaybeFallback);
-  } else {
-    CancelPendingResponse(ExecuteModelResult::kDisconnectAndCancel);
-  }
-}
-
-// on_device_model::mojom::StreamingResponder:
-void SessionImpl::OnResponse(on_device_model::mojom::ResponseChunkPtr chunk) {
-  proto::OnDeviceModelServiceResponse* logged_response =
-      on_device_state_->MutableLoggedResponse();
-
-  if (on_device_state_->current_response.empty()) {
-    base::TimeDelta time_to_first_response =
-        base::TimeTicks::Now() - on_device_state_->start;
-    base::UmaHistogramMediumTimes(
-        base::StrCat(
-            {"OptimizationGuide.ModelExecution.OnDeviceFirstResponseTime.",
-             GetStringNameForModelExecutionFeature(feature_)}),
-        time_to_first_response);
-    logged_response->set_time_to_first_response_millis(
-        time_to_first_response.InMilliseconds());
-  }
-
-  on_device_state_->current_response += chunk->text;
-  on_device_state_->num_unchecked_response_tokens++;
-  on_device_state_->num_response_tokens++;
-
-  if (HasRepeatingSuffix(on_device_state_->current_response)) {
-    // If a repeat is detected, halt the response, and cancel/finish early.
-    on_device_state_->receiver.reset();
-    logged_response->set_has_repeats(true);
-    if (features::GetOnDeviceModelRetractRepeats()) {
-      LogRepeatedResponse(on_device_state_->opts.logger.get(), feature_,
-                          on_device_state_->current_response);
-      logged_response->set_status(
-          proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED);
-      CancelPendingResponse(ExecuteModelResult::kResponseHadRepeats,
-                            ModelExecutionError::kFiltered);
-      return;
-    }
-
-    // Artificially send the OnComplete event to finish processing.
-    OnComplete(on_device_model::mojom::ResponseSummary::New());
-    return;
-  }
-
-  if (!on_device_state_->opts.safety_checker->safety_cfg()
-           .CanCheckPartialOutput(
-               on_device_state_->num_response_tokens,
-               on_device_state_->num_unchecked_response_tokens)) {
-    // Not enough new data to be worth re-evaluating yet.
-    return;
-  }
-
-  on_device_state_->num_unchecked_response_tokens = 0;
-  RunRawOutputSafetyCheck(ResponseCompleteness::kPartial);
-}
-
-void SessionImpl::OnComplete(
-    on_device_model::mojom::ResponseSummaryPtr summary) {
-  on_device_state_->receiver.reset();  // Suppress expected disconnect
-
-  proto::OnDeviceModelServiceResponse* logged_response =
-      on_device_state_->MutableLoggedResponse();
-  LogResponseHasRepeats(feature_, logged_response->has_repeats());
-  LogResponseCompleteTokens(feature_, on_device_state_->num_response_tokens);
-  base::TimeDelta time_to_completion =
-      base::TimeTicks::Now() - on_device_state_->start;
-  LogResponseCompleteTime(feature_, time_to_completion);
-  logged_response->set_time_to_completion_millis(
-      time_to_completion.InMilliseconds());
-
-  on_device_state_->opts.model_client->OnResponseCompleted();
-
-  on_device_state_->response_completeness = ResponseCompleteness::kComplete;
-  RunRawOutputSafetyCheck(ResponseCompleteness::kComplete);
-}
-
-void SessionImpl::RunRawOutputSafetyCheck(ResponseCompleteness completeness) {
-  on_device_state_->opts.safety_checker->RunRawOutputCheck(
-      on_device_state_->current_response, completeness,
-      base::BindOnce(&SessionImpl::OnRawOutputSafetyResult,
-                     on_device_state_->session_weak_ptr_factory_.GetWeakPtr(),
-                     on_device_state_->current_response.size(), completeness));
-}
-
-void SessionImpl::OnRawOutputSafetyResult(size_t raw_output_size,
-                                          ResponseCompleteness completeness,
-                                          SafetyChecker::Result safety_result) {
-  if (safety_result.failed_to_run) {
-    DestroyOnDeviceStateAndFallbackToRemote(
-        ExecuteModelResult::kFailedConstructingMessage);
-    return;
-  }
-  if (safety_result.is_unsafe || safety_result.is_unsupported_language) {
-    if (on_device_state_->histogram_logger) {
-      on_device_state_->histogram_logger->set_result(
-          ExecuteModelResult::kUsedOnDeviceOutputUnsafe);
-    }
-    on_device_state_->AddModelExecutionLogs(std::move(safety_result.logs));
-    if (features::GetOnDeviceModelRetractUnsafeContent()) {
-      CancelPendingResponse(ExecuteModelResult::kUsedOnDeviceOutputUnsafe,
-                            safety_result.is_unsupported_language
-                                ? ModelExecutionError::kUnsupportedLanguage
-                                : ModelExecutionError::kFiltered);
-
-      return;
-    }
-  }
-  if (completeness == ResponseCompleteness::kComplete) {
-    on_device_state_->AddModelExecutionLogs(std::move(safety_result.logs));
-  }
-  on_device_state_->latest_safe_raw_output.length = raw_output_size;
-  SendResponse(completeness);
-}
-
-on_device_model::mojom::Session& SessionImpl::GetOrCreateSession() {
-  CHECK(ShouldUseOnDeviceModel());
-  if (!on_device_state_->session) {
-    on_device_state_->opts.model_client->GetModelRemote()->StartSession(
-        on_device_state_->session.BindNewPipeAndPassReceiver());
-    on_device_state_->session.set_disconnect_handler(base::BindOnce(
-        &SessionImpl::OnSessionDisconnect, base::Unretained(this)));
-  }
-  return *on_device_state_->session;
-}
-
-void SessionImpl::OnSessionDisconnect() {
-  if (context_) {
-    // Persist the current context, so that ExecuteModel() can be called
-    // without adding the same context.
-    on_device_state_->add_context_before_execute = true;
-  }
-  on_device_state_->session.reset();
-}
-
-void SessionImpl::CancelPendingResponse(ExecuteModelResult result,
-                                        ModelExecutionError error) {
-  if (!on_device_state_) {
-    return;
-  }
-  if (on_device_state_->histogram_logger) {
-    on_device_state_->histogram_logger->set_result(result);
-  }
-  auto callback = std::move(on_device_state_->callback);
-  auto log_ai_data_request = std::move(on_device_state_->log_ai_data_request);
-  on_device_state_->ResetRequestState();
-  if (callback) {
-    OptimizationGuideModelExecutionError og_error =
-        OptimizationGuideModelExecutionError::FromModelExecutionError(error);
-    std::unique_ptr<ModelQualityLogEntry> log_entry;
-    std::unique_ptr<proto::ModelExecutionInfo> model_execution_info;
-    if (og_error.ShouldLogModelQuality()) {
-      log_entry = std::make_unique<ModelQualityLogEntry>(
-          on_device_state_->opts.log_uploader);
-      log_entry->log_ai_data_request()->MergeFrom(*log_ai_data_request);
-      std::string model_execution_id = GenerateExecutionId();
-      log_entry->set_model_execution_id(model_execution_id);
-      model_execution_info = std::make_unique<proto::ModelExecutionInfo>(
-          log_entry->log_ai_data_request()->model_execution_info());
-      model_execution_info->set_execution_id(model_execution_id);
-      model_execution_info->set_model_execution_error_enum(
-          static_cast<uint32_t>(og_error.error()));
-    }
-    callback.Run(OptimizationGuideModelStreamingExecutionResult(
-        base::unexpected(og_error), /*provided_by_on_device=*/true,
-        std::move(log_entry), std::move(model_execution_info)));
-  }
-}
-
-void SessionImpl::SendResponse(ResponseCompleteness completeness) {
-  if (completeness == ResponseCompleteness::kPartial &&
-      features::ShouldUseTextSafetyRemoteFallbackForEligibleFeatures()) {
-    // We don't send streaming responses in this mode.
-    return;
-  }
-
-  if (!on_device_state_->opts.adapter->ShouldParseResponse(completeness)) {
-    return;
-  }
-
-  std::string safe_response = on_device_state_->current_response.substr(
-      0, on_device_state_->latest_safe_raw_output.length);
-  LogRawResponse(on_device_state_->opts.logger.get(), feature_, safe_response);
-  on_device_state_->MutableLoggedResponse()->set_output_string(safe_response);
-  size_t previous_response_pos = on_device_state_->latest_response_pos;
-  on_device_state_->latest_response_pos =
-      on_device_state_->latest_safe_raw_output.length;
-  on_device_state_->opts.adapter->ParseResponse(
-      *last_message_, safe_response, previous_response_pos,
-      base::BindOnce(&SessionImpl::OnParsedResponse,
-                     on_device_state_->session_weak_ptr_factory_.GetWeakPtr(),
-                     completeness));
-}
-
-void SessionImpl::OnParsedResponse(
-    ResponseCompleteness completeness,
-    base::expected<proto::Any, ResponseParsingError> output) {
-  if (!output.has_value()) {
-    switch (output.error()) {
-      case ResponseParsingError::kRejectedPii:
-        on_device_state_->MutableLoggedResponse()->set_status(
-            proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED);
-        CancelPendingResponse(ExecuteModelResult::kContainedPII,
-                              ModelExecutionError::kFiltered);
-        return;
-      case ResponseParsingError::kFailed:
-        CancelPendingResponse(
-            ExecuteModelResult::kFailedConstructingResponseMessage,
-            ModelExecutionError::kGenericFailure);
-        return;
-    }
-  }
-  on_device_state_->opts.safety_checker->RunResponseChecks(
-      *last_message_, *output, completeness,
-      base::BindOnce(&SessionImpl::OnResponseSafetyResult,
-                     on_device_state_->session_weak_ptr_factory_.GetWeakPtr(),
-                     completeness, *output));
-}
-
-void SessionImpl::OnResponseSafetyResult(ResponseCompleteness completeness,
-                                         proto::Any output,
-                                         SafetyChecker::Result safety_result) {
-  if (safety_result.failed_to_run) {
-    DestroyOnDeviceStateAndFallbackToRemote(
-        ExecuteModelResult::kFailedConstructingMessage);
-    return;
-  }
-  if (completeness == ResponseCompleteness::kComplete ||
-      safety_result.is_unsafe || safety_result.is_unsupported_language) {
-    on_device_state_->AddModelExecutionLogs(std::move(safety_result.logs));
-  }
-  if (safety_result.is_unsafe || safety_result.is_unsupported_language) {
-    if (on_device_state_->histogram_logger) {
-      on_device_state_->histogram_logger->set_result(
-          ExecuteModelResult::kUsedOnDeviceOutputUnsafe);
-    }
-    if (features::GetOnDeviceModelRetractUnsafeContent()) {
-      CancelPendingResponse(ExecuteModelResult::kUsedOnDeviceOutputUnsafe,
-                            safety_result.is_unsupported_language
-                                ? ModelExecutionError::kUnsupportedLanguage
-                                : ModelExecutionError::kFiltered);
-
-      return;
-    }
-  }
-  if (completeness == ResponseCompleteness::kPartial) {
-    SendPartialResponseCallback(output);
-    return;
-  }
-
-  if (features::ShouldUseTextSafetyRemoteFallbackForEligibleFeatures()) {
-    RunTextSafetyRemoteFallbackAndCompletionCallback(std::move(output));
-    return;
-  }
-
-  SendSuccessCompletionCallback(output);
-}
-
-void SessionImpl::SendPartialResponseCallback(
-    const proto::Any& success_response_metadata) {
-  on_device_state_->callback.Run(OptimizationGuideModelStreamingExecutionResult(
-      base::ok(StreamingResponse{.response = success_response_metadata,
-                                 .is_complete = false}),
-      /*provided_by_on_device=*/true, /*log_entry=*/nullptr));
-}
-
-void SessionImpl::SendSuccessCompletionCallback(
-    const proto::Any& success_response_metadata) {
-  // Complete the log entry and promise it to the ModelQualityUploaderService.
-  std::unique_ptr<ModelQualityLogEntry> log_entry;
-  std::unique_ptr<proto::ModelExecutionInfo> model_execution_info;
-  if (on_device_state_->log_ai_data_request) {
-    SetExecutionResponse(feature_, *(on_device_state_->log_ai_data_request),
-                         success_response_metadata);
-    on_device_state_->MutableLoggedResponse()->set_status(
-        proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_SUCCESS);
-    log_entry = std::make_unique<ModelQualityLogEntry>(
-        on_device_state_->opts.log_uploader);
-    log_entry->log_ai_data_request()->MergeFrom(
-        *on_device_state_->log_ai_data_request);
-    std::string model_execution_id = GenerateExecutionId();
-    log_entry->set_model_execution_id(model_execution_id);
-    model_execution_info = std::make_unique<proto::ModelExecutionInfo>(
-        on_device_state_->log_ai_data_request->model_execution_info());
-    model_execution_info->set_execution_id(model_execution_id);
-    on_device_state_->log_ai_data_request.reset();
-  }
-
-  // Return the execution response.
-  on_device_state_->callback.Run(OptimizationGuideModelStreamingExecutionResult(
-      base::ok(StreamingResponse{.response = success_response_metadata,
-                                 .is_complete = true}),
-      /*provided_by_on_device=*/true, std::move(log_entry),
-      std::move(model_execution_info)));
-
-  on_device_state_->ResetRequestState();
 }
 
 bool SessionImpl::ShouldUseOnDeviceModel() const {
-  return on_device_state_ && on_device_state_->opts.model_client->ShouldUse();
-}
-
-void SessionImpl::DestroyOnDeviceStateAndFallbackToRemote(
-    ExecuteModelResult result) {
-  if (on_device_state_->histogram_logger) {
-    on_device_state_->histogram_logger->set_result(result);
-  }
-  auto log_ai_data_request = std::move(on_device_state_->log_ai_data_request);
-  auto callback = std::move(on_device_state_->callback);
-  DestroyOnDeviceState();
-  execute_remote_fn_.Run(
-      feature_, *last_message_, std::nullopt, std::move(log_ai_data_request),
-      base::BindOnce(&InvokeStreamingCallbackWithRemoteResult,
-                     std::move(callback)));
+  return on_device_context_ && on_device_context_->CanUse();
 }
 
 void SessionImpl::DestroyOnDeviceState() {
-  DCHECK(!on_device_state_ || !on_device_state_->callback);
-  on_device_state_.reset();
+  on_device_context_.reset();
 }
 
 std::unique_ptr<google::protobuf::MessageLite> SessionImpl::MergeContext(
@@ -870,137 +232,6 @@
   return message;
 }
 
-void SessionImpl::RunTextSafetyRemoteFallbackAndCompletionCallback(
-    proto::Any success_response_metadata) {
-  auto ts_request = on_device_state_->opts.adapter->ConstructTextSafetyRequest(
-      *last_message_, on_device_state_->current_response);
-  if (!ts_request) {
-    CancelPendingResponse(
-        ExecuteModelResult::kFailedConstructingRemoteTextSafetyRequest,
-        ModelExecutionError::kGenericFailure);
-    return;
-  }
-
-  proto::InternalOnDeviceModelExecutionInfo remote_ts_model_execution_info;
-  auto* ts_request_log = remote_ts_model_execution_info.mutable_request()
-                             ->mutable_text_safety_model_request();
-  ts_request_log->set_text(ts_request->text());
-  ts_request_log->set_url(ts_request->url());
-
-  execute_remote_fn_.Run(
-      ModelBasedCapabilityKey::kTextSafety, *ts_request, std::nullopt,
-      /*log_ai_data_request=*/nullptr,
-      base::BindOnce(&SessionImpl::OnTextSafetyRemoteResponse,
-                     on_device_state_->session_weak_ptr_factory_.GetWeakPtr(),
-                     std::move(remote_ts_model_execution_info),
-                     std::move(success_response_metadata)));
-}
-
-void SessionImpl::OnTextSafetyRemoteResponse(
-    proto::InternalOnDeviceModelExecutionInfo remote_ts_model_execution_info,
-    proto::Any success_response_metadata,
-    OptimizationGuideModelExecutionResult result,
-    std::unique_ptr<ModelQualityLogEntry> remote_log_entry) {
-  bool is_unsafe =
-      !result.response.has_value() &&
-      result.response.error().error() ==
-          OptimizationGuideModelExecutionError::ModelExecutionError::kFiltered;
-  if (on_device_state_->log_ai_data_request) {
-    if (remote_log_entry) {
-      auto* ts_response_log = remote_ts_model_execution_info.mutable_response()
-                                  ->mutable_text_safety_model_response();
-      ts_response_log->set_server_execution_id(
-          remote_log_entry->model_execution_id());
-      ts_response_log->set_is_unsafe(is_unsafe);
-    }
-    *(on_device_state_->log_ai_data_request->mutable_model_execution_info()
-          ->mutable_on_device_model_execution_info()
-          ->add_execution_infos()) = remote_ts_model_execution_info;
-  }
-
-  if (is_unsafe) {
-    CancelPendingResponse(ExecuteModelResult::kUsedOnDeviceOutputUnsafe,
-                          ModelExecutionError::kFiltered);
-    return;
-  }
-
-  if (!result.response.has_value()) {
-    CancelPendingResponse(ExecuteModelResult::kTextSafetyRemoteRequestFailed,
-                          ModelExecutionError::kGenericFailure);
-    return;
-  }
-
-  SendSuccessCompletionCallback(success_response_metadata);
-}
-
-SessionImpl::OnDeviceState::OnDeviceState(OnDeviceOptions&& options,
-                                          SessionImpl* session)
-    : opts(std::move(options)),
-      receiver(session),
-      session_weak_ptr_factory_(session) {}
-
-SessionImpl::OnDeviceState::~OnDeviceState() = default;
-
-proto::OnDeviceModelServiceResponse*
-SessionImpl::OnDeviceState::MutableLoggedResponse() {
-  CHECK(log_ai_data_request);
-  CHECK_GT(log_ai_data_request->model_execution_info()
-               .on_device_model_execution_info()
-               .execution_infos_size(),
-           0);
-  return log_ai_data_request->mutable_model_execution_info()
-      ->mutable_on_device_model_execution_info()
-      ->mutable_execution_infos(0)
-      ->mutable_response()
-      ->mutable_on_device_model_service_response();
-}
-
-void SessionImpl::OnDeviceState::AddModelExecutionLog(
-    const proto::InternalOnDeviceModelExecutionInfo& log) {
-  CHECK(log_ai_data_request);
-
-  log_ai_data_request->mutable_model_execution_info()
-      ->mutable_on_device_model_execution_info()
-      ->add_execution_infos()
-      ->CopyFrom(log);
-}
-
-void SessionImpl::OnDeviceState::AddModelExecutionLogs(
-    google::protobuf::RepeatedPtrField<
-        proto::InternalOnDeviceModelExecutionInfo> logs) {
-  CHECK(log_ai_data_request);
-
-  log_ai_data_request->mutable_model_execution_info()
-      ->mutable_on_device_model_execution_info()
-      ->mutable_execution_infos()
-      ->MergeFrom(std::move(logs));
-}
-
-void SessionImpl::OnDeviceState::ResetRequestState() {
-  receiver.reset();
-  callback.Reset();
-  current_response.clear();
-  latest_response_pos = 0;
-  start = base::TimeTicks();
-  histogram_logger.reset();
-  log_ai_data_request.reset();
-  num_unchecked_response_tokens = 0;
-  latest_safe_raw_output.length = 0;
-  response_completeness = ResponseCompleteness::kPartial;
-  session_weak_ptr_factory_.InvalidateWeakPtrs();
-}
-
-SessionImpl::OnDeviceState::SafeRawOutput::SafeRawOutput() = default;
-SessionImpl::OnDeviceState::SafeRawOutput::~SafeRawOutput() = default;
-
-SessionImpl::ExecuteModelHistogramLogger::~ExecuteModelHistogramLogger() {
-  base::UmaHistogramEnumeration(
-      base::StrCat(
-          {"OptimizationGuide.ModelExecution.OnDeviceExecuteModelResult.",
-           GetStringNameForModelExecutionFeature(feature_)}),
-      result_);
-}
-
 void SessionImpl::GetSizeInTokens(
     const std::string& text,
     OptimizationGuideModelSizeInTokenCallback callback) {
@@ -1011,7 +242,7 @@
   }
   auto input = on_device_model::mojom::Input::New();
   input->pieces.push_back(text);
-  GetOrCreateSession().GetSizeInTokens(
+  on_device_context_->GetOrCreateSession()->GetSizeInTokens(
       std::move(input),
       mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback), 0));
 }
@@ -1031,7 +262,7 @@
 }
 
 const proto::Any& SessionImpl::GetOnDeviceFeatureMetadata() const {
-  return on_device_state_->opts.adapter->GetFeatureMetadata();
+  return on_device_context_->opts().adapter->GetFeatureMetadata();
 }
 
 const SamplingParams SessionImpl::GetSamplingParams() const {
@@ -1047,13 +278,13 @@
     std::move(callback).Run(0);
     return;
   }
-  auto input = on_device_state_->opts.adapter->ConstructInputString(
+  auto input = on_device_context_->opts().adapter->ConstructInputString(
       request, want_input_context);
   if (!input) {
     std::move(callback).Run(0);
     return;
   }
-  GetOrCreateSession().GetSizeInTokens(
+  on_device_context_->GetOrCreateSession()->GetSizeInTokens(
       std::move(input->input),
       mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback), 0));
 }
diff --git a/components/optimization_guide/core/model_execution/session_impl.h b/components/optimization_guide/core/model_execution/session_impl.h
index 398c7df..844415c 100644
--- a/components/optimization_guide/core/model_execution/session_impl.h
+++ b/components/optimization_guide/core/model_execution/session_impl.h
@@ -10,10 +10,13 @@
 #include <string>
 #include <vector>
 
+#include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
 #include "components/optimization_guide/core/model_execution/feature_keys.h"
+#include "components/optimization_guide/core/model_execution/on_device_context.h"
+#include "components/optimization_guide/core/model_execution/on_device_execution.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_feature_adapter.h"
 #include "components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h"
 #include "components/optimization_guide/core/model_execution/safety_checker.h"
@@ -23,60 +26,19 @@
 #include "components/optimization_guide/proto/model_quality_metadata.pb.h"
 #include "components/optimization_guide/proto/model_quality_service.pb.h"
 #include "components/optimization_guide/proto/text_safety_model_metadata.pb.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/on_device_model/public/mojom/on_device_model.mojom.h"
 
-class OptimizationGuideLogger;
-
 namespace optimization_guide {
-class OnDeviceModelFeatureAdapter;
 
-using ExecuteRemoteFn = base::RepeatingCallback<void(
-    ModelBasedCapabilityKey feature,
-    const google::protobuf::MessageLite&,
-    std::optional<base::TimeDelta> timeout,
-    std::unique_ptr<proto::LogAiDataRequest>,
-    OptimizationGuideModelExecutionResultCallback)>;
+class OnDeviceContext;
 
 // Session implementation that uses either the on device model or the server
 // model.
-class SessionImpl : public OptimizationGuideModelExecutor::Session,
-                    public on_device_model::mojom::StreamingResponder {
+class SessionImpl : public OptimizationGuideModelExecutor::Session {
  public:
-  class OnDeviceModelClient {
-   public:
-    virtual ~OnDeviceModelClient() = 0;
-    // Create another client for the same model.
-    virtual std::unique_ptr<OnDeviceModelClient> Clone() const = 0;
-    // Called to check whether this client is still usable.
-    virtual bool ShouldUse() = 0;
-    // Called to retrieve connection the managed model.
-    virtual mojo::Remote<on_device_model::mojom::OnDeviceModel>&
-    GetModelRemote() = 0;
-    // Called to report a successful execution of the model.
-    virtual void OnResponseCompleted() = 0;
-  };
-
-  struct OnDeviceOptions final {
-    OnDeviceOptions();
-    OnDeviceOptions(const OnDeviceOptions&);
-    OnDeviceOptions(OnDeviceOptions&&);
-    ~OnDeviceOptions();
-
-    std::unique_ptr<OnDeviceModelClient> model_client;
-    proto::OnDeviceModelVersions model_versions;
-    scoped_refptr<const OnDeviceModelFeatureAdapter> adapter;
-    std::unique_ptr<SafetyChecker> safety_checker;
-    TokenLimits token_limits;
-
-    base::WeakPtr<OptimizationGuideLogger> logger;
-    base::WeakPtr<ModelQualityLogsUploaderService> log_uploader;
-
-    // Returns true if the on-device model may be used.
-    bool ShouldUse() const;
-  };
-
   // Possible outcomes of AddContext(). Maps to histogram enum
   // "OptimizationGuideOnDeviceAddContextResult".
   // These values are persisted to logs. Entries should not be renumbered
@@ -88,57 +50,6 @@
     kMaxValue = kFailedConstructingInput,
   };
 
-  // Possible outcomes of ExecuteModel().
-  // These values are persisted to logs. Entries should not be renumbered and
-  // numeric values should never be reused.
-  enum class ExecuteModelResult {
-    // On-device was not used.
-    kOnDeviceNotUsed = 0,
-    // On-device was used, and it completed successfully.
-    kUsedOnDevice = 1,
-    // Failed constructing message, and used server.
-    kFailedConstructingMessage = 2,
-    // Got a response from on-device, but failed constructing the message.
-    kFailedConstructingResponseMessage = 3,
-    // Timed out and used server.
-    kTimedOut = 4,
-    // Received a disconnect while waiting for response. This may trigger
-    // fallback to another model, e.g. on the server, if configured.
-    kDisconnectAndMaybeFallback = 5,
-    // Received a disconnect while waiting for response and cancelled.
-    kDisconnectAndCancel = 6,
-    // Response was cancelled because ExecuteModel() was called while waiting
-    // for response.
-    kCancelled = 7,
-    // SessionImpl was destroyed while waiting for a response.
-    kDestroyedWhileWaitingForResponse = 8,
-    // On-device was used, it completed successfully, but the output is
-    // considered unsafe.
-    kUsedOnDeviceOutputUnsafe = 9,
-    // On-device was used, but the output was rejected (because contained PII).
-    kContainedPII = 10,
-    // On-device was used, but the output was rejected because it had repeats.
-    kResponseHadRepeats = 11,
-    // On-device was used and the output was complete but the output was
-    // rejected since it did not have the required safety scores.
-    kResponseCompleteButNoRequiredSafetyScores = 12,
-    // On-device was used and completed successfully, but the output was not in
-    // a language that could be reliably evaluated for safety.
-    kUsedOnDeviceOutputUnsupportedLanguage = 13,
-    // On-device was used and completed successfully, but failed constructing
-    // the text safety remote request.
-    kFailedConstructingRemoteTextSafetyRequest = 14,
-    // On-device was used and completed successfully, but the text safety remote
-    // request failed for some reason.
-    kTextSafetyRemoteRequestFailed = 15,
-    // On-device was used, but the request was considered unsafe.
-    kRequestUnsafe = 16,
-
-    // Please update OptimizationGuideOnDeviceExecuteModelResult in
-    // optimization/enums.xml.
-    kMaxValue = kRequestUnsafe,
-  };
-
   SessionImpl(ModelBasedCapabilityKey feature,
               std::optional<OnDeviceOptions> on_device_opts,
               ExecuteRemoteFn execute_remote_fn,
@@ -166,180 +77,23 @@
       OptimizationGuideModelSizeInTokenCallback callback) override;
   const SamplingParams GetSamplingParams() const override;
 
-  // on_device_model::mojom::StreamingResponder:
-  void OnResponse(on_device_model::mojom::ResponseChunkPtr chunk) override;
-  void OnComplete(on_device_model::mojom::ResponseSummaryPtr summary) override;
-  // Called when the StreamingResponder remote is disconnected.
-  void OnResponderDisconnect();
-
   // Returns true if the on-device model should be used.
   bool ShouldUseOnDeviceModel() const;
 
  private:
-  class ContextProcessor;
-
-  // Used to log the result of ExecuteModel.
-  class ExecuteModelHistogramLogger {
-   public:
-    explicit ExecuteModelHistogramLogger(ModelBasedCapabilityKey feature)
-        : feature_(feature) {}
-    ~ExecuteModelHistogramLogger();
-
-    void set_result(ExecuteModelResult result) { result_ = result; }
-
-   private:
-    const ModelBasedCapabilityKey feature_;
-    ExecuteModelResult result_ = ExecuteModelResult::kOnDeviceNotUsed;
-  };
-
-  // Captures all state used for the on device model.
-  struct OnDeviceState {
-    OnDeviceState(OnDeviceOptions&& opts, SessionImpl* session);
-    ~OnDeviceState();
-
-    // Returns true if ExecuteModel() was called and the complete response
-    // has not been received.
-    bool did_execute_and_waiting_for_on_complete() const {
-      return start != base::TimeTicks() &&
-             response_completeness == ResponseCompleteness::kPartial;
-    }
-
-    // Returns the mutable on-device model service response for logging.
-    proto::OnDeviceModelServiceResponse* MutableLoggedResponse();
-
-    // Adds an execution info for the text safety model based on `this`.
-    void AddModelExecutionLog(
-        const proto::InternalOnDeviceModelExecutionInfo& log);
-    // Adds a collection of model execution logs to the request log.
-    void AddModelExecutionLogs(google::protobuf::RepeatedPtrField<
-                               proto::InternalOnDeviceModelExecutionInfo> logs);
-
-    // Resets all state related to a request.
-    void ResetRequestState();
-
-    OnDeviceOptions opts;
-    mojo::Remote<on_device_model::mojom::Session> session;
-    std::unique_ptr<ContextProcessor> context_processor;
-    mojo::Receiver<on_device_model::mojom::StreamingResponder> receiver;
-    std::string current_response;
-    OptimizationGuideModelExecutionResultStreamingCallback callback;
-    // If true, the context is added before execution. This is set to true if
-    // a disconnect happens.
-    bool add_context_before_execute = false;
-    // Time ExecuteModel() was called.
-    base::TimeTicks start;
-    // Timer used to detect when no response has been received and fallback
-    // to remote execution.
-    base::OneShotTimer timer_for_first_response;
-    // Used to log the result of ExecuteModel().
-    std::unique_ptr<ExecuteModelHistogramLogger> histogram_logger;
-    // Used to log execution information for the request.
-    std::unique_ptr<proto::LogAiDataRequest> log_ai_data_request;
-
-    // How many tokens (response chunks) have been added since the last safety
-    // evaluation was requested.
-    size_t num_unchecked_response_tokens = 0;
-    // How many tokens (response chunks) have been added.
-    size_t num_response_tokens = 0;
-
-    struct SafeRawOutput {
-      SafeRawOutput();
-      ~SafeRawOutput();
-      // How much of 'current_response' was checked.
-      size_t length = 0;
-    };
-    // The longest response that has passed the raw output text safety check.
-    SafeRawOutput latest_safe_raw_output;
-    // The last position in the response that has been streamed to the
-    // responder.
-    size_t latest_response_pos = 0;
-
-    // Whether the model response is complete.
-    ResponseCompleteness response_completeness = ResponseCompleteness::kPartial;
-
-    // Factory for weak pointers related to this session that are invalidated
-    // with the request state.
-    base::WeakPtrFactory<SessionImpl> session_weak_ptr_factory_;
-  };
-
   AddContextResult AddContextImpl(
       const google::protobuf::MessageLite& request_metadata);
 
-  // Gets the active session or restarts a session if the session is reset.
-  on_device_model::mojom::Session& GetOrCreateSession();
-
-  // Called when on-device session disconnects.
-  void OnSessionDisconnect();
-
-  // Cancels any pending response and resets response state.
-  void CancelPendingResponse(
-      ExecuteModelResult result,
-      OptimizationGuideModelExecutionError::ModelExecutionError error =
-          OptimizationGuideModelExecutionError::ModelExecutionError::
-              kCancelled);
-
-  // Calls SendResponse(kComplete) if we've received the full response and have
-  // finished checking raw output safety for it.
-  void MaybeSendCompleteResponse();
-
-  // Sends `current_response_` to the client.
-  void SendResponse(ResponseCompleteness completeness);
-
-  void DestroyOnDeviceStateAndFallbackToRemote(ExecuteModelResult result);
-
   void DestroyOnDeviceState();
 
-  // Called to run the text safety remote fallback. Will invoke completion
-  // callback when done.
-  void RunTextSafetyRemoteFallbackAndCompletionCallback(
-      proto::Any success_response_metadata);
-
-  // Callback invoked with RequestSafetyCheck result.
-  void OnRequestSafetyResult(on_device_model::mojom::InputOptionsPtr options,
-                             SafetyChecker::Result safety_result);
-
-  // Begins request execution (leads to OnResponse/OnComplete).
-  void BeginRequestExecution(on_device_model::mojom::InputOptionsPtr options);
-
-  // Evaluates raw output safety.
-  // Will invoke SendResponse if evaluations are successful.
-  void RunRawOutputSafetyCheck(ResponseCompleteness completeness);
-
-  // Called when output safety check completes.
-  void OnRawOutputSafetyResult(size_t raw_output_size,
-                               ResponseCompleteness completeness,
-                               SafetyChecker::Result safety_result);
-
-  // Callback invoked when the text safety remote fallback response comes back.
-  // Will invoke the session's completion callback and destroy state.
-  void OnTextSafetyRemoteResponse(
-      proto::InternalOnDeviceModelExecutionInfo remote_ts_model_execution_info,
-      proto::Any success_response_metadata,
-      OptimizationGuideModelExecutionResult result,
-      std::unique_ptr<ModelQualityLogEntry> remote_log_entry);
-
-  // Called when a response has finished parsing.
-  void OnParsedResponse(
-      ResponseCompleteness completeness,
-      base::expected<proto::Any, ResponseParsingError> output);
-
-  // Called when response safety check completes.
-  void OnResponseSafetyResult(ResponseCompleteness completeness,
-                              proto::Any output,
-                              SafetyChecker::Result safety_result);
+  // Called when an on-device execution flow terminates, and can be cleaned up.
+  void OnDeviceExecutionTerminated(bool healthy);
 
   // Returns a new message created by merging `request` into `context_`. This
   // is a bit tricky since we don't know the type of MessageLite.
   std::unique_ptr<google::protobuf::MessageLite> MergeContext(
       const google::protobuf::MessageLite& request);
 
-  // Sends the partial response callback.
-  void SendPartialResponseCallback(const proto::Any& success_response_metadata);
-
-  // Sends the success completion callback and destroys any state.
-  void SendSuccessCompletionCallback(
-      const proto::Any& success_response_metadata);
-
   // Helper function to get the size of request in tokens with boolean flag to
   // control if we are extracting the context or the execution text.
   void GetSizeInTokensInternal(
@@ -353,14 +107,17 @@
   std::unique_ptr<google::protobuf::MessageLite> context_;
   base::TimeTicks context_start_time_;
 
-  // Last message executed.
-  std::unique_ptr<google::protobuf::MessageLite> last_message_;
+  // Manages the on-device session holding the processed context.
+  // If this is null, on-device executions cannot be started.
+  std::unique_ptr<OnDeviceContext> on_device_context_;
 
-  // Has a value when using the on device model.
-  std::optional<OnDeviceState> on_device_state_;
+  // Manages state for an ongoing on-device execution.
+  std::optional<OnDeviceExecution> on_device_execution_;
 
   // Params used to control output sampling for the on device model.
   const SamplingParams sampling_params_;
+
+  base::WeakPtrFactory<SessionImpl> weak_ptr_factory_{this};
 };
 
 }  // namespace optimization_guide
diff --git a/components/os_crypt/async/browser/BUILD.gn b/components/os_crypt/async/browser/BUILD.gn
index 2c47265..e7b19d7 100644
--- a/components/os_crypt/async/browser/BUILD.gn
+++ b/components/os_crypt/async/browser/BUILD.gn
@@ -80,6 +80,26 @@
     ]
   }
 
+  source_set("freedesktop_secret_key_provider") {
+    sources = [
+      "fallback_linux_key_provider.cc",
+      "fallback_linux_key_provider.h",
+      "freedesktop_secret_key_provider.cc",
+      "freedesktop_secret_key_provider.h",
+    ]
+
+    deps = [
+      ":key_provider_interface",
+      "//base",
+      "//components/dbus",
+      "//components/os_crypt/async/common",
+      "//components/os_crypt/sync",
+      "//crypto",
+      "//dbus",
+    ]
+    public_deps = [ "//build:branding_buildflags" ]
+  }
+
   source_set("test_secret_portal") {
     testonly = true
 
@@ -125,8 +145,13 @@
   }
 
   if (is_linux && use_dbus) {
-    sources += [ "secret_portal_key_provider_unittest.cc" ]
+    sources += [
+      "freedesktop_secret_key_provider_compat_unittest.cc",
+      "freedesktop_secret_key_provider_unittest.cc",
+      "secret_portal_key_provider_unittest.cc",
+    ]
     deps += [
+      ":freedesktop_secret_key_provider",
       ":secret_portal_key_provider",
       "//components/dbus",
       "//components/prefs:test_support",
diff --git a/components/os_crypt/async/browser/fallback_linux_key_provider.cc b/components/os_crypt/async/browser/fallback_linux_key_provider.cc
new file mode 100644
index 0000000..48d39d8
--- /dev/null
+++ b/components/os_crypt/async/browser/fallback_linux_key_provider.cc
@@ -0,0 +1,43 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/os_crypt/async/browser/fallback_linux_key_provider.h"
+
+#include <utility>
+
+#include "components/os_crypt/async/common/algorithm.mojom.h"
+
+namespace os_crypt_async {
+
+namespace {
+
+// These constants are duplicated from the sync backend.
+constexpr char kEncryptionTag[] = "v10";
+
+// PBKDF2-HMAC-SHA1(1 iteration, key = "peanuts", salt = "saltysalt")
+constexpr auto kV10Key =
+    std::to_array<uint8_t>({0xfd, 0x62, 0x1f, 0xe5, 0xa2, 0xb4, 0x02, 0x53,
+                            0x9d, 0xfa, 0x14, 0x7c, 0xa9, 0x27, 0x27, 0x78});
+
+}  // namespace
+
+FallbackLinuxKeyProvider::FallbackLinuxKeyProvider(bool use_for_encryption)
+    : use_for_encryption_(use_for_encryption) {}
+
+FallbackLinuxKeyProvider::~FallbackLinuxKeyProvider() = default;
+
+void FallbackLinuxKeyProvider::GetKey(KeyCallback callback) {
+  Encryptor::Key key(kV10Key, mojom::Algorithm::kAES128CBC);
+  std::move(callback).Run(kEncryptionTag, std::move(key));
+}
+
+bool FallbackLinuxKeyProvider::UseForEncryption() {
+  return use_for_encryption_;
+}
+
+bool FallbackLinuxKeyProvider::IsCompatibleWithOsCryptSync() {
+  return false;
+}
+
+}  // namespace os_crypt_async
diff --git a/components/os_crypt/async/browser/fallback_linux_key_provider.h b/components/os_crypt/async/browser/fallback_linux_key_provider.h
new file mode 100644
index 0000000..eed0cb59
--- /dev/null
+++ b/components/os_crypt/async/browser/fallback_linux_key_provider.h
@@ -0,0 +1,32 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OS_CRYPT_ASYNC_BROWSER_FALLBACK_LINUX_KEY_PROVIDER_H_
+#define COMPONENTS_OS_CRYPT_ASYNC_BROWSER_FALLBACK_LINUX_KEY_PROVIDER_H_
+
+#include "components/os_crypt/async/browser/key_provider.h"
+
+namespace os_crypt_async {
+
+class FallbackLinuxKeyProvider : public KeyProvider {
+ public:
+  explicit FallbackLinuxKeyProvider(bool use_for_encryption);
+
+  FallbackLinuxKeyProvider(const FallbackLinuxKeyProvider&) = delete;
+  FallbackLinuxKeyProvider& operator=(const FallbackLinuxKeyProvider&) = delete;
+
+  ~FallbackLinuxKeyProvider() override;
+
+  // KeyProvider:
+  void GetKey(KeyCallback callback) override;
+  bool UseForEncryption() override;
+  bool IsCompatibleWithOsCryptSync() override;
+
+ private:
+  const bool use_for_encryption_;
+};
+
+}  // namespace os_crypt_async
+
+#endif  // COMPONENTS_OS_CRYPT_ASYNC_BROWSER_FALLBACK_LINUX_KEY_PROVIDER_H_
diff --git a/components/os_crypt/async/browser/freedesktop_secret_key_provider.cc b/components/os_crypt/async/browser/freedesktop_secret_key_provider.cc
new file mode 100644
index 0000000..f4d7b48
--- /dev/null
+++ b/components/os_crypt/async/browser/freedesktop_secret_key_provider.cc
@@ -0,0 +1,266 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/os_crypt/async/browser/freedesktop_secret_key_provider.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/base64.h"
+#include "base/check.h"
+#include "base/containers/span.h"
+#include "base/environment.h"
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/nix/xdg_util.h"
+#include "base/no_destructor.h"
+#include "base/rand_util.h"
+#include "components/dbus/thread_linux/dbus_thread_linux.h"
+#include "components/os_crypt/async/common/algorithm.mojom.h"
+#include "crypto/encryptor.h"
+#include "crypto/kdf.h"
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+
+namespace os_crypt_async {
+
+namespace {
+
+// These constants are duplicated from the sync backend.
+constexpr char kEncryptionTag[] = "v11";
+constexpr char kSalt[] = "saltysalt";
+constexpr size_t kDerivedKeySizeInBits = 128;
+constexpr size_t kEncryptionIterations = 1;
+
+template <typename ReplyArgs>
+void CallMethod(dbus::ObjectProxy* object_proxy,
+                const std::string& interface_name,
+                const std::string& method_name,
+                const DbusType& arguments,
+                base::OnceCallback<void(std::optional<ReplyArgs>)> callback) {
+  dbus::MethodCall method_call(interface_name, method_name);
+  dbus::MessageWriter writer(&method_call);
+  arguments.Write(&writer);
+  object_proxy->CallMethod(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+      base::BindOnce(
+          [](const std::string& interface_name, const std::string& method_name,
+             base::OnceCallback<void(std::optional<ReplyArgs>)> callback,
+             dbus::Response* response) {
+            if (!response) {
+              std::move(callback).Run(std::nullopt);
+              return;
+            }
+            dbus::MessageReader reader(response);
+            ReplyArgs reply;
+            if (!reply.Read(&reader)) {
+              LOG(ERROR) << "Failed to read reply for " << interface_name << "."
+                         << method_name << ": expected type "
+                         << ReplyArgs::GetSignature() << " but got type "
+                         << response->GetSignature();
+              std::move(callback).Run(std::nullopt);
+              return;
+            }
+            std::move(callback).Run(std::move(reply));
+          },
+          interface_name, method_name, std::move(callback)));
+}
+
+scoped_refptr<dbus::Bus> CreateBus() {
+  dbus::Bus::Options options;
+  options.bus_type = dbus::Bus::SESSION;
+  options.connection_type = dbus::Bus::PRIVATE;
+  options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
+  return base::MakeRefCounted<dbus::Bus>(options);
+}
+
+}  // namespace
+
+FreedesktopSecretKeyProvider::FreedesktopSecretKeyProvider(
+    bool use_for_encryption,
+    const std::string& product_name,
+    scoped_refptr<dbus::Bus> bus)
+    : use_for_encryption_(use_for_encryption),
+      product_name_(product_name),
+      bus_(std::move(bus)) {
+  if (!bus_) {
+    bus_ = CreateBus();
+  }
+}
+
+FreedesktopSecretKeyProvider::~FreedesktopSecretKeyProvider() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void FreedesktopSecretKeyProvider::GetKey(KeyCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(callback);
+  key_callback_ = std::move(callback);
+
+  if (!secret_for_testing_.empty()) {
+    DeriveKeyFromSecret(base::as_byte_span(secret_for_testing_));
+    return;
+  }
+
+  dbus_utils::CheckForServiceAndStart(
+      bus_, kSecretServiceName,
+      base::BindOnce(&FreedesktopSecretKeyProvider::OnServiceStarted,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+bool FreedesktopSecretKeyProvider::UseForEncryption() {
+  return use_for_encryption_;
+}
+
+bool FreedesktopSecretKeyProvider::IsCompatibleWithOsCryptSync() {
+  return true;
+}
+
+void FreedesktopSecretKeyProvider::OnServiceStarted(
+    std::optional<bool> service_started) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!service_started.value_or(false)) {
+    FinalizeFailure();
+    return;
+  }
+
+  auto* service_proxy = bus_->GetObjectProxy(
+      kSecretServiceName, dbus::ObjectPath(kSecretServicePath));
+  CallMethod(service_proxy, kSecretServiceInterface, kMethodReadAlias,
+             DbusString(kDefaultAlias),
+             base::BindOnce(&FreedesktopSecretKeyProvider::OnReadAliasDefault,
+                            weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FreedesktopSecretKeyProvider::OnReadAliasDefault(
+    std::optional<DbusObjectPath> collection_path) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!collection_path.has_value()) {
+    FinalizeFailure();
+    return;
+  }
+  if (collection_path->value().value() != "/") {
+    default_collection_proxy_ =
+        bus_->GetObjectProxy(kSecretServiceName, collection_path->value());
+    OpenSession();
+  } else {
+    NOTIMPLEMENTED();
+    FinalizeFailure();
+  }
+}
+
+void FreedesktopSecretKeyProvider::OpenSession() {
+  auto* service_proxy = bus_->GetObjectProxy(
+      kSecretServiceName, dbus::ObjectPath(kSecretServicePath));
+  CallMethod(service_proxy, kSecretServiceInterface, kMethodOpenSession,
+             MakeDbusParameters(DbusString(kAlgorithmPlain),
+                                MakeDbusVariant(DbusString(kInputPlain))),
+             base::BindOnce(&FreedesktopSecretKeyProvider::OnOpenSession,
+                            weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FreedesktopSecretKeyProvider::OnOpenSession(
+    std::optional<DbusParameters<DbusVariant, DbusObjectPath>> session_reply) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!session_reply.has_value()) {
+    FinalizeFailure();
+    return;
+  }
+  const auto& [_, result] = session_reply->value();
+  session_proxy_ = bus_->GetObjectProxy(kSecretServiceName, result.value());
+  session_opened_ = true;
+
+  auto search_attrs = MakeDbusArray(MakeDbusDictEntry(
+      DbusString(kApplicationAttributeKey), DbusString(kAppName)));
+
+  CallMethod(default_collection_proxy_, kSecretCollectionInterface,
+             kMethodSearchItems, search_attrs,
+             base::BindOnce(&FreedesktopSecretKeyProvider::OnSearchItems,
+                            weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FreedesktopSecretKeyProvider::OnSearchItems(
+    std::optional<DbusArray<DbusObjectPath>> results) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!results.has_value()) {
+    FinalizeFailure();
+    return;
+  }
+
+  if (results->value().empty()) {
+    NOTIMPLEMENTED();
+    FinalizeFailure();
+    return;
+  }
+
+  auto* item_proxy = bus_->GetObjectProxy(kSecretServiceName,
+                                          results->value().front().value());
+  CallMethod(item_proxy, kSecretItemInterface, kMethodGetSecret,
+             MakeDbusParameters(DbusObjectPath(session_proxy_->object_path())),
+             base::BindOnce(&FreedesktopSecretKeyProvider::OnGetSecret,
+                            weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FreedesktopSecretKeyProvider::OnGetSecret(
+    std::optional<DbusSecret> secret_reply) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!secret_reply.has_value()) {
+    FinalizeFailure();
+    return;
+  }
+
+  const auto& [session_path, parameters, value, content_type] =
+      secret_reply->value();
+  const auto& secret_bytes = value.value();
+  if (!secret_bytes) {
+    FinalizeFailure();
+    return;
+  }
+  if (secret_bytes->size() == 0) {
+    LOG(ERROR) << "GetSecret returned an empty secret.";
+    FinalizeFailure();
+    return;
+  }
+
+  DeriveKeyFromSecret(base::span(*secret_bytes));
+}
+
+void FreedesktopSecretKeyProvider::DeriveKeyFromSecret(
+    base::span<const uint8_t> secret) {
+  static_assert(kDerivedKeySizeInBits % 8 == 0);
+  std::array<uint8_t, kDerivedKeySizeInBits / 8> key_bytes;
+  crypto::kdf::DeriveKeyPbkdf2HmacSha1(
+      {kEncryptionIterations}, secret,
+      base::as_byte_span(base::span_from_cstring(kSalt)), key_bytes,
+      crypto::SubtlePassKey{});
+  Encryptor::Key key(key_bytes, mojom::Algorithm::kAES128CBC);
+  FinalizeSuccess(std::move(key));
+}
+
+void FreedesktopSecretKeyProvider::FinalizeSuccess(Encryptor::Key key) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::move(key_callback_).Run(kEncryptionTag, std::move(key));
+  CloseSession();
+}
+
+void FreedesktopSecretKeyProvider::FinalizeFailure() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!key_callback_) {
+    return;
+  }
+  std::move(key_callback_).Run(std::string(), std::nullopt);
+  CloseSession();
+}
+
+void FreedesktopSecretKeyProvider::CloseSession() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (session_opened_) {
+    CallMethod(session_proxy_, kSecretSessionInterface, kMethodClose,
+               DbusVoid(), base::BindOnce([](std::optional<DbusVoid>) {}));
+  }
+}
+
+}  // namespace os_crypt_async
diff --git a/components/os_crypt/async/browser/freedesktop_secret_key_provider.h b/components/os_crypt/async/browser/freedesktop_secret_key_provider.h
new file mode 100644
index 0000000..aad3ebc
--- /dev/null
+++ b/components/os_crypt/async/browser/freedesktop_secret_key_provider.h
@@ -0,0 +1,132 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OS_CRYPT_ASYNC_BROWSER_FREEDESKTOP_SECRET_KEY_PROVIDER_H_
+#define COMPONENTS_OS_CRYPT_ASYNC_BROWSER_FREEDESKTOP_SECRET_KEY_PROVIDER_H_
+
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "base/files/scoped_file.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "build/branding_buildflags.h"
+#include "components/dbus/properties/types.h"
+#include "components/dbus/utils/check_for_service_and_start.h"
+#include "components/dbus/utils/name_has_owner.h"
+#include "components/os_crypt/async/browser/key_provider.h"
+#include "crypto/encryptor.h"
+#include "dbus/bus.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+
+namespace os_crypt_async {
+
+// FreedesktopSecretKeyProvider uses the org.freedesktop.secrets interface
+// to retrieve a secret from backend (GNOME Keyring, KWallet, KeePassXC),
+// which can then be used to encrypt confidential data.
+class FreedesktopSecretKeyProvider : public KeyProvider {
+ public:
+  FreedesktopSecretKeyProvider(bool use_for_encryption,
+                               const std::string& product_name,
+                               scoped_refptr<dbus::Bus> bus);
+  ~FreedesktopSecretKeyProvider() override;
+
+  // KeyProvider:
+  void GetKey(KeyCallback callback) override;
+  bool UseForEncryption() override;
+  bool IsCompatibleWithOsCryptSync() override;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(FreedesktopSecretKeyProviderTest, BasicHappyPath);
+  friend class FreedesktopSecretKeyProviderCompatTest;
+
+  using DbusSecret = DbusStruct</*session=*/DbusObjectPath,
+                                /*parameters=*/DbusByteArray,
+                                /*value=*/DbusByteArray,
+                                /*content_type=*/DbusString>;
+
+  static constexpr char kSecretServiceName[] = "org.freedesktop.secrets";
+  static constexpr char kSecretServicePath[] = "/org/freedesktop/secrets";
+  static constexpr char kSecretServiceInterface[] =
+      "org.freedesktop.Secret.Service";
+  static constexpr char kSecretCollectionInterface[] =
+      "org.freedesktop.Secret.Collection";
+  static constexpr char kSecretItemInterface[] = "org.freedesktop.Secret.Item";
+  static constexpr char kSecretSessionInterface[] =
+      "org.freedesktop.Secret.Session";
+
+  static constexpr char kMethodReadAlias[] = "ReadAlias";
+  static constexpr char kMethodGetSecret[] = "GetSecret";
+  static constexpr char kMethodOpenSession[] = "OpenSession";
+  static constexpr char kMethodClose[] = "Close";
+  static constexpr char kMethodSearchItems[] = "SearchItems";
+  static constexpr char kPropertiesInterface[] =
+      "org.freedesktop.DBus.Properties";
+  static constexpr char kMethodGet[] = "Get";
+
+  static constexpr char kDefaultAlias[] = "default";
+
+  // These constants are duplicated from the sync backend.
+  static constexpr char kApplicationAttributeKey[] = "application";
+  static constexpr char kSchemaAttributeKey[] = "xdg:schema";
+  static constexpr char kSchemaAttributeValue[] =
+      "chrome_libsecret_os_crypt_password_v2";
+
+  static constexpr char kAlgorithmPlain[] = "plain";
+  static constexpr char kInputPlain[] = "";
+  static constexpr char kMimePlain[] = "text/plain";
+
+  static constexpr char kSecretCollectionLabelProperty[] =
+      "org.freedesktop.Secret.Collection.Label";
+  static constexpr char kSecretItemAttributesProperty[] =
+      "org.freedesktop.Secret.Item.Attributes";
+  static constexpr char kSecretItemLabelProperty[] =
+      "org.freedesktop.Secret.Item.Label";
+  static constexpr char kDefaultCollectionLabel[] = "Default Keyring";
+  static constexpr char kLabelProperty[] = "Label";
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  static constexpr char kAppName[] = "chrome";
+#else
+  static constexpr char kAppName[] = "chromium";
+#endif
+
+  void OnServiceStarted(std::optional<bool> service_started);
+  void OnReadAliasDefault(std::optional<DbusObjectPath> collection_path);
+  void OnOpenSession(
+      std::optional<DbusParameters<DbusVariant, DbusObjectPath>> session_reply);
+  void OnSearchItems(std::optional<DbusArray<DbusObjectPath>> results);
+  void OnGetSecret(std::optional<DbusSecret> secret_reply);
+
+  void OpenSession();
+  void DeriveKeyFromSecret(base::span<const uint8_t> secret);
+  void FinalizeSuccess(Encryptor::Key key);
+  void FinalizeFailure();
+  void CloseSession();
+
+  raw_ptr<dbus::ObjectProxy> default_collection_proxy_ = nullptr;
+  raw_ptr<dbus::ObjectProxy> session_proxy_ = nullptr;
+  bool session_opened_ = false;
+
+  const bool use_for_encryption_;
+  const std::string product_name_;
+  scoped_refptr<dbus::Bus> bus_;
+  KeyCallback key_callback_;
+
+  std::string secret_for_testing_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<FreedesktopSecretKeyProvider> weak_ptr_factory_{this};
+};
+
+}  // namespace os_crypt_async
+
+#endif  // COMPONENTS_OS_CRYPT_ASYNC_BROWSER_FREEDESKTOP_SECRET_KEY_PROVIDER_H_
diff --git a/components/os_crypt/async/browser/freedesktop_secret_key_provider_compat_unittest.cc b/components/os_crypt/async/browser/freedesktop_secret_key_provider_compat_unittest.cc
new file mode 100644
index 0000000..981a33ba
--- /dev/null
+++ b/components/os_crypt/async/browser/freedesktop_secret_key_provider_compat_unittest.cc
@@ -0,0 +1,143 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "components/os_crypt/async/browser/fallback_linux_key_provider.h"
+#include "components/os_crypt/async/browser/freedesktop_secret_key_provider.h"
+#include "components/os_crypt/async/browser/os_crypt_async.h"
+#include "components/os_crypt/sync/key_storage_linux.h"
+#include "components/os_crypt/sync/os_crypt.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace os_crypt_async {
+
+namespace {
+
+constexpr char kSecretKey[] = "the_secret_key";
+constexpr char kPlaintext[] = "the_secret_plaintext";
+
+class OSCryptMockerLinux : public KeyStorageLinux {
+ public:
+  OSCryptMockerLinux() = default;
+
+  OSCryptMockerLinux(const OSCryptMockerLinux&) = delete;
+  OSCryptMockerLinux& operator=(const OSCryptMockerLinux&) = delete;
+
+  ~OSCryptMockerLinux() override = default;
+
+  bool Init() override { return true; }
+  std::optional<std::string> GetKeyImpl() override { return std::nullopt; }
+};
+
+std::unique_ptr<KeyStorageLinux> CreateNewMock() {
+  return std::make_unique<OSCryptMockerLinux>();
+}
+
+}  // namespace
+
+// This class tests that FreedesktopSecretKeyProvider is forwards and backwards
+// compatible with OSCrypt.
+class FreedesktopSecretKeyProviderCompatTest : public ::testing::Test {
+ protected:
+  Encryptor GetEncryptorInstance(bool v11) {
+    std::vector<std::pair<size_t, std::unique_ptr<KeyProvider>>> providers;
+    if (v11) {
+      auto provider = std::make_unique<FreedesktopSecretKeyProvider>(
+          /*use_for_encryption=*/true, "", nullptr);
+      provider->secret_for_testing_ = kSecretKey;
+      providers.emplace_back(0, std::move(provider));
+    } else {
+      providers.emplace_back(0, std::make_unique<FallbackLinuxKeyProvider>(
+                                    /*use_for_encryption=*/true));
+    }
+    OSCryptAsync factory(std::move(providers));
+
+    base::RunLoop run_loop;
+    std::optional<Encryptor> encryptor;
+    auto subscription =
+        factory.GetInstance(base::BindLambdaForTesting(
+                                [&](Encryptor encryptor_param, bool success) {
+                                  EXPECT_TRUE(success);
+                                  encryptor.emplace(std::move(encryptor_param));
+                                  run_loop.Quit();
+                                }),
+                            Encryptor::Option::kNone);
+    run_loop.Run();
+    return std::move(*encryptor);
+  }
+
+  void TearDown() override { OSCrypt::ClearCacheForTesting(); }
+
+  base::test::TaskEnvironment task_environment_;
+};
+
+TEST_F(FreedesktopSecretKeyProviderCompatTest, IsAvailable) {
+  OSCrypt::SetEncryptionPasswordForTesting(kSecretKey);
+  Encryptor encryptor = GetEncryptorInstance(/*v11=*/true);
+
+  ASSERT_TRUE(encryptor.IsEncryptionAvailable());
+  ASSERT_TRUE(encryptor.IsDecryptionAvailable());
+}
+
+TEST_F(FreedesktopSecretKeyProviderCompatTest, DecryptOldV11) {
+  OSCrypt::SetEncryptionPasswordForTesting(kSecretKey);
+  Encryptor encryptor = GetEncryptorInstance(/*v11=*/true);
+
+  std::string ciphertext;
+  ASSERT_TRUE(OSCrypt::EncryptString(kPlaintext, &ciphertext));
+  EXPECT_TRUE(base::StartsWith(ciphertext, "v11"));
+
+  std::string decrypted;
+  EXPECT_TRUE(encryptor.DecryptString(ciphertext, &decrypted));
+  EXPECT_EQ(kPlaintext, decrypted);
+}
+
+TEST_F(FreedesktopSecretKeyProviderCompatTest, EncryptForOldV11) {
+  OSCrypt::SetEncryptionPasswordForTesting(kSecretKey);
+  Encryptor encryptor = GetEncryptorInstance(/*v11=*/true);
+
+  std::string ciphertext;
+  ASSERT_TRUE(encryptor.EncryptString(kPlaintext, &ciphertext));
+  EXPECT_TRUE(base::StartsWith(ciphertext, "v11"));
+
+  std::string decrypted;
+  EXPECT_TRUE(OSCrypt::DecryptString(ciphertext, &decrypted));
+  EXPECT_EQ(kPlaintext, decrypted);
+}
+
+TEST_F(FreedesktopSecretKeyProviderCompatTest, DecryptOldV10) {
+  OSCrypt::UseMockKeyStorageForTesting(base::BindOnce(&CreateNewMock));
+  Encryptor encryptor = GetEncryptorInstance(/*v11=*/false);
+
+  std::string ciphertext;
+  ASSERT_TRUE(OSCrypt::EncryptString(kPlaintext, &ciphertext));
+  EXPECT_TRUE(base::StartsWith(ciphertext, "v10"));
+
+  std::string decrypted;
+  EXPECT_TRUE(encryptor.DecryptString(ciphertext, &decrypted));
+  EXPECT_EQ(kPlaintext, decrypted);
+}
+
+TEST_F(FreedesktopSecretKeyProviderCompatTest, EncryptForOldV10) {
+  OSCrypt::UseMockKeyStorageForTesting(base::BindOnce(&CreateNewMock));
+  Encryptor encryptor = GetEncryptorInstance(/*v11=*/false);
+
+  std::string ciphertext;
+  ASSERT_TRUE(encryptor.EncryptString(kPlaintext, &ciphertext));
+  EXPECT_TRUE(base::StartsWith(ciphertext, "v10"));
+
+  std::string decrypted;
+  EXPECT_TRUE(OSCrypt::DecryptString(ciphertext, &decrypted));
+  EXPECT_EQ(kPlaintext, decrypted);
+}
+
+}  // namespace os_crypt_async
diff --git a/components/os_crypt/async/browser/freedesktop_secret_key_provider_unittest.cc b/components/os_crypt/async/browser/freedesktop_secret_key_provider_unittest.cc
new file mode 100644
index 0000000..0568d22
--- /dev/null
+++ b/components/os_crypt/async/browser/freedesktop_secret_key_provider_unittest.cc
@@ -0,0 +1,225 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/os_crypt/async/browser/freedesktop_secret_key_provider.h"
+
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "base/files/scoped_file.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/test/bind.h"
+#include "build/branding_buildflags.h"
+#include "components/dbus/properties/types.h"
+#include "components/dbus/utils/name_has_owner.h"
+#include "crypto/encryptor.h"
+#include "dbus/message.h"
+#include "dbus/mock_bus.h"
+#include "dbus/mock_object_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::Return;
+
+namespace os_crypt_async {
+
+namespace {
+
+constexpr char kProductName[] = "test_product";
+constexpr char kCollectionPath[] =
+    "/org/freedesktop/secrets/collection/default";
+constexpr char kSessionPath[] = "/org/freedesktop/secrets/session/test_session";
+constexpr char kItemPath[] =
+    "/org/freedesktop/secrets/collection/default/item0";
+
+constexpr char kFakeSecret[] = "c3VwZXJfc2VjcmV0X2tleQ==";
+
+template <typename T>
+class MatchArgs {
+ public:
+  using is_gtest_matcher = void;
+
+  explicit MatchArgs(T&& args) : args_(std::forward<T>(args)) {}
+
+  bool MatchAndExplain(const DbusVariant& match, std::ostream*) const {
+    const T* match_args = match.GetAs<T>();
+    return match_args && *match_args == args_;
+  }
+
+  void DescribeTo(std::ostream* os) const { *os << "DbusTypes match"; }
+
+  void DescribeNegationTo(std::ostream* os) const {
+    *os << "DbusTypes mismatch";
+  }
+
+ private:
+  T args_;
+};
+
+template <typename T>
+auto RespondWith(T&& args) {
+  return [args = std::move(args)](
+             const std::string&, const std::string&, DbusVariant,
+             dbus::ObjectProxy::ResponseCallback* callback) {
+    auto response = dbus::Response::CreateEmpty();
+    dbus::MessageWriter writer(response.get());
+    args.Write(&writer);
+    std::move(*callback).Run(response.get());
+  };
+}
+
+class MockObjectProxyWithTypedCalls : public dbus::MockObjectProxy {
+ public:
+  MockObjectProxyWithTypedCalls(dbus::Bus* bus,
+                                const std::string& service_name,
+                                const dbus::ObjectPath& object_path)
+      : dbus::MockObjectProxy(bus, service_name, object_path) {
+    // Forward to Call().
+    EXPECT_CALL(*this, DoCallMethod(_, _, _))
+        .Times(AtLeast(0))
+        .WillRepeatedly([this](dbus::MethodCall* method_call, int timeout_ms,
+                               dbus::ObjectProxy::ResponseCallback* callback) {
+          dbus::MessageReader reader(method_call);
+          auto args = ReadDbusMessage(&reader);
+          Call(method_call->GetInterface(), method_call->GetMember(),
+               std::move(args), callback);
+        });
+  }
+
+  MOCK_METHOD4(Call,
+               void(const std::string& interface,
+                    const std::string& method_name,
+                    DbusVariant args,
+                    dbus::ObjectProxy::ResponseCallback* callback));
+
+ protected:
+  ~MockObjectProxyWithTypedCalls() override = default;
+};
+
+}  // namespace
+
+TEST(FreedesktopSecretKeyProviderTest, BasicHappyPath) {
+  auto mock_bus = base::MakeRefCounted<dbus::MockBus>(dbus::Bus::Options());
+
+  // Initialize object proxies
+  auto mock_dbus_proxy = base::MakeRefCounted<MockObjectProxyWithTypedCalls>(
+      mock_bus.get(), DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS));
+  EXPECT_CALL(*mock_bus, GetObjectProxy(DBUS_SERVICE_DBUS,
+                                        dbus::ObjectPath(DBUS_PATH_DBUS)))
+      .WillRepeatedly(Return(mock_dbus_proxy.get()));
+  auto mock_service_proxy = base::MakeRefCounted<MockObjectProxyWithTypedCalls>(
+      mock_bus.get(), FreedesktopSecretKeyProvider::kSecretServiceName,
+      dbus::ObjectPath(FreedesktopSecretKeyProvider::kSecretServicePath));
+  EXPECT_CALL(
+      *mock_bus,
+      GetObjectProxy(
+          FreedesktopSecretKeyProvider::kSecretServiceName,
+          dbus::ObjectPath(FreedesktopSecretKeyProvider::kSecretServicePath)))
+      .WillRepeatedly(Return(mock_service_proxy.get()));
+  auto mock_collection_proxy =
+      base::MakeRefCounted<MockObjectProxyWithTypedCalls>(
+          mock_bus.get(), FreedesktopSecretKeyProvider::kSecretServiceName,
+          dbus::ObjectPath(kCollectionPath));
+  EXPECT_CALL(*mock_bus,
+              GetObjectProxy(FreedesktopSecretKeyProvider::kSecretServiceName,
+                             dbus::ObjectPath(kCollectionPath)))
+      .WillRepeatedly(Return(mock_collection_proxy.get()));
+  auto mock_item_proxy = base::MakeRefCounted<MockObjectProxyWithTypedCalls>(
+      mock_bus.get(), FreedesktopSecretKeyProvider::kSecretServiceName,
+      dbus::ObjectPath(kItemPath));
+  EXPECT_CALL(*mock_bus,
+              GetObjectProxy(FreedesktopSecretKeyProvider::kSecretServiceName,
+                             dbus::ObjectPath(kItemPath)))
+      .WillRepeatedly(Return(mock_item_proxy.get()));
+  auto mock_session_proxy = base::MakeRefCounted<MockObjectProxyWithTypedCalls>(
+      mock_bus.get(), FreedesktopSecretKeyProvider::kSecretServiceName,
+      dbus::ObjectPath(kSessionPath));
+  EXPECT_CALL(*mock_bus,
+              GetObjectProxy(FreedesktopSecretKeyProvider::kSecretServiceName,
+                             dbus::ObjectPath(kSessionPath)))
+      .WillRepeatedly(Return(mock_session_proxy.get()));
+
+  // NameHasOwner for Secret Service
+  EXPECT_CALL(*mock_dbus_proxy,
+              Call(DBUS_INTERFACE_DBUS, "NameHasOwner",
+                   MatchArgs(DbusString(
+                       FreedesktopSecretKeyProvider::kSecretServiceName)),
+                   _))
+      .WillOnce(RespondWith(DbusBoolean(true)));
+
+  // ReadAlias("default")
+  EXPECT_CALL(
+      *mock_service_proxy,
+      Call(FreedesktopSecretKeyProvider::kSecretServiceInterface,
+           FreedesktopSecretKeyProvider::kMethodReadAlias,
+           MatchArgs(DbusString(FreedesktopSecretKeyProvider::kDefaultAlias)),
+           _))
+      .WillOnce(RespondWith(DbusObjectPath(dbus::ObjectPath(kCollectionPath))));
+
+  // OpenSession
+  EXPECT_CALL(
+      *mock_service_proxy,
+      Call(FreedesktopSecretKeyProvider::kSecretServiceInterface,
+           FreedesktopSecretKeyProvider::kMethodOpenSession,
+           MatchArgs(MakeDbusParameters(
+               DbusString(FreedesktopSecretKeyProvider::kAlgorithmPlain),
+               MakeDbusVariant(
+                   DbusString(FreedesktopSecretKeyProvider::kInputPlain)))),
+           _))
+      .WillOnce(RespondWith(
+          MakeDbusParameters(MakeDbusVariant(DbusString("")),
+                             DbusObjectPath(dbus::ObjectPath(kSessionPath)))));
+
+  // SearchItems
+  EXPECT_CALL(
+      *mock_collection_proxy,
+      Call(FreedesktopSecretKeyProvider::kSecretCollectionInterface,
+           FreedesktopSecretKeyProvider::kMethodSearchItems,
+           MatchArgs(MakeDbusArray(MakeDbusDictEntry(
+               DbusString(
+                   FreedesktopSecretKeyProvider::kApplicationAttributeKey),
+               DbusString(FreedesktopSecretKeyProvider::kAppName)))),
+           _))
+      .WillOnce(RespondWith(
+          MakeDbusArray(DbusObjectPath(dbus::ObjectPath(kItemPath)))));
+
+  // GetSecret
+  EXPECT_CALL(
+      *mock_item_proxy,
+      Call(FreedesktopSecretKeyProvider::kSecretItemInterface,
+           FreedesktopSecretKeyProvider::kMethodGetSecret,
+           MatchArgs(DbusObjectPath(dbus::ObjectPath(kSessionPath))), _))
+      .WillOnce(RespondWith(MakeDbusStruct(
+          DbusObjectPath(dbus::ObjectPath(kSessionPath)),
+          DbusByteArray(base::MakeRefCounted<base::RefCountedString>("")),
+          DbusByteArray(
+              base::MakeRefCounted<base::RefCountedString>(kFakeSecret)),
+          DbusString(FreedesktopSecretKeyProvider::kMimePlain))));
+
+  // Close
+  EXPECT_CALL(*mock_session_proxy,
+              Call(FreedesktopSecretKeyProvider::kSecretSessionInterface,
+                   FreedesktopSecretKeyProvider::kMethodClose,
+                   MatchArgs(DbusVoid()), _))
+      .WillOnce(RespondWith(DbusVoid()));
+
+  FreedesktopSecretKeyProvider provider(/*use_for_encryption=*/true,
+                                        kProductName, mock_bus);
+  std::string tag;
+  std::optional<Encryptor::Key> key;
+  provider.GetKey(base::BindLambdaForTesting(
+      [&](const std::string& returned_tag,
+          std::optional<Encryptor::Key> returned_key) {
+        tag = returned_tag;
+        key = std::move(returned_key);
+      }));
+  EXPECT_EQ(tag, "v11");
+  EXPECT_TRUE(key.has_value());
+}
+
+}  // namespace os_crypt_async
diff --git a/components/os_crypt/async/common/algorithm.mojom b/components/os_crypt/async/common/algorithm.mojom
index 528e29d..6a153c5 100644
--- a/components/os_crypt/async/common/algorithm.mojom
+++ b/components/os_crypt/async/common/algorithm.mojom
@@ -9,4 +9,7 @@
   // Algorithm used on Windows: 256 bit key with 96 bit random nonce at the
   // start of the data.
   kAES256GCM,
+
+  // Compatible with "v11" os_crypt_sync encryption on Linux.
+  kAES128CBC,
 };
diff --git a/components/os_crypt/async/common/encryptor.cc b/components/os_crypt/async/common/encryptor.cc
index 247062f..53c0cb8 100644
--- a/components/os_crypt/async/common/encryptor.cc
+++ b/components/os_crypt/async/common/encryptor.cc
@@ -16,6 +16,7 @@
 #include "components/os_crypt/async/common/algorithm.mojom.h"
 #include "components/os_crypt/sync/os_crypt.h"
 #include "crypto/aead.h"
+#include "crypto/aes_cbc.h"
 #include "crypto/random.h"
 #include "mojo/public/cpp/bindings/default_construct_tag.h"
 #include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
@@ -34,6 +35,11 @@
 
 constexpr size_t kNonceLength = 96 / 8;  // AES_GCM_NONCE_LENGTH
 
+constexpr std::array<uint8_t, crypto::aes_cbc::kBlockSize> kFixedIvForAes128Cbc{
+    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
+    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
+};
+
 }  // namespace
 
 Encryptor::Key::Key(base::span<const uint8_t> key,
@@ -59,6 +65,9 @@
     case mojom::Algorithm::kAES256GCM:
       CHECK_EQ(key.size(), Key::kAES256GCMKeySize);
       break;
+    case mojom::Algorithm::kAES128CBC:
+      CHECK_EQ(key.size(), Key::kAES128CBCKeySize);
+      break;
   }
 }
 
@@ -141,6 +150,11 @@
       ciphertext.insert(ciphertext.begin(), nonce.cbegin(), nonce.cend());
       return ciphertext;
     }
+    case mojom::Algorithm::kAES128CBC: {
+      std::vector<uint8_t> ciphertext = crypto::aes_cbc::Encrypt(
+          key_, base::as_byte_span(kFixedIvForAes128Cbc), plaintext);
+      return ciphertext;
+    }
   }
   LOG(FATAL) << "Unsupported algorithm" << static_cast<int>(*algorithm_);
 }
@@ -177,6 +191,21 @@
 
       return aead.Open(data, nonce, /*additional_data=*/{});
     }
+    case mojom::Algorithm::kAES128CBC: {
+      auto plaintext =
+          crypto::aes_cbc::Decrypt(key_, kFixedIvForAes128Cbc, ciphertext);
+      if (plaintext.has_value()) {
+        return plaintext;
+      }
+      // Decryption failed - try the empty fallback key. See
+      // https://crbug.com/40055416.
+      // PBKDF2-HMAC-SHA1(1 iteration, key = "", salt = "saltysalt")
+      constexpr auto kEmptyKey = std::to_array<uint8_t>(
+          {0xd0, 0xd0, 0xec, 0x9c, 0x7d, 0x77, 0xd4, 0x3a, 0xc5, 0x41, 0x87,
+           0xfa, 0x48, 0x18, 0xd1, 0x7f});
+      return crypto::aes_cbc::Decrypt(kEmptyKey, kFixedIvForAes128Cbc,
+                                      ciphertext);
+    }
   }
   LOG(FATAL) << "Unsupported algorithm" << static_cast<int>(*algorithm_);
 }
diff --git a/components/os_crypt/async/common/encryptor.h b/components/os_crypt/async/common/encryptor.h
index ffd939a1..b3809d1 100644
--- a/components/os_crypt/async/common/encryptor.h
+++ b/components/os_crypt/async/common/encryptor.h
@@ -51,6 +51,7 @@
     ~Key();
 
     static constexpr size_t kAES256GCMKeySize = 256u / 8u;
+    static constexpr size_t kAES128CBCKeySize = 128u / 8u;
 
     // Mojo uses this public constructor for serialization.
     explicit Key(mojo::DefaultConstruct::Tag);
diff --git a/components/os_crypt/async/common/encryptor_mojom_traits.cc b/components/os_crypt/async/common/encryptor_mojom_traits.cc
index afb94b4..6c53660 100644
--- a/components/os_crypt/async/common/encryptor_mojom_traits.cc
+++ b/components/os_crypt/async/common/encryptor_mojom_traits.cc
@@ -66,6 +66,10 @@
   switch (data.algorithm()) {
     case os_crypt_async::mojom::Algorithm::kAES256GCM:
       key_size.emplace(os_crypt_async::Encryptor::Key::kAES256GCMKeySize);
+      break;
+    case os_crypt_async::mojom::Algorithm::kAES128CBC:
+      key_size.emplace(os_crypt_async::Encryptor::Key::kAES128CBCKeySize);
+      break;
   }
 
   if (!key_size.has_value()) {
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestWebContentsData.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestWebContentsData.java
index 4172a08..9e10b03 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestWebContentsData.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestWebContentsData.java
@@ -59,10 +59,8 @@
      * @return Whether there has been an activationless PaymentRequest.show() for this WebContents.
      */
     public boolean hadActivationlessShow() {
-        if (mWebContents == null) return false;
-        WebContents webContents = mWebContents.get();
+        WebContents webContents = getWebContents();
         if (webContents == null || webContents.isDestroyed()) return false;
-
         return PaymentRequestWebContentsDataJni.get().hadActivationlessShow(webContents);
     }
 
@@ -71,10 +69,8 @@
      * tracked on the native side in PaymentRequestWebContentsManager.
      */
     public void recordActivationlessShow() {
-        if (mWebContents == null) return;
-        WebContents webContents = mWebContents.get();
+        WebContents webContents = getWebContents();
         if (webContents == null || webContents.isDestroyed()) return;
-
         PaymentRequestWebContentsDataJni.get().recordActivationlessShow(webContents);
     }
 
diff --git a/components/pdf/browser/pdf_document_helper.cc b/components/pdf/browser/pdf_document_helper.cc
index 0e67647..cfb3c625 100644
--- a/components/pdf/browser/pdf_document_helper.cc
+++ b/components/pdf/browser/pdf_document_helper.cc
@@ -253,6 +253,15 @@
   remote_pdf_client_->GetPageText(page_index, std::move(callback));
 }
 
+void PDFDocumentHelper::GetMostVisiblePageIndex(
+    pdf::mojom::PdfListener::GetMostVisiblePageIndexCallback callback) {
+  if (!remote_pdf_client_) {
+    std::move(callback).Run(std::nullopt);
+    return;
+  }
+  remote_pdf_client_->GetMostVisiblePageIndex(std::move(callback));
+}
+
 void PDFDocumentHelper::OnSelectionEvent(ui::SelectionEventType event) {
   // Should be handled by `TouchSelectionControllerClientAura`.
   NOTREACHED();
diff --git a/components/pdf/browser/pdf_document_helper.h b/components/pdf/browser/pdf_document_helper.h
index ca3be36..97e3b34c 100644
--- a/components/pdf/browser/pdf_document_helper.h
+++ b/components/pdf/browser/pdf_document_helper.h
@@ -99,6 +99,8 @@
 
   void GetPageText(int32_t page_index,
                    pdf::mojom::PdfListener::GetPageTextCallback callback);
+  void GetMostVisiblePageIndex(
+      pdf::mojom::PdfListener::GetMostVisiblePageIndexCallback callback);
 
  private:
   friend class content::DocumentUserData<PDFDocumentHelper>;
diff --git a/components/pdf/browser/pdf_document_helper_browsertest.cc b/components/pdf/browser/pdf_document_helper_browsertest.cc
index d24e5e9..d56e60a 100644
--- a/components/pdf/browser/pdf_document_helper_browsertest.cc
+++ b/components/pdf/browser/pdf_document_helper_browsertest.cc
@@ -49,6 +49,10 @@
               GetPageText,
               (int32_t, GetPageTextCallback callback),
               (override));
+  MOCK_METHOD(void,
+              GetMostVisiblePageIndex,
+              (GetMostVisiblePageIndexCallback callback),
+              (override));
 };
 
 class TestPDFDocumentHelperClient : public PDFDocumentHelperClient {
diff --git a/components/safe_browsing/content/browser/download/download_stats.cc b/components/safe_browsing/content/browser/download/download_stats.cc
index 9a92066..5504b83e 100644
--- a/components/safe_browsing/content/browser/download/download_stats.cc
+++ b/components/safe_browsing/content/browser/download/download_stats.cc
@@ -112,23 +112,6 @@
       base::UserMetricsAction("SafeBrowsing.Download.WarningBypassed"));
 }
 
-void RecordDownloadOpenedLatency(download::DownloadDangerType danger_type,
-                                 download::DownloadContent download_content,
-                                 base::Time download_opened_time,
-                                 base::Time download_end_time,
-                                 bool show_download_in_folder) {
-  if (danger_type != download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) {
-    return;
-  }
-  std::string metric_suffix =
-      show_download_in_folder ? ".ShowInFolder" : ".OpenDirectly";
-  base::UmaHistogramCustomTimes(
-      "SBClientDownload.SafeDownloadOpenedLatency2" + metric_suffix,
-      /* sample */ download_opened_time - download_end_time,
-      /* min */ base::Seconds(1),
-      /* max */ base::Days(1), /* buckets */ 50);
-}
-
 void RecordDownloadFileTypeAttributes(
     DownloadFileType::DangerLevel danger_level,
     bool has_user_gesture,
diff --git a/components/safe_browsing/content/browser/download/download_stats.h b/components/safe_browsing/content/browser/download/download_stats.h
index 22de644..50650cf 100644
--- a/components/safe_browsing/content/browser/download/download_stats.h
+++ b/components/safe_browsing/content/browser/download/download_stats.h
@@ -56,22 +56,6 @@
     bool is_https,
     bool has_user_gesture);
 
-// Records the latency after completion a download was opened from the download
-// shelf/bubble or the chrome://downloads page, or show in folder was clicked.
-void RecordDownloadOpenedLatency(download::DownloadDangerType danger_type,
-                                 download::DownloadContent download_content,
-                                 base::Time download_opened_time,
-                                 base::Time download_end_time,
-                                 bool show_download_in_folder);
-
-// Records the latency after completion for when a download was opened (via the
-// shelf/bubble or chrome://downloads), or show in folder was clicked, by
-// extension type.
-void RecordDownloadOpenedLatencyFileType(
-    download::DownloadContent download_content,
-    base::Time download_opened_time,
-    base::Time download_end_time);
-
 // Records the attributes of a download.
 void RecordDownloadFileTypeAttributes(
     DownloadFileType::DangerLevel danger_level,
diff --git a/components/safe_browsing/content/browser/download/download_stats_unittest.cc b/components/safe_browsing/content/browser/download/download_stats_unittest.cc
index 95a2aa0..58d4795b 100644
--- a/components/safe_browsing/content/browser/download/download_stats_unittest.cc
+++ b/components/safe_browsing/content/browser/download/download_stats_unittest.cc
@@ -85,39 +85,6 @@
                    "SafeBrowsing.Download.WarningBypassed"));
 }
 
-TEST(SafeBrowsingDownloadStatsTest, RecordDownloadOpened) {
-  base::HistogramTester histogram_tester;
-
-  base::Time download_end_time = base::Time::Now();
-  download::DownloadContent fake_content =
-      download::DownloadContent::kSpreadSheet;
-  // Not logged for dangerous downloads.
-  RecordDownloadOpenedLatency(
-      download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT,
-      fake_content, download_end_time + base::Days(1), download_end_time,
-      /*show_download_in_folder=*/false);
-  histogram_tester.ExpectTotalCount(
-      "SBClientDownload.SafeDownloadOpenedLatency2.OpenDirectly", 0);
-
-  RecordDownloadOpenedLatency(
-      download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      fake_content, download_end_time + base::Days(1), download_end_time,
-      /*show_download_in_folder=*/false);
-  histogram_tester.ExpectTimeBucketCount(
-      "SBClientDownload.SafeDownloadOpenedLatency2.OpenDirectly",
-      /*sample=*/base::Days(1),
-      /*count=*/1);
-
-  RecordDownloadOpenedLatency(
-      download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-      fake_content, download_end_time + base::Hours(5), download_end_time,
-      /*show_download_in_folder=*/true);
-  histogram_tester.ExpectTimeBucketCount(
-      "SBClientDownload.SafeDownloadOpenedLatency2.ShowInFolder",
-      /*sample=*/base::Hours(5),
-      /*count=*/1);
-}
-
 TEST(SafeBrowsingDownloadStatsTest, RecordDownloadFileTypeAttributes) {
   {
     base::HistogramTester histogram_tester;
diff --git a/content/browser/android/selection/selection_popup_controller.cc b/content/browser/android/selection/selection_popup_controller.cc
index 91e6f41..f511d0e 100644
--- a/content/browser/android/selection/selection_popup_controller.cc
+++ b/content/browser/android/selection/selection_popup_controller.cc
@@ -9,6 +9,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
+#include "base/feature_list.h"
 #include "content/browser/android/selection/composited_touch_handle_drawable.h"
 #include "content/browser/gpu/gpu_data_manager_impl.h"
 #include "content/browser/renderer_host/render_widget_host_view_android.h"
@@ -63,6 +64,10 @@
   return enabled;
 }
 
+BASE_FEATURE(kDismissMagnifierOnViewSwap,
+             "DismissMagnifierOnViewSwap",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 }  // namespace
 
 static jboolean
@@ -200,6 +205,17 @@
   if (new_rwhva)
     new_rwhva->set_selection_popup_controller(this);
   rwhva_ = new_rwhva;
+
+  if (!base::FeatureList::IsEnabled(kDismissMagnifierOnViewSwap)) {
+    return;
+  }
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> obj = java_obj_.get(env);
+  if (obj.is_null()) {
+    return;
+  }
+
+  Java_SelectionPopupControllerImpl_renderWidgetHostViewChanged(env, obj);
 }
 
 void SelectionPopupController::OnSelectionEvent(
diff --git a/content/browser/webui/web_ui_mojo_browsertest.cc b/content/browser/webui/web_ui_mojo_browsertest.cc
index 6a5858f6..fa265af3 100644
--- a/content/browser/webui/web_ui_mojo_browsertest.cc
+++ b/content/browser/webui/web_ui_mojo_browsertest.cc
@@ -48,6 +48,7 @@
 #include "mojo/public/cpp/bindings/binder_map.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/mojom/base/time.mojom.h"
 #include "third_party/blink/public/common/chrome_debug_urls.h"
 
 namespace content {
@@ -113,6 +114,10 @@
         dict_ptr ? dict_ptr->Clone() : nullptr);
   }
 
+  void EchoTypemaps(base::Time time, EchoTypemapsCallback cb) override {
+    std::move(cb).Run(time);
+  }
+
  private:
   mojo::Receiver<mojom::WebUITsMojoTestCache> receiver_;
   std::map<GURL, std::string> cache_;
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
index 1f75933..52bd611 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
@@ -1503,6 +1503,13 @@
         }
     }
 
+    @CalledByNative
+    private void renderWidgetHostViewChanged() {
+        if (getMagnifierAnimator() != null) {
+            getMagnifierAnimator().handleDragStopped();
+        }
+    }
+
     // All coordinates are in DIP.
     @VisibleForTesting
     @CalledByNative
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
index 259cb70..8997018 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
@@ -16,7 +16,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
 
 /**
  * This class receives callbacks that act as hooks for various a native web contents events related
@@ -24,16 +23,19 @@
  */
 @NullMarked
 public abstract class WebContentsObserver {
-    // TODO(jdduke): Remove the destroy method and hold observer embedders
-    // responsible for explicit observer detachment.
-    // Using a weak reference avoids cycles that might prevent GC of WebView's WebContents.
-    protected @Nullable WeakReference<WebContents> mWebContents;
+    private @Nullable WebContents mWebContents;
 
     public WebContentsObserver(WebContents webContents) {
-        mWebContents = new WeakReference<WebContents>(webContents);
+        mWebContents = webContents;
         webContents.addObserver(this);
     }
 
+    /** Return the web contents associated with the observer. */
+    @Nullable
+    public WebContents getWebContents() {
+        return mWebContents;
+    }
+
     /**
      * Called when a RenderFrame for renderFrameHost is created in the renderer process. To avoid
      * creating a RenderFrameHost object without necessity, only its id is passed. Call
@@ -244,9 +246,8 @@
     /** Stop observing the web contents and clean up associated references. */
     public void destroy() {
         if (mWebContents == null) return;
-        final WebContents webContents = mWebContents.get();
+        final WebContents webContents = mWebContents;
         mWebContents = null;
-        if (webContents == null) return;
         webContents.removeObserver(this);
     }
 
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index fac5ded..e9478ce 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1150,7 +1150,10 @@
     "data/web_ui_ts_test.test-mojom",
     "data/web_ui_ts_test_types.test-mojom",
   ]
-  public_deps = [ "//url/mojom:url_mojom_gurl" ]
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//url/mojom:url_mojom_gurl",
+  ]
   webui_module_path = "/content/test/data"
 
   deps = [ ":web_ui_ts_test_other_mojo_bindings" ]
diff --git a/content/test/data/web_ui_mojo_ts_test.ts b/content/test/data/web_ui_mojo_ts_test.ts
index 1203b95..0eadbbc 100644
--- a/content/test/data/web_ui_mojo_ts_test.ts
+++ b/content/test/data/web_ui_mojo_ts_test.ts
@@ -225,6 +225,13 @@
     }
   }
 
+  {
+    const result = await cache.echoTypemaps(new Date(12321));
+    assert(
+        result.time.getTime() === new Date(12321).getTime(),
+        `unexpected date received ${result.time.getTime()}`);
+  }
+
   return true;
 }
 
diff --git a/content/test/data/web_ui_ts_test.test-mojom b/content/test/data/web_ui_ts_test.test-mojom
index de5ef0a..eb88c8c9 100644
--- a/content/test/data/web_ui_ts_test.test-mojom
+++ b/content/test/data/web_ui_ts_test.test-mojom
@@ -6,6 +6,7 @@
 
 import "content/test/data/web_ui_ts_test_types.test-mojom";
 import "content/test/data/web_ui_ts_test_other_types.test-mojom";
+import "mojo/public/mojom/base/time.mojom";
 import "url/mojom/url.mojom";
 
 enum TestEnum {
@@ -63,4 +64,8 @@
     SimpleMappedType simple_mapped_type,
     NestedMappedType nested_mapped_type,
     StringDict? other_mapped_type);
+
+  EchoTypemaps(
+    mojo_base.mojom.JSTime time
+  ) => (mojo_base.mojom.JSTime time);
 };
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test.py b/content/test/gpu/gpu_tests/gpu_integration_test.py
index e70cfa3..5f82b32 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test.py
@@ -556,8 +556,10 @@
     if not cls._args_changed_this_browser_start:
       return
 
-    # chrome://gpu does not exist for Webview.
-    if cls.browser.browser_type == 'android-webview-instrumentation':
+    # chrome://gpu does not exist for Webview or the Fuchsia cast streaming
+    # shell.
+    if cls.browser.browser_type in ('android-webview-instrumentation',
+                                    'cast-streaming-shell'):
       return
 
     # TODO(crbug.com/376498163): Remove this early return once Telemetry's
diff --git a/crypto/subtle_passkey.h b/crypto/subtle_passkey.h
index ab28bd2..5143935 100644
--- a/crypto/subtle_passkey.h
+++ b/crypto/subtle_passkey.h
@@ -23,6 +23,10 @@
 crypto::SubtlePassKey MakeCryptoPassKey();
 }
 
+namespace os_crypt_async {
+class FreedesktopSecretKeyProvider;
+}
+
 class OSCryptImpl;
 
 namespace crypto {
@@ -57,9 +61,10 @@
   // arbitrary (possibly attacker-supplied) PBKDF2 parameters.
   friend SubtlePassKey chromeos::onc::MakeCryptoPassKey();
 
-  // This class uses custom PBKDF2 parameters and has to keep doing so for
+  // These classes use custom PBKDF2 parameters and have to keep doing so for
   // compatibility with existing persisted data.
   friend class ::OSCryptImpl;
+  friend class os_crypt_async::FreedesktopSecretKeyProvider;
 };
 
 }  // namespace crypto
diff --git a/docs/README.md b/docs/README.md
index 7f36c13..f8ed76d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -463,7 +463,8 @@
 *   [What's Up With Processes - Episode 8](transcripts/wuwt-e08-processes.md)
 *   [What's Up With Site Isolation - Episode 9](transcripts/wuwt-e09-site-isolation.md)
 *   [What's Up With Web Platform - Episode 10](transcripts/wuwt-e10-web-platform.md)
-*   [What's Up With Web Standards - Episode 11](transcriptswuwt-e11-web-standards.md)
+*   [What's Up With Web Standards - Episode 11](transcripts/wuwt-e11-web-standards.md)
+*   [What's Up With Base - Episode 12](transcripts/wuwt-e12-base.md)
 
 ### Probably Obsolete
 *   [TPM Quick Reference](tpm_quick_ref.md) - Trusted Platform Module notes.
diff --git a/docs/transcripts/wuwt-e12-base.md b/docs/transcripts/wuwt-e12-base.md
new file mode 100644
index 0000000..65dcf19
--- /dev/null
+++ b/docs/transcripts/wuwt-e12-base.md
@@ -0,0 +1,1008 @@
+# What’s Up With Base
+
+This is a transcript of [What's Up With
+That](https://www.youtube.com/playlist?list=PL9ioqAuyl6ULIdZQys3fwRxi3G3ns39Hq)
+Episode 12, a 2024 video discussion between [Sharon (yangsharon@chromium.org)
+and Peter
+(pkasting@chromium.org)](https://www.youtube.com/watch?v=hXTcG7DJ3Ms).
+
+The transcript was automatically generated by speech-to-text software. It may
+contain minor errors.
+
+---
+
+Base is one of the lowest level directories in Chromium. What's in there? What
+should you be using? Why don't we just use the C++ standard library?
+
+Notes:
+
+- https://docs.google.com/document/d/1Monua0VRs_JIR-NU9mQ7MO_dw-uiFDK9QAj1Sj_V32g/edit
+
+Links:
+
+- [C++ 201]
+
+---
+
+00:00 SHARON: Hello, and welcome to "What's Up With That?," the series that
+demystifies all things Chrome. I'm your host, Sharon, and today we're talking
+about //base. What is it the base of? What are Chromium-specific types we use?
+How does it fit in with C++ at large? Today's special guest answering all of
+that and more is Peter. He's our newest //base owner, a longtime team member,
+and a driver behind style guide changes, C++ future allowances, and updating to
+new versions of C++. He has a series, [C++ 201], on this channel that helped
+inspire this series. So welcome, Peter.
+
+00:33 PETER: Thank you.
+
+00:33 SHARON: So what is //base?
+
+00:39 PETER: It's one of our lowest level directories, and it pretty much has
+all the low-level stuff everything else depends on.
+
+00:45 SHARON: Sounds important. So can you tell us a bit more about what those
+specific things are and why they're important?
+
+00:51 PETER: Yeah. So stuff goes in //base if it's broadly useful to lots of
+different unrelated places and if it's semantically fundamental, like maybe it
+would go in the STL, at least if it were broadly useful to C++, and if you
+can't move it to a better, more specific subdirectory. Anything that fits those
+is probably a good candidate.
+
+01:17 SHARON: OK, so what are things that do versus don't belong in //base,
+then?
+
+01:24 PETER: If it's something that is maybe useful for programming in general
+but we don't need it in Chrome specifically, then it doesn't go in //base.
+We're not trying to make a toolkit for general-purpose use outside Chrome. If
+it's just speculative-- you came up with a cool idea and you're hoping somebody
+might use it-- then probably don't put it in //base, at least until you can
+actually make various things use it. And also, it shouldn't overlap with
+anything that's already in the STL or absl or elsewhere in //base, at least,
+unless we have some clear guidance on, here's how you pick which alternative to
+use, and ideally, some tooling doing that too.
+
+02:03 SHARON: Yeah, if you look around the Chromium codebase, you see base
+types, you see absl types, and you see standard library types. So when do we
+use each of these libraries? Because certain things-- if you were around before
+we used to use base optional. Now we use absl optional. Or do we use std
+optional now?
+
+02:22 PETER: We do use std optional.
+
+02:22 SHARON: So that changes, and that has changed over time. So when do we
+use which ones?
+
+02:28 PETER: Yeah, we've used all three of those, actually. Generally, all else
+being equal, we will prefer the STL if it's available and absl if it's there
+and //base if it's not in either one. So with optional, we had an optional
+before the STL had it because it got it in C++ 17, and we had optional long
+before that. And then when we allowed absl, we switched to absl optional
+because it was there. And then when we allowed C++ 17, we switched over to
+that. So that's the most common thing. And then //base, in that case-- just be
+used for stuff that supplements the STL. Things that only Chrome needs-- maybe
+they're not usable by all C++ plus everywhere in the world, or we're
+polyfilling something that we're hoping is in an upcoming standard. So we can
+move faster than maybe the upstream library can because we only need to worry
+about us. Some stuff supersedes things in the STL or absl. If we have problems
+with particular APIs and we need to work around things or ban things-- like
+`bit_cast`-- we have our own version of `bit_cast` that warns you if you're
+doing something silly. If we're trying to integrate tightly with something in
+the library to do more than what's in the spec, like we have our own version of
+span, and that's partly because we want to integrate tighter lifetime checks.
+
+03:59 SHARON: Yeah, we'll get into all the different types we have in //base in
+a bit. So what's absl? Because that's something that's newish to Chrome and
+didn't always exist there. So why did we start using it? What is it? Tell us
+more.
+
+04:12 PETER: Yep. absl came out of other teams at Google. Some of the internal
+code in Google's-- you could think of it as Google's version of //base,
+internally. And they said, hey, this is broadly useful. We'd like to make it
+available to the open-source world. And they spent a lot of time, and they
+released this thing called absl. And we allowed that-- I don't remember-- I
+think around 2016 or something-- 2017? And I drove that process, in part
+because there was lots of useful stuff there. I think absl had variant at the
+time. And that was in an upcoming STL, and a lot of our code could have used
+variant. So I allowed us to use absl after getting all the necessary sign-offs.
+And you can think of it as the Google bits of Google's //base that they thought
+would be useful to the world.
+
+05:14 SHARON: OK. Can you tell us a bit about the provenance of //base? Where
+did it come from? The name, I guess, is fairly self-explanatory, but a bit
+about the history of how we got here.
+
+05:26 PETER: Yeah, Chrome started development in mid 2006, and we built things
+internally in Google. So we used a lot of pieces of the same sorts of places
+that absl itself came from, like core utilities in //base. And then once we
+split off to our own repository-- and this is even before public launch-- we
+couldn't depend on the rest of Google anymore. So we copied a few bits over. We
+copied over, for example, a type called `string_piece`, which later became
+proposed to the STL and became `string_view`. So we had an equivalent to
+`string_view` back then. And we copied a few other bits. And then since then,
+it's just been added to ad hoc.
+
+06:06 SHARON: OK. Who owns //base now? Because everyone uses it, but there
+isn't a team that's just a dedicated //base team. So who is out here making
+sure that the fundamentals still work?
+
+06:20 PETER: Yeah, there is no formal core //base team. There's currently an
+OWNERS file with 11 different people in it. Those folks all have other
+particular areas on the team that they nominally are in. Like, Dana is in the
+security side of things. And I work on the UI side of things. And to some
+degree, we bring those hats to //base, so we contribute to areas where we have
+particular expertise. A lot of it is just self-driven. People tend to gravitate
+there when they have an interest in mucking with really low-level C++, doing
+core tooling and API fixes. And so it's very much like any other directory in
+the codebase. Anyone is free to touch it. And if you want to become an owner of
+it, then you can get to touch it lots.
+
+07:19 SHARON: [LAUGHS] Great. Yeah, as someone who is not a hardcore C++
+person, I am very glad there are people who are and keep things running. So
+//base exists in the Chromium source directory. Where can you use it?
+Presumably everywhere within that. But can you tell us about where you can and
+can't use //base?
+
+07:41 PETER: Yeah, mostly everywhere, although there are a few gotchas. So
+there are particular tiny pieces of //base that can't use the rest of //base
+for reasons. There's also a few pieces of our core code that we want to make
+sure don't depend on //base, like down deep in the installer or the Sandbox,
+like code that is extremely low-level, early stuff that needs to run without
+anything else being loaded. Otherwise, in Chrome, you can pretty much freely
+use things with a few exceptions. As you mentioned, stuff outside Chrome-- so
+most stuff in third party-- can't use //base. Interestingly, that sometimes
+includes first-party-ish code. The biggest thing that people get caught out by
+is probably Blink. Code in Blink can't just use any part of //base it wants.
+There's actually an allow-list that a Python script audits. And the main reason
+for that is that Blink uses a memory allocation thing called Oilpan, and it
+needs to make sure that all the APIs are safe to use with that. So that's why
+we have that. But this also means that things that don't live in the Chrome
+repository-- so V8 or Crashpad, our crash core utilities-- don't have //base.
+So often, they'll have their own forks or small copies or things like that.
+
+09:06 SHARON: OK. Yeah. I'm sure if you work in any of those areas, you're
+pretty familiar with what you can and can't use. So let's get into the fun part
+and do a run-through of what exists in //base. So obviously, we're not going to
+cover all of it. It is a huge directory. But there are some types you see more
+than others, so let's run through some of them.
+
+09:23 PETER: Yeah, this is the rare case when I actually think, because the
+stuff in //base is so broadly used and so useful, it is worth team members'
+time to go through the list themselves and just randomly scan down the files in
+each directory and be like, oh, what's that, and look into it. I wouldn't
+normally say that. Chrome is like millions of lines of code and I don't even
+know how many files. But probably worth doing it at some point in base. But
+yes, some things that I found when I was doing this, because I don't have all
+this committed to memory-- PartitionAlloc. So if people have heard of
+MiraclePtr, or even if not, Chrome has its own allocator called PartitionAlloc.
+We use it pretty much everywhere. It's actually kind of its own standalone
+project at this point. It's one of those pieces in //base that can't depend on
+the rest of //base. But it lives inside //base. And if you have ever seen the
+`raw_ptr` or `raw_ref` types-- which, at this point, it's hard to have worked
+in Chromium and not seen `raw_ptr` somewhere-- then that lives in there. We
+have expected. So this is basically a polyfill of the STL's `std::expected`
+from C++ 23. So expected is a type that's mostly used as a return type from
+functions. And it basically is two things. It's a value type for when your
+function worked, which might be void if your function doesn't need to actually
+return a value and you just wanted it to do some stuff, or else it's an error
+type if the function failed, and then it can hold details. So you can think of
+it like a special-purpose variant of two types, with some helpers and stuff
+like that.
+
+11:07 SHARON: What about optional, which seems related to that?
+
+11:14 PETER: So optional and expected both are useful for functions that can
+succeed or fail. Really, the difference is, semantically, that optional is
+usually a value if it succeeds or else no value. So it tells you whether it
+succeeded, and it tells you what the value is if so. It doesn't tell you
+anything on failure, and it's not very useful for the case where success
+doesn't mean returning anything. So if it's a getter-- hey, give me this
+thing-- and it fails, then obviously, you return nothing. But if it's not a
+getter-- it's like, go paint some stuff on the screen, then the only return
+value is, yes, I painted it. So the fact that expected can return void in the
+success case makes it useful for that. And then expected can return details on
+errors. So that makes it more useful for when you want to pass more information
+back or handle failure or something. Not necessary if you actually-- there's no
+information needed on failure. Yeah, it didn't work. We don't care why. In that
+case, maybe optional is still a good idea.
+
+12:24 SHARON: OK. Up next we have `flat_map`, `flat_set`. That feels like a
+//base classic. It's one of the first things, when I was doing code reviews-- I
+was like, oh, you could use a `flat_map` here.
+
+12:32 PETER: Yeah, so this is another polyfill. `flat_map` and `flat_set` are
+actually in C++ 23 also.
+
+12:43 SHARON: OK. Which version of C++ is Chromium using now?
+
+12:43 PETER: Chrome is currently on C++ 20. Basically, `flat_map` and
+`flat_set` are-- let me-- maps and sets or associative containers, I might call
+them, generally store things in a tree-like structure. So that gives them all
+their properties of you can find or insert things in logarithmic time, and
+removal is cheap and lots of other stuff. The problem with trees is that
+because, in memory, they're just pointers to different areas of the heap, this
+has really bad performance in terms of the constant factors. Because,
+basically, every time you traverse to a new element, you're loading a new cache
+line. So `flat_map` and `flat_set` store all their data in a contiguous block.
+It's basically a sorted vector. And on the face of it, you'd think this would
+be bad because sorted vectors, if you remove an element, you have to shift all
+the other elements. And now it's linear time. And that's true. And for very
+large maps and sets, map and set are way better than `flat_map` and `flat_set`
+because big O is important. But for small ones, like the cache-line effects
+dominate and `flat_map`-- is actually much more performant.
+
+14:01 SHARON: What's a good heuristic for "big" or "small" in this case? It's
+one of those, like, this is Google. I forgot how to count that low.
+
+14:07 PETER: We have a doc with more guidance on how to pick which type you
+want. So if you look at base/containers/README.md, there's some information
+there about, how big are these things, and how do I know what stuff to put in
+it. My general mental thought is most code I run into is kind of two buckets.
+Either it's like a dozen things or a thousand things. And those fit pretty well
+into-- a dozen is a `flat_map` and a thousand is a map. If you're somewhere in
+between-- you have a hundred things-- it's probably more iffy. Do some
+benchmarking. But the other thing is performance is not always the concern
+everywhere. I mean, performance matters, but if your code is not hot, then
+binary size might matter more. Or just semantics or not having to change types
+at an API boundary or whatever.
+
+15:04 SHARON: Yeah, you and your code reviewer can go dig that out. Cool. What
+about FilePath?
+
+15:09 PETER: FilePath. So as people who have worked across operating systems
+know, operating systems unfortunately choose to do things differently. Windows
+has to use a back slash instead of a forward slash because they like CP/M. And
+so we need abstractions to do things parse file paths or construct file paths.
+More subtly, the different operating systems also use different encodings for
+their file paths. Mac is UTF-8, which seems sane to me. Windows is UTF-16,
+which is kind of unfortunate, but historically sane. And then Linux is
+actually-- you don't know because Linux is literally whatever encoding was used
+by whoever wrote the file. And it could be anything, and you just use UTF-8 and
+hope for the best, which is what we do. And it mostly works. I don't know.
+There's scary comments in there, and I'm like, I don't want to touch this.
+
+16:10 SHARON: Yeah. Some of the Linux stuff-- it's like, who owns this? And
+it's like--
+
+16:15 PETER: And this is another one where there are STL utilities. So in this
+case, std::filesystem exists. I think that's C++ 17, off the top of my head.
+I'm less familiar because we ban it. And the reason we ban it is that the
+Google style guide bans it. We didn't actually make this call. And they banned
+it over security concerns, some testing concerns. I don't remember. Titus
+Winters, who was deeply involved with the C++ working group, had a lot of
+internal commentary on why he didn't think it was appropriate for Google use,
+and we didn't think Chrome differed enough from Google to make an exception.
+
+16:58 SHARON: So a brief sidebar about style guides-- so there's a Google-wide
+style guide. And then there's a Chromium-specific style guide. So where do
+these things differ?
+
+17:04 PETER: It's interesting. I actually just wrote a formal policy on that we
+have approved, but not yet written down in the tree. And basically, the formal
+answer is we differ whenever the consensus of the cxx mailing list says we
+differ. And cxx, by the way, is a mailing list that anybody in Chrome is
+welcome to join. It's a moderated list for non-members, primarily just to
+reduce spam. We're not actually trying to keep anybody out. There's no
+expertise bar to join it. You don't have to take a test and prove your C++ Foo.
+But that's where things get discussed, like, should we allow x, y, and z? For
+Googlers, you might be familiar with C-style internally. This is very much the
+Chromium version of the C-style mailing list. And mostly, our rule is, yeah, we
+do what Google style does, except in cases where there's a good Chrome reason
+to differ. And there are sometimes. Google doesn't ship to client machines, so
+it doesn't care about the size of updates or how much space it takes on the
+hard disk. So they don't care about binary size. The way that we do, especially
+on mobile platforms. They have different kinds of security concerns. They're
+concerned about the sorts of security holes that you could get on a server-side
+app. We're concerned about the sorts of security holes you could get from an
+attacker on the web. Those overlap. They're not quite the same. So there's a
+lot of subtleties why we might make a different call, but it means that in
+general, Chrome style is Google style with this set of changes to it that we
+document in our style guide.
+
+18:43 SHARON: All right.
+
+18:43 PETER: And yeah, get on the mailing list if you would like to kibitz and
+tell us that we're all idiots.
+
+18:50 SHARON: [LAUGHS] I'm sure, yeah, everyone would love that. Cool. The next
+on our list is NoDestructor.
+
+18:56 PETER: So, yes, this is one of the bazillion "I have a Singleton-ish sort
+of thing" types. So we have lazy instance. We have Singleton. We have
+NoDestructor. The rule is basically ignore all the others and use NoDestructor.
+The other things are old. They're deprecated. Don't use them. NoDestructor
+pretty much just tells the compiler, leak this, at program shutdown. Doesn't
+try to run it. And that has two nice effects. One is it prevents what you might
+call the destruction order fiasco, which is you're in the middle of tearing
+things down, and you don't know whether you can rely on other things that are
+also getting torn down. So who puts their gun down first, kind of thing. If
+nobody has to be destroyed, then you just don't care. And then the other thing
+that's nice is it just does less work on shutdown because really, at shutdown,
+ideally, what we want to do is two things-- write all the important data to
+disk. And then just kill the process. Like, die as fast as possible. Don't do
+anything else. Once all your data is written, you don't care. Tell the OS to
+wipe you off the system. So NoDestructor sort of helps get closer to that
+world.
+
+20:10 SHARON: On a similar note, RAII Scope? Scoper?
+
+20:10 PETER: Right. Yeah, anything for scoping stuff. So RAII, for people who
+haven't encountered that acronym, stands for Resource-- excuse me. Resource
+Allocation Is Initialization. And it really just means using C++'s objects and
+lifetimes in order to get stuff guaranteed to happen when something goes out of
+scope. So converting some manual calls of, do this on Enter; do this on Exit,
+into the lifetime of an object that handles those for you. And so we have
+things like AutoLock, which takes and releases a lock for you. Anything with
+"scoped" in the name-- Scoped Observation adds and removes an observer for you.
+AutoReset is actually trivia, the first thing I ever contributed to //base, at
+least that I recall. So I looked it up the other day. It dates back to 2009. It
+was called Scoped Bool at that point because it only did bools, and now it does
+everything. I was scared of templates in 2009 and did not know how to use them,
+so I only made it work with bools because templates. I know more about
+templates now, and my feeling then was totally justified. It was completely
+correct. But yes. So pretty much, anything you find in //base that has Auto or
+Scoped at the front of the name is some kind of scoping helper.
+
+21:39 SHARON: OK. Yeah, I don't see AutoReset used a ton. Can you briefly
+mention how that works?
+
+21:45 PETER: So AutoReset is just, set a value. And when I go out of scope,
+reset back to the old one. So you can do this, for example, if you want-- if
+your class has a member that is-- hey, I am inside the blah, blah, blah
+function right now, and therefore, you should allow or not allow this
+functionality to happen. A few classes have to have-- it's kind of hacky-- but
+something like that in their design. So you can use an AutoReset around setting
+that member within the particular scope that it's supposed to be set in. You
+can use AutoResets in tests, too. Say, OK, within this scope, make this
+variable be this. And then just automatically, when it goes out of scope, you
+put it back.
+
+22:28 SHARON: Yeah, that's a handy one. Cool. In terms of template stuff maybe,
+span?
+
+22:37 PETER: Yeah, so span is-- people might see span used a lot more lately
+because the security folks have been driving a process called spanification.
+And their goal with this is pretty much to get rid of all pointer arithmetic in
+the entire project. So span is something we call a view type. And I would
+actually to write a talk on view types at some point because I think they're
+less understood than they should be because they're very useful. But basically,
+a view type is some kind of a window into a block of contiguous objects that it
+doesn't own. It just says, here's some memory. Some things live there. You can
+think of this like a pointer because since arrays decay to pointers-- for
+example, an array that you have decayed to a pointer literally is a view onto
+that memory. A span is very similar to that, except that it also carries a size
+along with it, so it remembers how big it was. So in that sense, similar to
+std::array. But unlike stdarray, it's not managing the memory directly. It's
+just saying, no, the memory lives out somewhere else. So maybe it's in a
+vector. Maybe it's in an array. Maybe it's somewhere in the binary or it's on
+the heap. We don't actually care. Here's the pointer to it. And here's the
+size. And that's good because the fact that you have a size means all your APIs
+can bound check. So you can either handle things nicely, or you can crash the
+process on security holes or something. And you can also-- wow, my brain just
+completely died. Oh, yes, You can split spans or do other helpful things with
+them. Grab the first [INAUDIBLE] elements of the span, convert to other span
+types, things like that. So we provide those APIs. And that's what having a
+dedicated type for that gives you.
+
+24:42 SHARON: This is maybe a bit of a silly question, but how do you write
+stuff in that memory, then, if--
+
+24:47 PETER: So, since the span is just a window, it can either be a window of
+writeable or non-writable types. So if you have a span of int, it just means
+here's some ints, and you can read and write them. If you have a span of const
+int, that means that it's read only.
+
+25:00 SHARON: OK, cool. So what about strong alias ID or type? There's a lot of
+types and whatnot. So--
+
+25:13 PETER: Yeah, I don't see these used a ton, but they're occasionally
+useful. Basically, strong alias and ID type, which is a special case of strong
+alias, is a generalization of the idea of an enum class. So it's, I have this
+type that is implemented in terms of this underlying type. But semantically,
+there are different things, and you shouldn't conflate them. So just like you
+might have an enum class that it's really an int and its values are ints but
+don't just pass it to a function that takes ints. You want the compiler to yell
+at you because that's a different meaning. Strong alias is a way of basically
+saying, hey, I have an anything. You can do this for even non-numeric types.
+You can just say, it's really a this thing over here in the implementation, but
+the semantics are different.
+
+26:10 SHARON: Right. I think you see it a lot for ID types of different
+classes, so you don't mix up what you're identifying with those numbers.
+
+26:17 PETER: Right. Yeah, I mean, any kind of identifier runs this risk of type
+confusion. And if you can just get away with using an enum class directly,
+that's probably fine, too. But sometimes you can't.
+
+26:30 SHARON: OK. All right. Next on our list is synchronization types, lock or
+available event, some examples of that?
+
+26:37 PETER: So there's a whole //base synchronization folder. And there's lots
+of things in it. Lock is probably the most commonly used thing. This is a mutex
+if you need to do thread-safe stuff in Chrome. And actually a word about thread
+safety in Chrome, because people have asked this before-- if the code in Chrome
+doesn't state otherwise, it is not thread-safe. It's assumed to all be running
+on a single thread. It's probably assumed to be running on the primary thread.
+The thing we call in the browser process the UI thread. Obviously, in some
+directories that doesn't hold, and the whole directory will say something. But
+normally, thread-safe code-- code used across threads is the exception, not the
+norm. But where it is used-- lock is our mutex type. It's very much like
+std::mutex. In the case of //base synchronization APIs, it's really just a case
+of, we had all our own stuff and used it all well in advance of C++ 11 adding
+it all to the STL. And then we could migrate. It's easy to migrate. And I said
+earlier we like to use stuff from the STL when we can. So I should put the
+caveats on that. It's easy to do that when either of the following is true. One
+is the STL provides the exact same semantics. And the cost is there's no reason
+not to do it. And two is there's a huge win. We get way better perf or better
+integration with something in the STL, or better safety or something like that.
+And in the case of the STL synchronization stuff, migration is scary because
+any difference or bug in the implementation of the STL that we use for those is
+going to be very bad and hard to track because it's all really low-level,
+cryptic cross-thread stuff. And then we don't actually know if there is any
+gain. There's an open bug on performance testing some of these things against
+each other. But because migration is so scary, no one has bothered. Free
+opportunity. If you want, go perf test it. Find a big difference. And then
+migrate. And that's probably cool. Of course, if you perf test it and don't
+find any difference, then you just wasted your time. Ha-ha.
+
+29:06 SHARON: [LAUGHS] RE the thread type-- so the most common ones you see, at
+least in the browser process, are the I/O and UI threads. So can you give us a
+quick rundown of what these two are and how they're different?
+
+29:17 PETER: Yeah, so in the browser process, there's a whole bunch of
+different threads, but the two you mentioned are the two most common ones. The
+UI thread is the main thread of the process, and we call it the UI thread, in
+part because all of the actual UI interaction-- event handling, painting, et
+cetera-- is done on that thread. We don't do most of that off-process. We do do
+compositing off thread and things like that. But if you write code in views and
+it goes and paints pixels, then that code will be running on the UI thread. The
+I/O thread is more confusingly named because at first glance, a lot of
+engineers assume that means that's the thread where we do reads and writes to
+disk. And it actually doesn't mean that. It's interaction between the different
+threads of the browser. So the I/O thread is more like the coordinator thread,
+where it's responsible for communicating between browser and renderer
+processes, or between the network stack and different things. So the I/O thread
+actually never does-- it's not supposed to touch disk at all because disk
+blocks, which is why you do it off thread to begin with. And the I/O thread,
+since it's coordinating all of the other things, needs to be very responsive.
+So in fact, there's other places-- there used to be something called the file
+thread. I can't remember if it still exists. We now have the thread pool that
+you can use to do some of these longer running blocking tasks. And then they'll
+probably communicate their results back directly, but if not, they'll use the
+I/O thread to coordinate that.
+
+30:59 SHARON: OK. All right. Back to our //base walkthrough. So up next is
+Time.
+
+31:04 PETER: Yeah, so another bit of trivia here. Before I was a //base owner,
+which-- I've only been a //base owner since March of this year, 2024-- but I
+have been a //base time owner for many years. So there are more than 11 //base
+OWNERS if you start counting the OWNERS of various subdirectories. And time has
+lots of things in it. But the three core classes, which are all in time.h, are
+Time, TimeTicks and TimeDelta. TimeDelta is easy because TimeDelta is just the
+difference between two Times or two TimeTicks. It's constexpr, and it's
+type-safe. And this means that if you want to store a value 100 milliseconds,
+even at compile time, even as a constant at the top of your .cc file, do not
+do, int blah, blah underscore MS equals 100. That's not type-safe. So it's very
+easy to accidentally add that to some value with different units. So yes,
+TimeDelta is type-safe. So if you say, auto, blah, blah equals base
+milliseconds 100, then that's 100 milliseconds. And it not only says what it is
+in the code, but you can't add it to the wrong units. The compiler will check
+you. And it doesn't-- It's not expensive. It's compiled into the binary. So
+that's great. Time versus TimeTicks is more subtle. These represent two
+slightly different versions of What Time is It? So Time is like a wall clock,
+human readable time. So a time like 3:57 PM, this time zone, on this date--
+that's a time. And human readable makes it really good for messages to humans
+or saying, this happened at this point, but really bad for doing calculations
+with because human times are messy. They skip forward in daylight savings
+things. They also skip backwards in daylight savings things. There's leap
+seconds. There's all sorts of complexities. So trying to find out the
+difference between two times is not as easy as it might seem. So then for that,
+we have TimeTicks, which is based on a monotonically increasing counter that
+runs while the process is running. And that's much more useful for saying, OK,
+15 seconds from now, I want this to happen, or something like that. There are
+still even gotchas with that because what does that clock do if the user puts
+their machine to sleep? Does it keep running or not? And so there's a lot of
+commentary in the code about what you do and when, et cetera. But
+fundamentally, that's the difference between those.
+
+33:44 SHARON: So in the stuff I've looked at, I haven't seen too much use of
+Time. Where is heavy usage of all this Time stuff?
+
+33:57 PETER: There is stuff in a number of different places. So for example,
+the media code needs heavy usage of Time because it needs to know when to
+schedule things. The network code might use Time for computing rates. So if
+it's like OK, I'm going to bandwidth-limit this, or I need to know how fast the
+user is downloading. A lot of UI code needs to use time to display various
+things to users. For example, the scheduled and update-- critical update for
+Chrome. You need to restart your machine within 30 minutes type of thing--
+needs to use Times. And then we use Times a lot when time stamping things that
+come in from sync or that we save to disk. Cases like that, we often need to
+know, is this sufficiently out of date? Do we need to go get a new one?
+
+34:42 SHARON: OK, sounds good. All right. Next on our exploration is value in
+//base.
+
+34:55 PETER: Yeah, //base value-- I think subject to the longest running
+code-health migration thing. We had a code-health thing going for //base value
+for. I don't even know how many years to migrate APIs. And actually,
+ironically, I have almost never used //base value. So I always thought like,
+why are we spending so much time doing this? But //base value is basically a
+C++ class that abstracts, what kinds of values can you store in JSON? And JSON
+matters because JSON is how we store all of our preferences. It's also a good
+abstraction for values that come to and from JavaScript. But all that is
+handled differently. Like, V8 and Blink worry more about that. And usually, by
+the time you get to stuff in //base, they have dealt with those sorts of things
+already. So mostly, where you'll see value is when you're going to and from the
+pref store. Preferences are the backing abstraction, also, for sync. So
+anything that's synced-- you'll probably go through //base value. That gives it
+some things that, to a C++-only programmer, would be odd. For example, that you
+can store a double in it, but you can't store an `int64_t`. And that makes
+sense if you think in terms of JSON doesn't have the concept of a 64-bit int.
+So that's why //base value models that. But this also means that value is not a
+good type to use for a member of your class or a general-purpose thing to pass
+around in APIs. Most of the time, you have one specific type. Use that type. If
+you have multiple things, use a variant. You only really want to use value at
+the boundary level of-- you're serializing to or from some kind of storage that
+uses values, and then after that, you put it in its own dedicated type.
+
+36:45 SHARON: OK, cool. All right. Next up, we have numerics. Numbers-- we like
+those.
+
+36:52 PETER: I got my shirt in here. So yes, the numerics library has a number
+of useful things. It has some mathematical constants. Actually, C++ now has a
+lot of these. We used to have our own constant for pi and square root of 2 and
+things like that that you would need to use a lot. And now C++ 20 has those,
+and we use those more widely. But we still have others. And we have some basic
+conversion functions like, if you're converting between degrees and radians
+don't write the code yourself. Just use our code. Not only is calling a
+function more readable than doing the math inline, but it prevents you
+accidentally going the wrong way or something like that. More interestingly, we
+also have a bunch of safe math libraries, so we have math operators and types
+that will either clamp out-of-range calculations and values, or they will, in
+fact, check fail and crash your process when bad stuff happens. And these are
+not only useful in the cases you would expect like, oh, I have some data coming
+over the network. I should probably check whether the size they want is sane--
+that sort of thing. But also cases you might not expect, casts to smaller size
+within the code. If you're going to use a static cast, the coder should be able
+to tell locally that that's provably safe, like you literally just checked that
+the size is less than such and such. So of course, it has to fit. Yeah, in that
+case, just use a `static_cast`. But if it's coming into some function and
+you're not guaranteed, don't make people go read it and find out that 13 other
+functions later, the transitive closure of x proves that this can be done. Use
+a `checked_cast`. And then even more surprisingly, conversions between integer
+and floating point types-- neither one can accurately represent the other. So
+you should use the safe math functions. You should not be doing things like
+calling `std::round` and then just casting to an int. That doesn't work.
+
+38:55 SHARON: Right.
+
+38:55 PETER: I have a screed on this that I wrote at one point.
+
+39:00 SHARON: OK. Don't roll your own math. All right. What about other ranges
+we have in //base? We talked about a couple earlier, I think, but are there
+more?
+
+39:06 PETER: Well, we talked about some-- we talked about span as a view type.
+I don't know that we talked about anything with ranges. So //base ranges was a
+backport of the range-based algorithms in std::ranges, in C++ 20. So these are
+basically-- everyone hated the old algorithms because they're so cumbersome.
+You always have to pass, my long vector name dot begin, my long vector name dot
+end. And this was even more annoying if you actually had to go to the trouble
+of stuffing something into a temporary just so that you could do that because
+it was coming from some other function. So the range algorithms provide this
+surprisingly nice piece of sugar by just letting you take a range-like object
+directly. And there's lots of complexities to, what is a range-like object? So
+we had back ported that. They give you a few other nice things. They have
+projections on all the algorithms, which lets you do some cool stuff without
+having to manually unwrap stuff. But basically, that's what //base ranges was.
+
+40:16 SHARON: OK, cool. Up next is something we mentioned a bit earlier, but
+general string stuff. So you mentioned `string_view` and whatnot. So are there
+other string things in //base to know about?
+
+40:27 PETER: There's conversions between various types of encodings. In
+particular, Windows APIs are basically UTF-16. Mac APIs are basically UTF-8.
+JavaScript is pretty much UTF-16. POSIX is pretty much UTF-8. There's lots of
+disagreement, and therefore, we end up doing this. So //base makes it easy to
+do this, although even better than doing an easy conversion is not doing the
+conversion. If you can write your APIs or storage such that you actually don't
+need to convert, that's better. I have fixed up code where once I trace through
+the 10 call chains, I discovered that, actually, we wanted the same type at the
+beginning and the end. We just converted back and forth about four times along
+the way. So fix that. Don't do that. There's utilities to split and tokenize
+strings. Probably not good to write your own HTML parser in this. That's why we
+have Blink. But if you're just doing some super trivial thing, we've got that.
+And then some of the bigger ones-- we have StringPrintF, which is basically C's
+sprintf(), where you want to do a formatted output, but into a string buffer
+instead of onto the screen. So we have something like that in StringPrintF,
+except that it returns a std::string. It's harder to misuse. It checks a lot of
+your format stuff at compile time. I also find it overused. We have cases where
+we StringPrintF with a format string that has no substitutions in it at all,
+which-- kind of strange. We have things that just concatenate strings, which
+could be StrCat. And then in general, lots of stuff is cryptic. And then I just
+mentioned StrCat. StrCat is basically a special-purpose function for doing
+string concatenation really quickly. You should not just blindly use StrCat for
+all concatenation. If you have two strings and you do string plus string,
+that's the shortest, most readable, and it turns out, most performant way of
+doing it. You should just do that. People have this idea of, oh yeah, string
+plus is terribly slow. Don't ever use that. Actually, it's great if you're only
+doing it once. If you're doing it over and over and over, it's terrible, but
+not because the implementation of plus is bad. It's because that basically is N
+squared. You have to potentially resize the string bigger and bigger and
+bigger. So StrCat lets you take a whole list of things to concatenate, and it
+does it all at once, which means it's linear time to do that. And StrCat also
+works very nicely with `string_view`s, so you can mix strings, `string_views`,
+C-style strings, et cetera, which is not possible with things like plus. So
+very, very useful function. Underused in my opinion.
+
+43:14 SHARON: OK, go check it out. So you listened to a bunch of all these
+different platforms that Chrome runs on and a lot of stuff that lives-- because
+of things like //base, you don't have to really worry about what platform
+Chrome is running on when you're working on things. For example, in content, we
+don't really have to worry about this. There's some Android-specific stuff, but
+that's not because of actual-- that's for other reasons. So how much of the
+magic that goes into making Chrome run across these different platforms lives
+in //base, versus somewhere else?
+
+43:54 PETER: Certainly more of it. So things like FilePath have to understand,
+fairly directly, the differences between operating systems. We also have a file
+in //base called `compiler_specific.h`, which is a very low-level-- here's a
+bunch of macros. And they differ by platform or by compiler. So C++ 20 gained a
+new attribute called-- shoot. Is it `no_tail_padding`? No. It's-- oh,
+`no_unique_address`. Yes, C++ 20 gained something called `no_unique_address`.
+The only reason I mention it is because on Windows, Clang does something
+different to match Microsoft. And so `compiler_specific` abstracts that detail
+away and says if you use our macro, then you get the same behavior everywhere.
+Things like that. That said, there are still plenty of cases where code outside
+//base needs to understand this. Since I work in UI, the examples that come to
+mind are in UI, like being a good platform citizen on the different OSes often
+means doing different things in terms of what keys do stuff, or how do you
+handle different events, or where should the buttons on the OS surfaces be, or
+things like that. How do fonts get rendered? Those are all things that wouldn't
+be handled in //base because they're higher-level concerns. They're stuff
+happening up in a UI layer somewhere. And probably, code in other directories
+has to do that kind of thing as well. But certainly, //base will take care of
+anything that you might think of as like a Unix versus Windows or POSIX versus
+Windows or Mac or something like that. Android versus iOS. API-level
+difference. That kind of thing will often be handled more at the //base level.
+
+45:46 SHARON: OK, cool. So re macros-- a bunch of macros live in //base. We
+previously had another Peter on to talk about DCHECK(). Do we have some updates
+there, if you want to give us a rundown?
+
+46:00 PETER: So I think your episode with pbos (Peter) was filmed in 2022.
+
+46:09 SHARON: It was a while ago.
+
+46:09 PETER: And then since then, he's been continuing to do hard work. So
+anything I mention here is pretty much credit to pbos. But we've changed our
+guidance on some of these things. So the guidance used to be, basically-- we
+have DCHECK() and CHECK(), and they both kind of mean, this shouldn't happen.
+Well, this should be true, and crash if it isn't. And the guidance used to
+pretty much be, use DCHECK() for everything. Except, use CHECK() for things
+that are security sensitive. And now the guidance is effectively reversed. It's
+basically, use CHECK() for everything. Only use DCHECK() if this is provably
+performance-disastrous here. And the big reason for that is we're finding
+increasingly that a lot of our crashes and security problems in the field come
+from violating the code's invariants. So we get to somewhere in the code, and
+the invariant that even had a DCHECK() that said, this shouldn't be true here--
+it was violated. So something got goofed up somewhere. And by converting all of
+these things into CHECK()s more, initially, it risks making the product more
+crashy. But assuming that we do it in a slow enough way and we fix things
+quickly as they come up, we eventually get to a state where we're actually
+enforcing our invariants and not just saying, well, we're pretty sure this is
+true in production, but we don't want to take the perf hit to do it.
+
+47:35 SHARON: Yeah, and avoiding those weird states is important because that's
+what attackers look for, of, once we're in this weird state, all bets are off
+kind of thing, and we can just do whatever. So--
+
+47:43 PETER: Yes.
+
+47:43 SHARON: --eliminating those.
+
+47:48 PETER: C++ 26 has something in this space called Contracts that they're
+working on, where you can annotate a function to basically say, these are the
+preconditions and postconditions and things. I don't know how that will turn
+out, and I don't know whether we'll want that at the time, it ships. The other
+thing to mention here is that-- I said that rolling this out can sometimes be
+hard. When we say, oh, our guidance is, use CHECK() unless it's
+perf-disastrous, a lot of the questions that I get are things like, well, what
+if I'm pretty sure this is true, but I mean, I don't know for certain? And I
+don't want to ship this thing and crash everyone in the wild. Shouldn't I use a
+DCHECK() if that's the case? And we have a couple tools for dealing with that.
+And one of them is the feature flag. Pretty much everything should be developed
+under a feature flag, unless it really, truly doesn't make sense to do so. And
+that's a way that you can say, oh, hey, we noticed everybody under this feature
+is crashing. Turn it off. But the other thing is that pbos added something
+called NotFatalUntil, which is a way of saying, hey, I'm putting this in. I'm
+explicitly going to make it fatal in the future. But for right now, I just want
+to collect crash stacks and data on it and not actually crash in the field. And
+that's a good tool that people can use to implement things in a cautious way.
+
+49:11 SHARON: Is that the same as DumpWithoutCrashing()?
+
+49:11 PETER: So NotFatalUntil, I believe, uses DumpWithoutCrashing() to
+implement things. DumpWithoutCrashing() is a way of explicitly just saying. I
+don't want this to be fatal. I just want to collect stuff. NotFatalUntil is a
+way of marking a CHECK(), as having that behavior, and it will automatically
+switch over at a certain milestone. So you say, NotFatalUntil M-136, and then
+when M-136 rolls around, bang, that becomes fatal. And everybody--
+
+49:40 SHARON: Everyone's crashing. Yeah.
+
+49:40 PETER: Hopefully not, because hopefully you caught and fixed all the
+problems with it before then. But yes.
+
+49:46 SHARON: So in terms of other macros we see a lot, maybe more as a-- not
+something you hopefully see as much in production, but in tests and general
+debugging is logging and various logging-adjacent macros. Can you tell us a bit
+about those?
+
+50:00 PETER: Yes. If you're on a team where you know, I have to be able to
+debug only from the log output. There's no other way. And I know how to collect
+it, and I know what I'm going to do with it, and I'm going to clean it up
+eventually when we fix the problem. Then if all those are true, go for it and
+log. But otherwise, no.
+
+50:16 SHARON: If you do want to collect data of, say, certain values, of
+certain variables out in the field that you can't get locally, we have better
+ways to do that.
+
+50:27 PETER: Yeah. There's debugging utilities. So you mentioned
+DumpWithoutCrashing(), and that's a way to send back a lot of data to us as if
+there was a crash. And then when you do that, you can use something called
+debug Alias(), where you can force a particular variable's value to get
+captured by the crash data, because normally, the crash data will include
+things like, well, these variables were on the stack. But in a release build, a
+lot of things are optimized away, so you can't guarantee something like that.
+So aliasing a variable using that particular //base utility is one way to make
+sure that-- we want to capture this, this, this, in this dump, for sure. Do it.
+
+51:05 SHARON: Is that the thing we also call "crash keys?"
+
+51:11 PETER: I think it uses crash keys to implement it. I haven't looked at
+this very recently. So--
+
+51:17 SHARON: OK, I think we'll end our meander through //base at that point.
+But there are many types we didn't cover. So how do people find those? Should
+they try to remember everything we just mentioned?
+
+51:28 PETER: Yes, this is the, Mr. Johnson, may I be excused; my brain is full,
+moment. So there's no way to remember all the different things in //base, as
+far as I can tell. I'm an owner, and I constantly find things that I'm like,
+oh, I didn't know we had this. Oh, that would have been useful. So I try to
+tell people, like, yeah, you can get better with some of this stuff. The
+biggest way to do this is just practice. Practice is much better than just raw,
+focused effort on polishing a single thing. Just write more CLs. Look more at
+the //base APIs. Use more things. Put them in practice. Send a bunch of stuff.
+If it's not perfect, I don't care. If it's a monotonic improvement over what
+we've got, I'll stamp that immediately and say, sure, let's move forward, and
+just do it more. But in the limit, nobody can remember all this stuff. And I
+feel a little bit bad that-- I think the message that a lot of people get in
+code review is like, what the heck are you doing? Why didn't you use a base
+blah blah, that you've never heard of? And you're like, I'm sorry that I am
+not a genius like you. So actually, I feel overwhelmed and incompetent, et
+cetera, a lot of the time, too. And it's because this is a hard problem and
+it's a big space. //base is huge. Chrome is huge.
+
+52:54 SHARON: Monotonically increasingly huge.
+
+52:54 PETER: C++ is huge. The web is huge. Nobody-- no human being is capable
+of being an expert in any of these areas. It's too big. And therefore, like if
+we can have compassion for each other, that's good. I hope, increasingly, we
+give people more encouragement and opportunity to succeed and not just, hey,
+avoid failure harder, because that's just a route to everybody getting burned
+out and miserable.
+
+53:20 SHARON: Yeah, I mean, Chrome is fun because a lot of people have been
+around for a while, so they have a better grasp of things, I guess, because it
+used to be simpler. So it's easier to patch in those incremental changes,
+whereas when you come in now, it's so much stuff. It's like, oh, my God. What?
+What's happening. And it gets harder and harder to start. And there's not that
+many new people at Chrome, relatively, so we kind of don't have that constant
+reminder of, oh, this is hard, and this is what people find hard now. So--
+
+53:55 PETER: Yes, anybody coming into Chrome-- it's enormous and overwhelming.
+And I mean, it overwhelms me, and I've been here since the inception. So it's
+very much true.
+
+54:06 SHARON: I think everyone is overwhelmed, no matter how long they've been
+here. It's just what they are overwhelmed by changes as you go.
+
+54:18 PETER: I have proposed in the past doing more formal training, not just
+classes or talks or something, but direct one on one-- here, you watch me step
+through this kind of problem and do this. And I'll watch you, and let's give
+each other feedback. And very much more apprenticeship model than just like
+lecturer model stuff in Chrome. I think that would be good. I think readability
+reviews would be useful. I used to be a C++ readability reviewer at Google. And
+all of those things-- it's been difficult to get organizational traction to
+actually go do those. So shameless plug-- if people think that would be useful
+for them and you want to do that with me or somebody else, let me know, and I
+will try to make it happen. And if you think that's a terrible idea, don't let
+me know. I don't need more discouragement right now.
+
+55:15 SHARON: Yeah. I mean, everyone in Chrome is incredibly helpful and
+friendly. There's people who you think, oh, they must be so busy. But they're
+always so willing to help and talk and whatever.
+
+55:21 PETER: And I think one of the keys to making that happen is finding the
+right people for questions and then not turning it into no good deed goes
+unpunished. So I've been guilty of this, where someone was helpful to me, and
+then I immediately just rammed 500 followups down their throat. And they,
+internally, were like, I think I will not be helpful in the future. That might
+work better. So I try to tell people, hey, as much as possible, instead of
+sending chats or emails to one specific person--
+
+56:00 SHARON: Post them to the mailing list.
+
+56:00 PETER: --post them on a mailing lists. Put them in chat threads. Come
+hang out on Slack. There's this weird dichotomy of teams in Chrome that use
+Slack, and teams in Chrome that do not use Slack at all, no. I don't really
+care, in terms of what your team wants to do. Neither one is wrong. But in
+terms of, can everybody else see it and make use of it, if you come over to
+Slack, then, yes, that can happen. If you're in your team's chat room, probably
+not. So that's my, you should all be in Slack if you would like to have your
+conversations visible and possibly helped by other people on the team.
+
+56:40 SHARON: In Slack, it's much easier to search and use and the other stuff
+we use. So if you want to be able to find an answer to something you asked a
+long time ago, it's going to be much easier in Slack than--
+
+56:51 PETER: Hey, now, you sound dangerously like someone who does not believe
+that all Google products are the best for all situations.
+
+57:00 SHARON: OK, so something you have mentioned a few times, and we've
+touched on is updating C++ versions. So you are quite well-acquainted with
+that. So what goes into going from, say, C++ 17 to 20?
+
+57:07 PETER: Yeah, I helped with 14 and 17, and then I pretty much drove 20. So
+just so people understand what it means when we say, well, what version of C++,
+pretty much, at any given time, Chrome has some version of C++ that it says it
+formally supports. So we say, right now, that we formally support C++ 20. And
+what that means in practice is really that we pass, like, dash std equals C++
+20 to the compiler and the linker when we build stuff. So this really means
+that's the version that's in our build files that we tell the compiler to use.
+And then it will complain about stuff outside that. That means you can also use
+earlier stuff. Whether you can use later stuff is a matter of whether the
+compiler will let you. C++ 20 introduced designated initializers. But in fact,
+you could use them before then, in part because compilers would allow that. And
+Google style guide, said, yes, and we're OK with that. So it's a function of
+that stuff. So we have that. And we have a guide called c++-features.md that
+says, here's all the stuff in the different language versions that you are, are
+not allowed to use. And when we decide that we want to go to a new version,
+like say, if we want to go to C++ 23 right now, the first thing that somebody
+will look at-- there's no formal owner of this. There's no timetable that says,
+thou shalt pull this new version into Chrome at this time, and this is the team
+that will do it. It's very much like, no one owns it, and it will happen if it
+happens. And the reason that I did 20 is because I wanted 20. It actually was
+even weirder than that. I wanted us to be on 20 in hopes that I could use MSVC
+to test something specific at low level in //base. And then that snowballed
+because I was just like, well, I'll just roll the C++ version. How hard could
+it be? A year later, we rolled. So--
+
+59:15 SHARON: That's not bad. That's pretty good.
+
+59:15 PETER: So what I might try doing first is simply see, does the toolchain
+support it? So that means go change the version we passed to the compiler and
+see if it compiles. Some stuff gets deprecated in new versions. And you maybe
+have to go fix that. And then the next thing is, well, do we want to allow it?
+Like, it does no good to say you can use C++ 23 if all of the features aren't
+implemented yet, and they're broken, and you don't know that they're broken
+because the compiler says that they work, but they don't. This was the case for
+a number of things in the past, where people would be like, yeah, you can
+theoretically use this. Don't use it. It doesn't work. So once those things are
+true, and we know the toolchain pretty much supports most of what we want to
+use, not all of-- the rule is, not all of, because toolchains have lagging
+things. Actually, Clang slash libc++ just finished C++ 17 within the last
+couple of months. They finished the last bits in order to mark it as fully
+complete on their thing. Obviously, C++ has been out for a long time, and we've
+been using it for a long time, and so has everybody else. So we don't wait for
+100% to be complete. We wait for enough. And then we pull in the new thing, fix
+everything, and write a bunch of updates to the features doc that says, you can
+and can't use these parts. And normally, we're somewhat conservative. We don't
+ban everything. We ban anything that doesn't work. But we might also ban some
+other stuff just because, yeah, we probably want this, but let's get ourselves
+onto the new version before we have to think about, what's the migration plan
+to this feature? So let's just, for now, block it. And we'll think of that
+after the fact. And that's what happened with C++ 20. We blocked a lot of stuff
+initially. And then in the months following that update, we've allowed more
+things. The std ranges stuff earlier was an example. I had said, that's what
+//base ranges was. And then if anybody out there was asking, well wait a
+minute, what do you mean, was? It's still there. I see it in the tree. What do
+you mean? It's because std ranges is actually, now, the approved way of going,
+and the old thing is deprecated. We will get rid of it at some point. There is
+a migration plan. But that's the path that all of the new things follow. So
+there's a whole list of stuff in C++ 20 that we want to allow. And I push
+aggressively to move forward if we can. And it happens when it happens.
+
+61:59 SHARON: OK. So if someone wants to be the person who updates to C++ 23 or
+generally get more involved in //base kind of things, become another //base
+owner, what kind of things can you start out by doing now to go into that
+direction?
+
+62:14 PETER: Yeah, it's funny because in the Notes doc that I wrote myself
+where this talk-- all of my notes to myself are at least very negative like,
+don't do this; don't do this. And I'm thinking, wow, how discouraging.
+Actually, the best answer is, go do it. A lot of these things-- like I said,
+with C++ 20, it happened because I was just like, let's do this. How hard can
+it be? Many bad events have started with those words. But with a lot of things.
+If you want to do stuff in //base or if you want to make major C++ changes in
+Chrome, just do it. We have this cultural principle now, think like an owner.
+Part of what that means is it actually is OK for you to go make major changes.
+You do not need to go get permission. Now, you may want to talk to some
+knowledgeable people so that you don't do something silly like, oh, well, I
+just spent four months making this possible, only to find that you had blocked
+it because-- other reason that I was unaware of or something. But that's very
+different than, hey, I don't know whether I'm allowed to do anything. Can I
+touch //base? No this is the special, sacred area. It's just like every other
+directory. Send CLs to an owner, and we'll do stuff. In fact, for a lot of
+people, that's enough. The C++ 20 upgrade I did I was not a //base owner for,
+and I still updated the whole C++ version that Chrome uses. And I reviewed all
+//base ranges. I did a whole bunch of LSCs to change our string types. All of
+that stuff happened without being a //base owner. You probably don't need to be
+in an OWNERS file to have special credentials or anything to do a lot of this
+stuff. If you're interested and you like doing it, then go do it. If you
+actually want to become a //base owner or something like that, then, yeah, like
+probably, eventually, you'll need to have some decent C++ skill. I don't like
+to use words like, expert, partly because no one knows what they mean, but also
+partly because I think the people who are the biggest experts in some area
+understand how much they don't know. And therefore, there's this sort of
+Dunning-Kruger like-- the people who are not experts are like, I'm an expert.
+And then the experts are like, I'm not an expert. So that happens. But know
+some stuff. Don't be totally terrified by templates. Maybe feel pretty
+comfortable with most of the stuff I covered in [C++ 201]. Some breadth of
+experience. If you can have a rough idea that, yeah, this would probably be
+useful several places in Chrome; I think we probably do this multiple places,
+then that's a useful instinct to be able to have. And you're comfortable
+tackling API design-level questions. So not just the implementation, but also,
+what direction do we want to move the code //base in? And is this a safe
+concept, that people should think at this level? Those kinds of considerations
+are things that you want to be comfortable thinking about. And then finally,
+fundamentally, you have to have somebody who's established trust over time.
+//base OWNERS get an automatic OWNERS override capability. This is not intended
+to be like a carrot of, go become a //base owner so you can have this. It's
+mostly just like a, hey, if you're touching this, you probably need to make a
+lot of changes that touch a lot of directories across the code to fix API usage
+or something. So just for convenience sake, we're going to give these people
+OWNERS override permission. There's still a request that you not use it on your
+own CLs. I'm not supposed to 00 plus 1 my own change after somebody reviews it,
+that sort of thing. And because of that level of power, we want to be sure that
+people who we're giving this to are people who can be trusted to use that in a
+responsible way. So somebody who's only been known to the project for three
+weeks-- even if they're the world-- Herb Sutter comes in and is like, hey, I am
+the head of the C++ committee. I know lots. Can I be a //base owner? It's like,
+probably no, not right away, not because we don't think you're good. It's just
+because you don't know Chrome yet, and we don't know you. And so a lot of
+building relationship over time.
+
+66:48 SHARON: Sounds good. Yeah, so if you want to get more into it, cxx is
+both a mailing list open to the public, as well as a Slack channel. Lots of
+other Slack channels, other related ones, too.
+
+67:06 PETER: Yeah, there is a //base Slack channel.
+
+67:06 SHARON: A //base Slack channel.
+
+67:06 PETER: And it's funny, because many of us are in many Slack channels and
+will have conversations simultaneously across several of them, which is a
+little odd. But yes, most of our day-to-day work gets discussed on Slack. So
+things like, how do I use this space API? Or do you think these things should
+change this way? That sort of question is very Slack-level. And then more
+formal like, shall this feature be allowed or banned? That's a mailing
+list-type question. So people are welcome to come participate in both of those
+as they desire to do so.
+
+67:43 SHARON: OK. Well, thank you so much. This was very fun to see-- get a
+glimpse into everything that is in phase, and hopefully, people will find it
+interesting and want to get more involved.
+
+67:56 PETER: All right.
+
+67:56 SHARON: All right. Thank you very much. You could do both. This is a
+fairly low-budget production, as you can tell by the pens holding up--
+
+68:02 PETER: And the fact that you-- where is second-- oh, they're--
+
+[C++ 201]: https://www.youtube.com/playlist?list=PL9ioqAuyl6UKP9uKZivfIAXwJzfMIQlyo
diff --git a/extensions/common/manifest_handlers/csp_info_unittest.cc b/extensions/common/manifest_handlers/csp_info_unittest.cc
index 96ca27f..a669d736 100644
--- a/extensions/common/manifest_handlers/csp_info_unittest.cc
+++ b/extensions/common/manifest_handlers/csp_info_unittest.cc
@@ -96,7 +96,7 @@
   EXPECT_EQ(kDefaultSandboxedPageCSP, CSPInfo::GetResourceContentSecurityPolicy(
                                           extension7.get(), "/test"));
 
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("sandboxed_pages_invalid_1.json",
                errors::kInvalidSandboxedPagesList),
       Testcase("sandboxed_pages_invalid_2.json", errors::kInvalidSandboxedPage),
@@ -106,7 +106,7 @@
                GetInvalidManifestKeyError(keys::kSandboxedPagesCSP)),
       Testcase("sandboxed_pages_invalid_5.json",
                GetInvalidManifestKeyError(keys::kSandboxedPagesCSP))};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 TEST_F(CSPInfoUnitTest, CSPStringKey) {
@@ -128,7 +128,7 @@
 }
 
 TEST_F(CSPInfoUnitTest, CSPDictionary_ExtensionPages) {
-  struct {
+  static constexpr struct {
     const char* file_name;
     const char* csp;
   } cases[] = {{"csp_dictionary_valid_1.json", "default-src 'none'"},
@@ -144,7 +144,7 @@
     EXPECT_EQ(test_case.csp, CSPInfo::GetExtensionPagesCSP(extension.get()));
   }
 
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("csp_invalid_2.json",
                GetInvalidManifestKeyError(
                    keys::kContentSecurityPolicy_ExtensionPagesPath)),
@@ -162,7 +162,7 @@
                    keys::kContentSecurityPolicy_ExtensionPagesPath,
                    "'unsafe-eval'", "worker-src")),
   };
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 // Tests the requirements for object-src specifications.
@@ -345,7 +345,7 @@
                   extension.get(), test_case.resource_path));
   }
 
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       {"sandbox_both_keys.json", errors::kSandboxPagesCSPKeyNotAllowed},
       {"sandbox_csp_with_dictionary.json",
        errors::kSandboxPagesCSPKeyNotAllowed},
@@ -355,7 +355,7 @@
       {"unsandboxed_csp.json",
        GetInvalidManifestKeyError(
            keys::kContentSecurityPolicy_SandboxedPagesPath)}};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 // Ensures that using a dictionary for the keys::kContentSecurityPolicy manifest
diff --git a/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc b/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc
index 4d55610..053b5986 100644
--- a/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc
+++ b/extensions/common/manifest_handlers/file_handler_manifest_unittest.cc
@@ -24,7 +24,7 @@
 using FileHandlersManifestTest = ManifestTest;
 
 TEST_F(FileHandlersManifestTest, InvalidFileHandlers) {
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("file_handlers_invalid_handlers.json",
                errors::kInvalidFileHandlers),
       Testcase("file_handlers_invalid_type.json",
@@ -44,7 +44,7 @@
       Testcase("file_handlers_invalid_verb.json",
                errors::kInvalidFileHandlerVerb),
   };
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 TEST_F(FileHandlersManifestTest, ValidFileHandlers) {
diff --git a/extensions/common/manifest_handlers/homepage_url_unittest.cc b/extensions/common/manifest_handlers/homepage_url_unittest.cc
index ddfc39cf..301af9c 100644
--- a/extensions/common/manifest_handlers/homepage_url_unittest.cc
+++ b/extensions/common/manifest_handlers/homepage_url_unittest.cc
@@ -20,15 +20,11 @@
   scoped_refptr<Extension> extension(
       LoadAndExpectSuccess("homepage_url_valid.json"));
 
-  Testcase testcases[] = {
-    Testcase("homepage_url_empty.json",
-             errors::kInvalidHomepageURL),
-    Testcase("homepage_url_invalid.json",
-             errors::kInvalidHomepageURL),
-    Testcase("homepage_url_bad_schema.json",
-             errors::kInvalidHomepageURL)
-  };
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  const Testcase testcases[] = {
+      Testcase("homepage_url_empty.json", errors::kInvalidHomepageURL),
+      Testcase("homepage_url_invalid.json", errors::kInvalidHomepageURL),
+      Testcase("homepage_url_bad_schema.json", errors::kInvalidHomepageURL)};
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 TEST_F(HomepageURLManifestTest, GetHomepageURL) {
diff --git a/extensions/common/manifest_handlers/manifest_url_about_unittest.cc b/extensions/common/manifest_handlers/manifest_url_about_unittest.cc
index b65bb5f..fde302b 100644
--- a/extensions/common/manifest_handlers/manifest_url_about_unittest.cc
+++ b/extensions/common/manifest_handlers/manifest_url_about_unittest.cc
@@ -19,7 +19,7 @@
   EXPECT_EQ(GURL("chrome-extension://" + extension->id() + "/about.html"),
             ManifestURL::GetAboutPage(extension.get()));
 
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       // Forbid data types other than strings.
       Testcase("shared_module_about_invalid_type.json",
                errors::kInvalidAboutPage),
@@ -27,7 +27,7 @@
       // Forbid absolute URLs.
       Testcase("shared_module_about_absolute.json",
                errors::kInvalidAboutPageExpectRelativePath)};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 }  // namespace extensions
diff --git a/extensions/common/manifest_handlers/requirements_unittest.cc b/extensions/common/manifest_handlers/requirements_unittest.cc
index a34cf2f..a20678a 100644
--- a/extensions/common/manifest_handlers/requirements_unittest.cc
+++ b/extensions/common/manifest_handlers/requirements_unittest.cc
@@ -15,7 +15,7 @@
 using RequirementsManifestTest = ManifestTest;
 
 TEST_F(RequirementsManifestTest, RequirementsInvalid) {
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("requirements_invalid_requirements.json",
                "Error at key 'requirements'. Type is invalid. Expected "
                "dictionary, found boolean."),
@@ -33,7 +33,7 @@
           "Error at key 'requirements.3D.features'. Manifest key is required."),
   };
 
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 TEST_F(RequirementsManifestTest, RequirementsValid) {
diff --git a/extensions/common/manifest_handlers/shared_module_manifest_unittest.cc b/extensions/common/manifest_handlers/shared_module_manifest_unittest.cc
index 53c4f27..1c1a8aa5 100644
--- a/extensions/common/manifest_handlers/shared_module_manifest_unittest.cc
+++ b/extensions/common/manifest_handlers/shared_module_manifest_unittest.cc
@@ -82,7 +82,7 @@
 }
 
 TEST_F(SharedModuleManifestTest, ExportParseErrors) {
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("shared_module_export_and_import.json",
                "Simultaneous 'import' and 'export' are not allowed."),
       Testcase("shared_module_export_not_dict.json",
@@ -97,7 +97,7 @@
                "Error at key 'export.allowlist'. Type is invalid. Expected "
                "list, found string."),
   };
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 TEST_F(SharedModuleManifestTest, SharedModuleStaticFunctions) {
@@ -166,7 +166,7 @@
 }
 
 TEST_F(SharedModuleManifestTest, ImportParseErrors) {
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("shared_module_import_not_list.json",
                "Error at key 'import'. Type is invalid. Expected list, found "
                "dictionary."),
@@ -175,7 +175,7 @@
       Testcase("shared_module_import_invalid_version.json",
                "Invalid value for 'import[0].minimum_version'."),
   };
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases, EXPECT_TYPE_ERROR);
 }
 
 }  // namespace extensions
diff --git a/extensions/common/manifest_handlers/update_url_unittest.cc b/extensions/common/manifest_handlers/update_url_unittest.cc
index 9aea5b97..b223231 100644
--- a/extensions/common/manifest_handlers/update_url_unittest.cc
+++ b/extensions/common/manifest_handlers/update_url_unittest.cc
@@ -16,7 +16,7 @@
 
 TEST_F(UpdateURLManifestTest, UpdateUrls) {
   // Test several valid update urls
-  Testcase testcases[] = {
+  const Testcase testcases[] = {
       Testcase("update_url_valid_1.json", ManifestLocation::kInternal,
                Extension::NO_FLAGS),
       Testcase("update_url_valid_2.json", ManifestLocation::kInternal,
@@ -25,15 +25,15 @@
                Extension::NO_FLAGS),
       Testcase("update_url_valid_4.json", ManifestLocation::kInternal,
                Extension::NO_FLAGS)};
-  RunTestcases(testcases, std::size(testcases), EXPECT_TYPE_SUCCESS);
+  RunTestcases(testcases, EXPECT_TYPE_SUCCESS);
 
   // Test some invalid update urls
-  Testcase testcases2[] = {
+  const Testcase testcases2[] = {
       Testcase("update_url_invalid_1.json", errors::kInvalidUpdateURL,
                ManifestLocation::kInternal, Extension::NO_FLAGS),
       Testcase("update_url_invalid_2.json", errors::kInvalidUpdateURL,
                ManifestLocation::kInternal, Extension::NO_FLAGS),
       Testcase("update_url_invalid_3.json", errors::kInvalidUpdateURL,
                ManifestLocation::kInternal, Extension::NO_FLAGS)};
-  RunTestcases(testcases2, std::size(testcases2), EXPECT_TYPE_ERROR);
+  RunTestcases(testcases2, EXPECT_TYPE_ERROR);
 }
diff --git a/extensions/common/manifest_test.cc b/extensions/common/manifest_test.cc
index a9e9959..459accb 100644
--- a/extensions/common/manifest_test.cc
+++ b/extensions/common/manifest_test.cc
@@ -2,11 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
-#pragma allow_unsafe_buffers
-#endif
-
 #include "extensions/common/manifest_test.h"
 
 #include <optional>
@@ -327,11 +322,10 @@
       location_(location),
       flags_(flags) {}
 
-void ManifestTest::RunTestcases(const Testcase* testcases,
-                                size_t num_testcases,
+void ManifestTest::RunTestcases(base::span<const Testcase> testcases,
                                 ExpectType type) {
-  for (size_t i = 0; i < num_testcases; ++i) {
-    RunTestcase(testcases[i], type);
+  for (const auto& testcase : testcases) {
+    RunTestcase(testcase, type);
   }
 }
 
diff --git a/extensions/common/manifest_test.h b/extensions/common/manifest_test.h
index 4c8c1fa..2f5f551 100644
--- a/extensions/common/manifest_test.h
+++ b/extensions/common/manifest_test.h
@@ -12,6 +12,7 @@
 #include <string>
 #include <string_view>
 
+#include "base/containers/span.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/values.h"
 #include "extensions/common/extension.h"
@@ -181,9 +182,7 @@
              int flags);
   };
 
-  void RunTestcases(const Testcase* testcases,
-                    size_t num_testcases,
-                    ExpectType type);
+  void RunTestcases(base::span<const Testcase> testcases, ExpectType type);
 
   void RunTestcase(const Testcase& testcase, ExpectType type);
 
diff --git a/gpu/ipc/client/gpu_channel_host.cc b/gpu/ipc/client/gpu_channel_host.cc
index 31bc968..ebf94b6 100644
--- a/gpu/ipc/client/gpu_channel_host.cc
+++ b/gpu/ipc/client/gpu_channel_host.cc
@@ -264,6 +264,10 @@
       base::BindOnce(&Listener::Close, base::Unretained(listener_.get())));
 }
 
+void GpuChannelHost::ResetChannelRemoteForTesting() {
+  gpu_channel_.reset();
+}
+
 int32_t GpuChannelHost::ReserveImageId() {
   return next_image_id_.GetNext();
 }
diff --git a/gpu/ipc/client/gpu_channel_host.h b/gpu/ipc/client/gpu_channel_host.h
index 1afb438c..fe7c6da 100644
--- a/gpu/ipc/client/gpu_channel_host.h
+++ b/gpu/ipc/client/gpu_channel_host.h
@@ -187,6 +187,9 @@
   friend class base::RefCountedThreadSafe<GpuChannelHost>;
   virtual ~GpuChannelHost();
 
+  // Clears its SharedAssociatedRemote.
+  void ResetChannelRemoteForTesting();
+
  private:
   // Establishes shared memory communication with the GPU process. This memory
   // is used to keep track of flushed items and avoid unnecessary IPCs.
diff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc
index 1253bb1..cb9f320fb 100644
--- a/headless/lib/browser/headless_web_contents_impl.cc
+++ b/headless/lib/browser/headless_web_contents_impl.cc
@@ -271,6 +271,11 @@
                : blink::mojom::DisplayMode::kBrowser;
   }
 
+  void SetContentsBounds(content::WebContents* source,
+                         const gfx::Rect& bounds) override {
+    headless_web_contents_->SetBounds(bounds);
+  }
+
  private:
   HeadlessBrowserImpl* browser() { return headless_web_contents_->browser(); }
 
diff --git a/headless/test/data/protocol/sanity/window-resize-to-expected.txt b/headless/test/data/protocol/sanity/window-resize-to-expected.txt
new file mode 100644
index 0000000..412f7fd
--- /dev/null
+++ b/headless/test/data/protocol/sanity/window-resize-to-expected.txt
@@ -0,0 +1,9 @@
+Tests window outer size is properly adjusted by `window.resizeTo()`.
+Outer window size (initial): {
+    outerHeight : 600
+    outerWidth : 800
+}
+Outer window size (final): {
+    outerHeight : 500
+    outerWidth : 700
+}
\ No newline at end of file
diff --git a/headless/test/data/protocol/sanity/window-resize-to.js b/headless/test/data/protocol/sanity/window-resize-to.js
new file mode 100644
index 0000000..b934311
--- /dev/null
+++ b/headless/test/data/protocol/sanity/window-resize-to.js
@@ -0,0 +1,21 @@
+// 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} = await testRunner.startBlank('Tests window outer ' +
+      'size is properly adjusted by `window.resizeTo()`.');
+  const initialSize = await session.evaluate('({outerWidth, outerHeight})');
+  testRunner.log(initialSize, 'Outer window size (initial): ');
+
+  const resizePromise = session.evaluateAsync(`
+    new Promise(resolve =>
+        {window.addEventListener('resize', resolve, {once: true})})
+  `);
+  await session.evaluate('window.resizeTo(700, 500)');
+  await resizePromise;
+
+  const finalSize = await session.evaluate('({outerWidth, outerHeight})');
+  testRunner.log(finalSize, 'Outer window size (final): ');
+  testRunner.completeTest();
+})
diff --git a/headless/test/headless_protocol_browsertest.cc b/headless/test/headless_protocol_browsertest.cc
index b48ed81..157f32a 100644
--- a/headless/test/headless_protocol_browsertest.cc
+++ b/headless/test/headless_protocol_browsertest.cc
@@ -356,9 +356,12 @@
 HEADLESS_PROTOCOL_TEST(ShowFilePickerInterception,
                        "sanity/show-file-picker-interception.js")
 
+// The `change-window-*.js` tests cover DevTools methods, while `window-*.js`
+// cover `window.*` JS APIs.
 HEADLESS_PROTOCOL_TEST(ChangeWindowSize, "sanity/change-window-size.js")
 HEADLESS_PROTOCOL_TEST(ChangeWindowState, "sanity/change-window-state.js")
 HEADLESS_PROTOCOL_TEST(WindowOuterSize, "sanity/window-outer-size.js")
+HEADLESS_PROTOCOL_TEST(WindowResizeTo, "sanity/window-resize-to.js")
 
 // https://crbug.com/378531862
 #if BUILDFLAG(IS_MAC)
diff --git a/infra/config/generated/builder-owners/bling-engprod@google.com.txt b/infra/config/generated/builder-owners/bling-engprod@google.com.txt
index d326e75..ad159a7 100644
--- a/infra/config/generated/builder-owners/bling-engprod@google.com.txt
+++ b/infra/config/generated/builder-owners/bling-engprod@google.com.txt
@@ -9,6 +9,7 @@
 ci/mac-arm64-on-arm64-rel
 ci/mac-arm64-rel
 ci/mac-official
+ci/mac-vm
 ci/mac11-arm64-rel-tests
 ci/mac12-arm64-rel-tests
 ci/mac13-arm64-rel-tests
@@ -16,6 +17,7 @@
 ci/mac14-tests
 ci/mac14-tests-dbg
 try/ios-vm
+try/mac-vm
 try/mac14-arm64-rel
 try/mac14-arm64-rel-compilator
 try/mac14-tests
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/mac-vm/gn-args.json b/infra/config/generated/builders/ci/mac-vm/gn-args.json
new file mode 100644
index 0000000..809d265
--- /dev/null
+++ b/infra/config/generated/builders/ci/mac-vm/gn-args.json
@@ -0,0 +1,11 @@
+{
+  "gn_args": {
+    "is_component_build": true,
+    "is_debug": true,
+    "symbol_level": 1,
+    "target_cpu": "arm64",
+    "target_os": "mac",
+    "use_remoteexec": true,
+    "use_siso": true
+  }
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/mac-vm/properties.json b/infra/config/generated/builders/ci/mac-vm/properties.json
new file mode 100644
index 0000000..d6916b4
--- /dev/null
+++ b/infra/config/generated/builders/ci/mac-vm/properties.json
@@ -0,0 +1,75 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "additional_exclusions": [
+        "infra/config/generated/builders/ci/mac-vm/gn-args.json"
+      ],
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "mac-vm",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.fyi",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Debug",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "mac"
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "mac-vm",
+          "project": "chromium"
+        }
+      ],
+      "mirroring_builder_group_and_names": [
+        {
+          "builder": "mac-vm",
+          "group": "tryserver.chromium.mac"
+        }
+      ],
+      "targets_spec_directory": "src/infra/config/generated/builders/ci/mac-vm/targets"
+    }
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-trusted",
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
+  },
+  "$build/siso": {
+    "configs": [
+      "builder"
+    ],
+    "enable_cloud_profiler": true,
+    "enable_cloud_trace": true,
+    "experiments": [],
+    "project": "rbe-chromium-trusted",
+    "remote_jobs": 250
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "chromium.fyi",
+  "recipe": "chromium",
+  "xcode_build_version": "16c5032a"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/mac-vm/shadow-properties.json b/infra/config/generated/builders/ci/mac-vm/shadow-properties.json
new file mode 100644
index 0000000..673c7c0
--- /dev/null
+++ b/infra/config/generated/builders/ci/mac-vm/shadow-properties.json
@@ -0,0 +1,17 @@
+{
+  "$build/reclient": {
+    "instance": "rbe-chromium-untrusted",
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
+  },
+  "$build/siso": {
+    "configs": [
+      "builder"
+    ],
+    "enable_cloud_profiler": true,
+    "enable_cloud_trace": true,
+    "experiments": [],
+    "project": "rbe-chromium-untrusted",
+    "remote_jobs": 250
+  }
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/mac-vm/targets/chromium.fyi.json b/infra/config/generated/builders/ci/mac-vm/targets/chromium.fyi.json
new file mode 100644
index 0000000..497f301
--- /dev/null
+++ b/infra/config/generated/builders/ci/mac-vm/targets/chromium.fyi.json
@@ -0,0 +1,42 @@
+{
+  "mac-vm": {
+    "additional_compile_targets": [
+      "all"
+    ],
+    "gtest_tests": [
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "base_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "Apple_(Virtual)",
+            "os": "Mac-14",
+            "pool": "chromium.tests.macvm"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "interactive_ui_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "Apple_(Virtual)",
+            "os": "Mac-14",
+            "pool": "chromium.tests.macvm"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test": "interactive_ui_tests",
+        "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/gn_args_locations.json b/infra/config/generated/builders/gn_args_locations.json
index ba66699..b11156f 100644
--- a/infra/config/generated/builders/gn_args_locations.json
+++ b/infra/config/generated/builders/gn_args_locations.json
@@ -332,6 +332,7 @@
     "linux-wpt-chromium-rel": "ci/linux-wpt-chromium-rel/gn-args.json",
     "mac-osxbeta-rel": "ci/mac-osxbeta-rel/gn-args.json",
     "mac-perfetto-rel": "ci/mac-perfetto-rel/gn-args.json",
+    "mac-vm": "ci/mac-vm/gn-args.json",
     "mac13-wpt-chromium-rel": "ci/mac13-wpt-chromium-rel/gn-args.json",
     "win-annotator-rel": "ci/win-annotator-rel/gn-args.json",
     "win-fieldtrial-rel": "ci/win-fieldtrial-rel/gn-args.json",
@@ -890,6 +891,7 @@
     "mac-perfetto-rel": "try/mac-perfetto-rel/gn-args.json",
     "mac-rel": "try/mac-rel/gn-args.json",
     "mac-ubsan-fyi-rel": "try/mac-ubsan-fyi-rel/gn-args.json",
+    "mac-vm": "try/mac-vm/gn-args.json",
     "mac11-arm64-rel": "try/mac11-arm64-rel/gn-args.json",
     "mac12-arm64-rel": "try/mac12-arm64-rel/gn-args.json",
     "mac12-tests": "try/mac12-tests/gn-args.json",
diff --git a/infra/config/generated/builders/try/mac-vm/gn-args.json b/infra/config/generated/builders/try/mac-vm/gn-args.json
new file mode 100644
index 0000000..809d265
--- /dev/null
+++ b/infra/config/generated/builders/try/mac-vm/gn-args.json
@@ -0,0 +1,11 @@
+{
+  "gn_args": {
+    "is_component_build": true,
+    "is_debug": true,
+    "symbol_level": 1,
+    "target_cpu": "arm64",
+    "target_os": "mac",
+    "use_remoteexec": true,
+    "use_siso": true
+  }
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/mac-vm/properties.json b/infra/config/generated/builders/try/mac-vm/properties.json
new file mode 100644
index 0000000..0e00899
--- /dev/null
+++ b/infra/config/generated/builders/try/mac-vm/properties.json
@@ -0,0 +1,67 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "additional_exclusions": [
+        "infra/config/generated/builders/try/mac-vm/gn-args.json"
+      ],
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "mac-vm",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium.fyi",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Debug",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "mac"
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "mac-vm",
+          "project": "chromium"
+        }
+      ],
+      "targets_spec_directory": "src/infra/config/generated/builders/try/mac-vm/targets"
+    }
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-untrusted",
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
+  },
+  "$build/siso": {
+    "configs": [
+      "builder"
+    ],
+    "enable_cloud_profiler": true,
+    "enable_cloud_trace": true,
+    "experiments": [],
+    "project": "rbe-chromium-untrusted"
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "tryserver.chromium.mac",
+  "recipe": "chromium_trybot"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/mac-vm/targets/chromium.fyi.json b/infra/config/generated/builders/try/mac-vm/targets/chromium.fyi.json
new file mode 100644
index 0000000..497f301
--- /dev/null
+++ b/infra/config/generated/builders/try/mac-vm/targets/chromium.fyi.json
@@ -0,0 +1,42 @@
+{
+  "mac-vm": {
+    "additional_compile_targets": [
+      "all"
+    ],
+    "gtest_tests": [
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "base_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "Apple_(Virtual)",
+            "os": "Mac-14",
+            "pool": "chromium.tests.macvm"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "interactive_ui_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "Apple_(Virtual)",
+            "os": "Mac-14",
+            "pool": "chromium.tests.macvm"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test": "interactive_ui_tests",
+        "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/infra/config/generated/health-specs/health-specs.json b/infra/config/generated/health-specs/health-specs.json
index 41fb4d0b..22e83e1 100644
--- a/infra/config/generated/health-specs/health-specs.json
+++ b/infra/config/generated/health-specs/health-specs.json
@@ -11443,6 +11443,27 @@
           }
         ]
       },
+      "mac-vm": {
+        "contact_team_email": "bling-engprod@google.com",
+        "problem_specs": [
+          {
+            "name": "Unhealthy",
+            "period_days": 7,
+            "score": 5,
+            "thresholds": {
+              "_default": "_default"
+            }
+          },
+          {
+            "name": "Low Value",
+            "period_days": 90,
+            "score": 1,
+            "thresholds": {
+              "_default": "_default"
+            }
+          }
+        ]
+      },
       "mac11-arm64-enterprise-companion-tester-dbg": {
         "contact_team_email": "omaha-client-dev@google.com",
         "problem_specs": [
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 8ba2982..90dc5f1f 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -5914,6 +5914,10 @@
         mode_allowlist: "FULL_RUN"
       }
       builders {
+        name: "chromium/try/mac-vm"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/mac11-arm64-rel"
         includable_only: true
       }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 3967313..7ada85e 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -64366,6 +64366,120 @@
       }
     }
     builders {
+      name: "mac-vm"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cpu:arm64"
+      dimensions: "free_space:standard"
+      dimensions: "os:Mac-14"
+      dimensions: "pool:luci.chromium.ci"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/ci/mac-vm/properties.json",'
+        '    "shadow_properties_file": "infra/config/generated/builders/ci/mac-vm/shadow-properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.fyi",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium"'
+        '}'
+      priority: 35
+      execution_timeout_secs: 36000
+      caches {
+        name: "xcode_ios_16c5032a"
+        path: "xcode_ios_16c5032a.app"
+      }
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium.use_per_builder_build_dir_name"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*_wpt_tests/.+)|(ninja://[^/]*headless_shell_wpt/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+      description_html: "Mac builder for running testing targets on Mac Virtual Machines<br/>This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/mac-vm\">mac-vm</a></li></ul>"
+      shadow_builder_adjustments {
+        service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+        pool: "luci.chromium.try"
+        dimensions: "free_space:"
+        dimensions: "pool:luci.chromium.try"
+      }
+      contact_team_email: "bling-engprod@google.com"
+      custom_metric_definitions {
+        name: "/chrome/infra/browser/builds/cached_count"
+        predicates: "has(build.output.properties.is_cached)"
+        predicates: "string(build.output.properties.is_cached) == \"true\""
+      }
+      custom_metric_definitions {
+        name: "/chrome/infra/browser/builds/ran_tests_retry_shard_count"
+        predicates: "has(build.output.properties.ran_tests_retry_shard)"
+      }
+      custom_metric_definitions {
+        name: "/chrome/infra/browser/builds/ran_tests_without_patch_count"
+        predicates: "has(build.output.properties.ran_tests_without_patch)"
+      }
+      custom_metric_definitions {
+        name: "/chrome/infra/browser/builds/uncached_count"
+        predicates: "has(build.output.properties.is_cached)"
+        predicates: "string(build.output.properties.is_cached) == \"false\""
+      }
+    }
+    builders {
       name: "mac11-arm64-enterprise-companion-tester-dbg"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -120446,6 +120560,117 @@
       }
     }
     builders {
+      name: "mac-vm"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cpu:arm64"
+      dimensions: "free_space:standard"
+      dimensions: "os:Mac-14"
+      dimensions: "pool:luci.chromium.try"
+      dimensions: "ssd:1"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/try/mac-vm/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "tryserver.chromium.mac",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium_trybot"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium.use_per_builder_build_dir_name"
+        value: 100
+      }
+      experiments {
+        key: "luci.buildbucket.canary_software"
+        value: 5
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome|content)/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*_wpt_tests/.+)|(ninja://[^/]*headless_shell_wpt/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+      description_html: "<br>Mac builder for running testing targets on Mac Virtual Machines<br/><br/>This builder mirrors the following CI builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/mac-vm\">mac-vm</a></li></ul>"
+      contact_team_email: "bling-engprod@google.com"
+      custom_metric_definitions {
+        name: "/chrome/infra/browser/builds/cached_count"
+        predicates: "has(build.output.properties.is_cached)"
+        predicates: "string(build.output.properties.is_cached) == \"true\""
+      }
+      custom_metric_definitions {
+        name: "/chrome/infra/browser/builds/ran_tests_retry_shard_count"
+        predicates: "has(build.output.properties.ran_tests_retry_shard)"
+      }
+      custom_metric_definitions {
+        name: "/chrome/infra/browser/builds/ran_tests_without_patch_count"
+        predicates: "has(build.output.properties.ran_tests_without_patch)"
+      }
+      custom_metric_definitions {
+        name: "/chrome/infra/browser/builds/uncached_count"
+        predicates: "has(build.output.properties.is_cached)"
+        predicates: "string(build.output.properties.is_cached) == \"false\""
+      }
+    }
+    builders {
       name: "mac11-arm64-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index c45eb25a..d985911 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -10116,11 +10116,6 @@
     short_name: "ios-blk"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/ios-vm"
-    category: "iOS"
-    short_name: "vm"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/ios-webkit-tot"
     category: "iOS"
     short_name: "wk"
@@ -10279,6 +10274,16 @@
     short_name: "cmp"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/ios-vm"
+    category: "macvm"
+    short_name: "ios"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.ci/mac-vm"
+    category: "macvm"
+    short_name: "mac"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/linux-multiscreen-fyi-rel"
     category: "mulitscreen"
   }
@@ -17935,6 +17940,9 @@
     name: "buildbucket/luci.chromium.try/mac-updater-try-builder-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/mac-vm"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/mac11-arm64-rel"
   }
   builders {
@@ -19431,6 +19439,9 @@
     name: "buildbucket/luci.chromium.try/mac-ubsan-fyi-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/mac-vm"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/mac11-arm64-rel"
   }
   builders {
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 784ca6a..16adbbf 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -5685,6 +5685,16 @@
   }
 }
 job {
+  id: "mac-vm"
+  realm: "ci"
+  schedule: "0 2-23/4 * * *"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "mac-vm"
+  }
+}
+job {
   id: "mac11-arm64-enterprise-companion-tester-dbg"
   realm: "ci"
   buildbucket {
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 351f0a89..d51d111 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -869,6 +869,53 @@
     ),
 )
 
+fyi_ios_builder(
+    name = "mac-vm",
+    description_html = "Mac builder for running testing targets on Mac Virtual Machines",
+    # every 4 hours beginning at 2am, deliberately offsetting from ios-vm
+    schedule = "0 2-23/4 * * *",
+    triggered_by = [],
+    builder_spec = builder_config.builder_spec(
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = ["mb"],
+            build_config = builder_config.build_config.DEBUG,
+            target_bits = 64,
+            target_platform = builder_config.target_platform.MAC,
+        ),
+    ),
+    gn_args = gn_args.config(
+        configs = [
+            "debug_builder",
+            "remoteexec",
+            "mac",
+            "arm64",
+        ],
+    ),
+    targets = targets.bundle(
+        targets = [
+            "mac_vm_tests",
+        ],
+        additional_compile_targets = [
+            "all",
+        ],
+        mixins = [
+            "mac_vm",
+        ],
+    ),
+    builderless = True,
+    os = os.MAC_DEFAULT,
+    cpu = cpu.ARM64,
+    console_view_entry = consoles.console_view_entry(
+        category = "macvm",
+        short_name = "mac",
+    ),
+    contact_team_email = "bling-engprod@google.com",
+)
+
 fyi_mac_builder(
     name = "mac13-wpt-chromium-rel",
     description_html = "Runs {} against Chrome.".format(
@@ -2017,8 +2064,8 @@
     os = os.MAC_DEFAULT,
     cpu = cpu.ARM64,
     console_view_entry = consoles.console_view_entry(
-        category = "iOS",
-        short_name = "vm",
+        category = "macvm",
+        short_name = "ios",
     ),
     contact_team_email = "bling-engprod@google.com",
     xcode = xcode.xcode_default,
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
index b5bed3f..a7fdfc8 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -257,6 +257,15 @@
 )
 
 try_.builder(
+    name = "mac-vm",
+    mirrors = ["ci/mac-vm"],
+    gn_args = "ci/mac-vm",
+    builderless = True,
+    cpu = cpu.ARM64,
+    contact_team_email = "bling-engprod@google.com",
+)
+
+try_.builder(
     name = "mac12-arm64-rel",
     branch_selector = branches.selector.MAC_BRANCHES,
     mirrors = [
diff --git a/infra/config/targets/bundles.star b/infra/config/targets/bundles.star
index a9dd6380..d011783 100644
--- a/infra/config/targets/bundles.star
+++ b/infra/config/targets/bundles.star
@@ -5327,6 +5327,21 @@
 )
 
 targets.bundle(
+    name = "mac_vm_tests",
+    targets = [
+        "base_unittests",
+        "interactive_ui_tests",
+    ],
+    per_test_modifications = {
+        "interactive_ui_tests": targets.mixin(
+            swarming = targets.swarming(
+                shards = 6,
+            ),
+        ),
+    },
+)
+
+targets.bundle(
     name = "monochrome_public_apk_checker_isolated_script",
     targets = [
         "monochrome_public_apk_checker",
diff --git a/internal b/internal
index 7cf9e93..3ce4702c 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 7cf9e93279ad7acb591d761935900a49457bbd70
+Subproject commit 3ce4702c33df16bb3cc2619173d8f80639872707
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 8716830..e3b7fa6 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -137,6 +137,14 @@
         {"5000", signin::kWaitThresholdMillisecondsForCapabilitiesApi, "5000"},
 };
 
+const FeatureEntry::Choice kLensOverlayAlternativeOnboardingChoices[] = {
+    {flags_ui::kGenericExperimentChoiceDefault, "", ""},
+    {"A: Speedbump menu", kLensOverlayAlternativeOnboardingType, "1"},
+    {"B: Updated Strings", kLensOverlayAlternativeOnboardingType, "2"},
+    {"C: Updated Strings and Graphics", kLensOverlayAlternativeOnboardingType,
+     "3"},
+};
+
 const FeatureEntry::FeatureParam kOmniboxUIMaxAutocompleteMatches3[] = {
     {OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "3"}};
 const FeatureEntry::FeatureParam kOmniboxUIMaxAutocompleteMatches4[] = {
@@ -2095,6 +2103,11 @@
      flag_descriptions::kLensOverlayForceShowOnboardingScreenDescription,
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kLensOverlayForceShowOnboardingScreen)},
+    {"lens-overlay-alternative-onboarding",
+     flag_descriptions::kLensOverlayAlternativeOnboardingName,
+     flag_descriptions::kLensOverlayAlternativeOnboardingDescription,
+     flags_ui::kOsIos,
+     MULTI_VALUE_TYPE(kLensOverlayAlternativeOnboardingChoices)},
     {"data-sharing", flag_descriptions::kDataSharingName,
      flag_descriptions::kDataSharingDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(data_sharing::features::kDataSharingFeature)},
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index fdb5793..3192e4c 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -685,6 +685,12 @@
 const char kLensFiltersAblationModeEnabledDescription[] =
     "Enables the filters ablation mode.";
 
+extern const char kLensOverlayAlternativeOnboardingName[] =
+    "Lens Overlay Onboarding";
+extern const char kLensOverlayAlternativeOnboardingDescription[] =
+    "Selects which lens overlay onboarding/entrypoint treatment is active. "
+    "No-op if lens overlay is off.";
+
 extern const char kLensOverlayDisablePriceInsightsName[] =
     "Allow Lens overlay to disable price insights";
 extern const char kLensOverlayDisablePriceInsightsDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 029d7db..cf72a1c7d 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -589,6 +589,11 @@
 extern const char kLensOverlayForceShowOnboardingScreenName[];
 extern const char kLensOverlayForceShowOnboardingScreenDescription[];
 
+// Title and description for the flag to switch the onboarding type for lens
+// overlay.
+extern const char kLensOverlayAlternativeOnboardingName[];
+extern const char kLensOverlayAlternativeOnboardingDescription[];
+
 // Title and description for the flag to disable price insights because of lens
 // overlay.
 extern const char kLensOverlayDisablePriceInsightsName[];
diff --git a/ios/chrome/browser/shared/public/features/features.h b/ios/chrome/browser/shared/public/features/features.h
index aca37f0..900a470 100644
--- a/ios/chrome/browser/shared/public/features/features.h
+++ b/ios/chrome/browser/shared/public/features/features.h
@@ -341,6 +341,12 @@
 // Feature to enable force showing the lens overlay onboarding screen.
 BASE_DECLARE_FEATURE(kLensOverlayForceShowOnboardingScreen);
 
+// Types of lens overlay onboarding.
+extern const char kLensOverlayAlternativeOnboardingType[];
+
+// Feature flag to switch between the lens overlay onboarding types.
+BASE_DECLARE_FEATURE(kLensOverlayAlternativeOnboarding);
+
 // Feature flag to enable UITraitCollection workaround for fixing incorrect
 // trait propagation.
 BASE_DECLARE_FEATURE(kEnableTraitCollectionWorkAround);
diff --git a/ios/chrome/browser/shared/public/features/features.mm b/ios/chrome/browser/shared/public/features/features.mm
index 80aee986..1db81f9 100644
--- a/ios/chrome/browser/shared/public/features/features.mm
+++ b/ios/chrome/browser/shared/public/features/features.mm
@@ -279,6 +279,13 @@
              "EnableLensOverlayForceShowOnboardingScreen",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+const char kLensOverlayAlternativeOnboardingType[] =
+    "kLensOverlayAlternativeOnboardingType";
+
+BASE_FEATURE(kLensOverlayAlternativeOnboarding,
+             "LensOverlayAlternativeOnboarding",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 BASE_FEATURE(kEnableTraitCollectionWorkAround,
              "EnableTraitCollectionWorkAround",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
index ede94dc..1a631eb 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-e1ca9f19b6327cda00dd4cc202472b273a996cfe
\ No newline at end of file
+f9c276013bc09835ee777f276500a1c0fc3d417b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
index a7af0232..da944615 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-6687d24c97104f7518a5490b2278c23480cc7c2f
\ No newline at end of file
+c7f503874ba3361e987d69532d76f2fd350dbf2a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index 05b8277..edb108b 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-572e5c1b9e74615fb0bee9db5f2a32f439cbd06b
\ No newline at end of file
+4c4b351b3bb6be8d3c8059b7c79a561c4561dd94
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
index 15a5bedbd..dee895e 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-5471dcfef6ce2458b6f02f1b084aa9ece6f17ed0
\ No newline at end of file
+354466def2316c12291dae63299b4b1bd8664d14
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index 7b06ec8..2a576b3 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-9fbecf50f813beb326ad465c0f7f18cf10bdc958
\ No newline at end of file
+6e5b74c35489209f87ddaddfd6849dcab21b8d46
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
index 89201572..e2b0dd9 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-460bf83e1ecab35084dad26b6030e0ca4bfde97e
\ No newline at end of file
+091e9c6335d8cb953360aeeded681cc18a5825b4
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
index 0c3812a..eccc093a 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-9dba49ae7e5d4ff5510667be5aedf8f2590244a4
\ No newline at end of file
+58a71c8b35b458bc524690b63eb6c1c0e2083875
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
index d5119663..abeefab 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-dfabad38c6d32633f9fbe5b78be00ec9b2d4f6e7
\ No newline at end of file
+8003265b7f1d9c379b54965304f8a66413fe40d6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
index 3ed7bfd..ec51b1c9 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-971c753906e7d6a3012b3ace190bedf2c99e17b1
\ No newline at end of file
+fae2cfeb80bdf208eee13184537ee9167b544af7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
index aea5596..7dc1db0b 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-52de1a9029b8b0b5f832e7cc7023b941a66f9d9f
\ No newline at end of file
+a2148282160b76d5786fc7292ac16fdc2ddc82f9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 3a66c3d..42ce12b 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-199991d9951c24ecea45c497ffe48ba7d655a4cb
\ No newline at end of file
+aea44dfc626dc4d0d5bc0fa8a7ff564bc7a84f87
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
index a4f15c77..5960e2d 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-6205d2142f52248e7819833a0051091d85ceeaef
\ No newline at end of file
+b1c940f614287f746a1270c8e5f84ebddfb6abb5
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 067708c..7b93f624 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-ab748dd653714cf3550844f30a8a9ca3e5450863
\ No newline at end of file
+20d36a8fcc917b8316fdacac67127c2e15a36e60
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 63de7b5..6f556b29 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-92e6272ba0e6f3da1577dcf1143a035a20df846b
\ No newline at end of file
+329024a6a1c99035186eb39b476224f5b1410ae6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index ba7a328f..97fde3c 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-fdc9736d61bab24b874fdfa201e6ae49062d575e
\ No newline at end of file
+d547c5757aa6da23dbf31943e8b9c8a6acf8827e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
index 101ebc3..6486e63a 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-5e22cd33277a837e91b84ffd0dfe90ee151598df
\ No newline at end of file
+f4de64ba9c0ce97079c8cedef2ae9a9145eff136
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index e2a5020..e629a55 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2425ca3486ca3d6794d3d3fe40b2c585879c283f
\ No newline at end of file
+c82402c19a19f285c36d56e0336b4054798cf62e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 7adb934..a7d6c72a 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-f33067cb299eb413feecdac9b5ab2bd5ddfda9e4
\ No newline at end of file
+3b5af3f669a11d41d994011bc88d6b8feba92e3c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index 8c94bb6b..a2fc0482 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-51b805092a8586e6a15ec2620136e76a81f9c9c0
\ No newline at end of file
+8b4a447dd294735093852817c4a46a1801ad37e0
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index 0833a8a..3a39c11 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-141c7a7b1b73e5db613b2b3459143df0f4318535
\ No newline at end of file
+794d180e7e49e92ccb5826ebf621115a80cb33de
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 439bc55d8..a6ffd579 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-92eb9b384365835b2065a381da0b9d1ee076580d
\ No newline at end of file
+a25df86ef3d523cf0b76cd906fbe18a1606466d5
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
index 5789dde..17a6baf1 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-83610c5dfc9e7c85a32e124f5645d6973071d63c
\ No newline at end of file
+de3e01355c2aea9737854eda243fa37aa395e386
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index 4874aa1..6e57ed6 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-09f1f695b635fd7ecc03083361bdf8541c8a2b54
\ No newline at end of file
+2e0e63e1802daf772419cf88c433cac71f3bbd32
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
index ac2e64d..7e292f7 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-363dc20dbc4841788c54b3c705f70181795e32ed
\ No newline at end of file
+eb438bdd0566d4afdd0ad3fa3ddf28d2d19e3f8d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index a204b8e..1881cc93 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-7e3e3a69a57638df8831bec9de343e13dc9898d9
\ No newline at end of file
+8cd584677c8c5fdfcb0bd3663382ddaf756e23fb
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
index 80be1cc1..77e8dd3 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-410888b44529570f9c00a4beadfb575df0153b98
\ No newline at end of file
+14b7d0ca7388da4855c02635cb9a02acaf5ff43e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 962117d0..f8c87a9 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-c2f7d0d7e7db529217db5913e19f231e549c823f
\ No newline at end of file
+4650bb45c9e15a8da92e061ea6c336dd849874b3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
index db07508..8c3e45331 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-23ae741873b9da24dcc78c21680e42e2f3d945ad
\ No newline at end of file
+9fd170e4992a15e10892975eff5b9ffa0f73d108
\ No newline at end of file
diff --git a/ios/third_party/material_components_ios/src b/ios/third_party/material_components_ios/src
index 0ac0dfe..5c9ba05 160000
--- a/ios/third_party/material_components_ios/src
+++ b/ios/third_party/material_components_ios/src
@@ -1 +1 @@
-Subproject commit 0ac0dfe5adf41700ef61e3bb0a1ece2362757433
+Subproject commit 5c9ba055eef03a043b7cf5191de54e1197fee86a
diff --git a/ios_internal b/ios_internal
index fa89054..5d2fd8e 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit fa89054d303701d661169f5d7cf4e8473dd3fe98
+Subproject commit 5d2fd8e76328068efba5dcac5421f3390b7aa9e7
diff --git a/media/cast/sender/frame_sender.h b/media/cast/sender/frame_sender.h
index c6960c31..9a5911b 100644
--- a/media/cast/sender/frame_sender.h
+++ b/media/cast/sender/frame_sender.h
@@ -47,18 +47,12 @@
     virtual void OnFrameCanceled(FrameId frame_id) {}
   };
 
-  // NOTE: currently only used by the VideoSender.
-  // TODO(https://crbug.com/1316434): cleanup bitrate calculations when libcast
-  // has successfully launched.
-  using GetSuggestedVideoBitrateCB = base::RepeatingCallback<int()>;
-
   // Method of creating a frame sender using an openscreen::cast::Sender.
   static std::unique_ptr<FrameSender> Create(
       scoped_refptr<CastEnvironment> cast_environment,
       const FrameSenderConfig& config,
       std::unique_ptr<openscreen::cast::Sender> sender,
-      Client& client,
-      GetSuggestedVideoBitrateCB get_bitrate_cb = GetSuggestedVideoBitrateCB());
+      Client& client);
 
   FrameSender();
   FrameSender(FrameSender&&) = delete;
@@ -104,10 +98,6 @@
   // Returns the number of frames that were sent but not yet acknowledged.
   virtual int GetUnacknowledgedFrameCount() const = 0;
 
-  // Returns the suggested bitrate the next frame should be encoded at.
-  virtual int GetSuggestedBitrate(base::TimeTicks playout_time,
-                                  base::TimeDelta playout_delay) = 0;
-
   // Configuration specific methods.
 
   // The maximum frame rate.
diff --git a/media/cast/sender/openscreen_frame_sender.cc b/media/cast/sender/openscreen_frame_sender.cc
index fc30382..53a8f4e 100644
--- a/media/cast/sender/openscreen_frame_sender.cc
+++ b/media/cast/sender/openscreen_frame_sender.cc
@@ -52,11 +52,9 @@
     scoped_refptr<CastEnvironment> cast_environment,
     const FrameSenderConfig& config,
     std::unique_ptr<openscreen::cast::Sender> sender,
-    Client& client,
-    FrameSender::GetSuggestedVideoBitrateCB get_bitrate_cb) {
+    Client& client) {
   return std::make_unique<OpenscreenFrameSender>(cast_environment, config,
-                                                 std::move(sender), client,
-                                                 std::move(get_bitrate_cb));
+                                                 std::move(sender), client);
 }
 
 // Convenience macro used in logging statements throughout this file.
@@ -68,8 +66,7 @@
     scoped_refptr<CastEnvironment> cast_environment,
     const FrameSenderConfig& config,
     std::unique_ptr<openscreen::cast::Sender> sender,
-    Client& client,
-    FrameSender::GetSuggestedVideoBitrateCB get_bitrate_cb)
+    Client& client)
     : cast_environment_(cast_environment),
       sender_(std::move(sender)),
       client_(client),
@@ -78,10 +75,6 @@
       min_playout_delay_(config.min_playout_delay),
       max_playout_delay_(config.max_playout_delay) {
   CHECK_GT(sender_->config().rtp_timebase, 0);
-  if (!is_audio_) {
-    bitrate_suggester_ = std::make_unique<VideoBitrateSuggester>(
-        config, std::move(get_bitrate_cb));
-  }
 
   const std::chrono::milliseconds target_playout_delay =
       sender_->config().target_playout_delay;
@@ -168,13 +161,6 @@
   return sender_->GetInFlightFrameCount();
 }
 
-int OpenscreenFrameSender::GetSuggestedBitrate(base::TimeTicks playout_time,
-                                               base::TimeDelta playout_delay) {
-  // Currently only used by the video sender.
-  DCHECK(!is_audio_);
-  return bitrate_suggester_->GetSuggestedBitrate();
-}
-
 double OpenscreenFrameSender::MaxFrameRate() const {
   return max_frame_rate_;
 }
@@ -294,7 +280,6 @@
   const int count_frames_in_flight =
       GetUnacknowledgedFrameCount() + client_->GetNumberOfFramesInEncoder();
   if (count_frames_in_flight >= kMaxUnackedFrames) {
-    RecordShouldDropNextFrame(/*should_drop=*/true);
     return CastStreamingFrameDropReason::kTooManyFramesInFlight;
   }
 
@@ -304,7 +289,6 @@
   const double max_frames_in_flight =
       max_frame_rate_ * duration_in_flight.InSecondsF();
   if (count_frames_in_flight >= max_frames_in_flight + kMaxFrameBurst) {
-    RecordShouldDropNextFrame(/*should_drop=*/true);
     return CastStreamingFrameDropReason::kBurstThresholdExceeded;
   }
 
@@ -331,18 +315,11 @@
     }
   }
   if (duration_would_be_in_flight > allowed_in_flight) {
-    RecordShouldDropNextFrame(/*should_drop=*/true);
     return CastStreamingFrameDropReason::kInFlightDurationTooHigh;
   }
 
   // Next frame is accepted.
-  RecordShouldDropNextFrame(/*should_drop=*/false);
   return CastStreamingFrameDropReason::kNotDropped;
 }
 
-void OpenscreenFrameSender::RecordShouldDropNextFrame(bool should_drop) {
-  if (bitrate_suggester_) {
-    bitrate_suggester_->RecordShouldDropNextFrame(should_drop);
-  }
-}
 }  // namespace media::cast
diff --git a/media/cast/sender/openscreen_frame_sender.h b/media/cast/sender/openscreen_frame_sender.h
index 01c18362..56ab9bc1 100644
--- a/media/cast/sender/openscreen_frame_sender.h
+++ b/media/cast/sender/openscreen_frame_sender.h
@@ -46,8 +46,7 @@
   OpenscreenFrameSender(scoped_refptr<CastEnvironment> cast_environment,
                         const FrameSenderConfig& config,
                         std::unique_ptr<openscreen::cast::Sender> sender,
-                        Client& client,
-                        FrameSender::GetSuggestedVideoBitrateCB get_bitrate_cb);
+                        Client& client);
   OpenscreenFrameSender(OpenscreenFrameSender&& other) = delete;
   OpenscreenFrameSender& operator=(OpenscreenFrameSender&& other) = delete;
   OpenscreenFrameSender(const OpenscreenFrameSender&) = delete;
@@ -64,8 +63,6 @@
       base::TimeDelta frame_duration) override;
   RtpTimeTicks GetRecordedRtpTimestamp(FrameId frame_id) const override;
   int GetUnacknowledgedFrameCount() const override;
-  int GetSuggestedBitrate(base::TimeTicks playout_time,
-                          base::TimeDelta playout_delay) override;
   double MaxFrameRate() const override;
   void SetMaxFrameRate(double max_frame_rate) override;
   base::TimeDelta TargetPlayoutDelay() const override;
@@ -98,8 +95,6 @@
   // fluctuates in response to the currently-measured network latency.
   base::TimeDelta GetAllowedInFlightMediaDuration() const;
 
-  void RecordShouldDropNextFrame(bool should_drop);
-
   // The cast environment.
   const scoped_refptr<CastEnvironment> cast_environment_;
 
@@ -109,9 +104,6 @@
   // The frame sender client.
   const raw_ref<Client> client_;
 
-  // The method for getting the recommended bitrate.
-  GetSuggestedVideoBitrateCB get_bitrate_cb_;
-
   // Max encoded frames generated per second.
   double max_frame_rate_;
 
@@ -160,10 +152,6 @@
   // buffer is the lower 8 bits of the FrameId.
   std::array<RtpTimeTicks, 256> frame_rtp_timestamps_;
 
-  // TODO(https://crbug.com/1316434): move this property to VideoSender once
-  // the legacy implementation has been removed.
-  std::unique_ptr<VideoBitrateSuggester> bitrate_suggester_;
-
   // NOTE: Weak pointers must be invalidated before all other member variables.
   base::WeakPtrFactory<OpenscreenFrameSender> weak_factory_{this};
 };
diff --git a/media/cast/sender/openscreen_frame_sender_unittest.cc b/media/cast/sender/openscreen_frame_sender_unittest.cc
index 815fc0a..818f07b9 100644
--- a/media/cast/sender/openscreen_frame_sender_unittest.cc
+++ b/media/cast/sender/openscreen_frame_sender_unittest.cc
@@ -82,8 +82,6 @@
   base::TimeDelta GetEncoderBacklogDuration() const override { return {}; }
   void OnFrameCanceled(FrameId frame_id) override {}
 
-  int get_suggested_bitrate() { return suggested_bitrate_; }
-
  protected:
   OpenscreenFrameSenderTest()
       : task_runner_(
@@ -108,23 +106,11 @@
 
     audio_sender_ = std::make_unique<OpenscreenFrameSender>(
         cast_environment_, kAudioConfig, std::move(openscreen_audio_sender),
-        *this,
-        base::BindRepeating(&OpenscreenFrameSenderTest::get_suggested_bitrate,
-                            // Safe because we destroy the audio sender before
-                            // destroying `this`.
-                            base::Unretained(this)));
+        *this);
 
     video_sender_ = std::make_unique<OpenscreenFrameSender>(
         cast_environment_, kVideoConfig, std::move(openscreen_video_sender),
-        *this,
-        base::BindRepeating(&OpenscreenFrameSenderTest::get_suggested_bitrate,
-                            // Safe because we destroy the audio sender before
-                            // destroying `this`.
-                            base::Unretained(this)));
-  }
-
-  void RecordShouldDropNextFrame(bool should_drop) {
-    video_sender_->RecordShouldDropNextFrame(should_drop);
+        *this);
   }
 
   void set_suggested_bitrate(int bitrate) { suggested_bitrate_ = bitrate; }
@@ -275,18 +261,4 @@
             video_sender().EnqueueFrame(std::move(video_frame_two)));
 }
 
-TEST_F(OpenscreenFrameSenderTest, HandlesSuggestedBitratesCorrectly) {
-  // NOTE: the VideoBitrateSuggester tests this workflow more thoroughly.
-
-  // We should start with the maximum video bitrate.
-  set_suggested_bitrate(5000001);
-  EXPECT_EQ(5000000, video_sender().GetSuggestedBitrate(base::TimeTicks{},
-                                                        base::TimeDelta{}));
-
-  // It should cap at the bitrate suggested by Open Screen.
-  set_suggested_bitrate(4998374);
-  EXPECT_EQ(4998374, video_sender().GetSuggestedBitrate(base::TimeTicks{},
-                                                        base::TimeDelta{}));
-}
-
 }  // namespace media::cast
diff --git a/media/cast/sender/video_bitrate_suggester.cc b/media/cast/sender/video_bitrate_suggester.cc
index d6bb94d8..7091f749 100644
--- a/media/cast/sender/video_bitrate_suggester.cc
+++ b/media/cast/sender/video_bitrate_suggester.cc
@@ -6,26 +6,24 @@
 
 #include <algorithm>
 #include <limits>
-#include <memory>
-#include <utility>
-#include <vector>
 
+#include "base/check.h"
 #include "base/feature_list.h"
 #include "base/logging.h"
 #include "media/base/media_switches.h"
-#include "media/cast/common/openscreen_conversion_helpers.h"
-#include "media/cast/common/sender_encoded_frame.h"
 #include "media/cast/constants.h"
 
 namespace media::cast {
 
 VideoBitrateSuggester::VideoBitrateSuggester(
     const FrameSenderConfig& config,
-    FrameSender::GetSuggestedVideoBitrateCB get_bitrate_cb)
-    : get_bitrate_cb_(std::move(get_bitrate_cb)),
+    GetVideoNetworkBandwidthCB get_bitrate_cb)
+    : get_bandwidth_cb_(std::move(get_bitrate_cb)),
       min_bitrate_(config.min_bitrate),
       max_bitrate_(config.max_bitrate),
-      suggested_max_bitrate_(max_bitrate_) {}
+      suggested_bitrate_(max_bitrate_) {
+  CHECK_GE(max_bitrate_, min_bitrate_);
+}
 
 VideoBitrateSuggester::~VideoBitrateSuggester() = default;
 
@@ -34,16 +32,16 @@
   // we also need to consider how well this device is handling encoding at
   // this bitrate overall.
   const int suggested_bitrate =
-      std::min(get_bitrate_cb_.Run(), suggested_max_bitrate_);
+      std::min(get_bandwidth_cb_.Run(), suggested_bitrate_);
 
   // Honor the config boundaries.
   return std::clamp(suggested_bitrate, min_bitrate_, max_bitrate_);
 }
 
 void VideoBitrateSuggester::RecordShouldDropNextFrame(bool should_drop) {
-  ++number_of_frames_requested_;
+  ++frames_requested_;
   if (should_drop) {
-    ++number_of_frames_dropped_;
+    ++frames_dropped_;
   }
 
   if (base::FeatureList::IsEnabled(
@@ -55,53 +53,39 @@
 }
 
 void VideoBitrateSuggester::UpdateSuggestionUsingExponentialAlgorithm() {
-  // We don't want to change the bitrate too frequently in order to give
-  // things time to adjust, so only adjust roughly once a second.
-  constexpr int kWindowSize = 30;
-  if (number_of_frames_requested_ == kWindowSize) {
-    DCHECK_GE(max_bitrate_, min_bitrate_);
+  static constexpr int kWindowSize = 30;
+  if (frames_requested_ == kWindowSize) {
+    // Be more conservative about increasing than decreasing the bitrate.
+    constexpr double kIncreaseFactor = 1.1;
+    constexpr double kDecreaseFactor = 0.7;
 
-    // We want to be more conservative about increasing the frame rate than
-    // decreasing it.
-    constexpr double kIncrease = 1.1;
-    constexpr double kDecrease = 0.8;
+    // Dropping any frames is a bad sign.
+    suggested_bitrate_ =
+        (frames_dropped_ > 0)
+            ? std::max<int>(min_bitrate_, suggested_bitrate_ * kDecreaseFactor)
+            : std::min<int>(max_bitrate_, suggested_bitrate_ * kIncreaseFactor);
 
-    // Generally speaking we shouldn't be dropping any frames, so even one is
-    // a bad sign.
-    suggested_max_bitrate_ =
-        (number_of_frames_dropped_ > 0)
-            ? std::max<int>(min_bitrate_, suggested_max_bitrate_ * kDecrease)
-            : std::min<int>(max_bitrate_, suggested_max_bitrate_ * kIncrease);
-
-    // Reset the recorded frame drops to start a new window.
-    number_of_frames_requested_ = 0;
-    number_of_frames_dropped_ = 0;
+    // Reset the frame counts to start a new window.
+    frames_requested_ = 0;
+    frames_dropped_ = 0;
   }
 }
 
 void VideoBitrateSuggester::UpdateSuggestionUsingLinearAlgorithm() {
-  // We don't want to change the bitrate too frequently in order to give
-  // things time to adjust, so only adjust every 100 frames (about 3 seconds
-  // at 30FPS).
-  constexpr int kWindowSize = 100;
-  if (number_of_frames_requested_ == kWindowSize) {
-    constexpr int kBitrateSteps = 8;
-    DCHECK_GE(max_bitrate_, min_bitrate_);
+  static constexpr int kWindowSize = 100;
+  if (frames_requested_ == kWindowSize) {
+    static constexpr int kBitrateSteps = 8;
     const int adjustment = (max_bitrate_ - min_bitrate_) / kBitrateSteps;
 
-    // Generally speaking we shouldn't be dropping any frames, so even one is
-    // a bad sign.
-    if (number_of_frames_dropped_ > 0) {
-      suggested_max_bitrate_ =
-          std::max(min_bitrate_, suggested_max_bitrate_ - adjustment);
-    } else {
-      suggested_max_bitrate_ =
-          std::min(max_bitrate_, suggested_max_bitrate_ + adjustment);
-    }
+    // Dropping any frames is a bad sign.
+    suggested_bitrate_ =
+        (frames_dropped_ > 0)
+            ? std::max(min_bitrate_, suggested_bitrate_ - adjustment)
+            : std::min(max_bitrate_, suggested_bitrate_ + adjustment);
 
-    // Reset the recorded frame drops to start a new window.
-    number_of_frames_requested_ = 0;
-    number_of_frames_dropped_ = 0;
+    // Reset the frame counts to start a new window.
+    frames_requested_ = 0;
+    frames_dropped_ = 0;
   }
 }
 
diff --git a/media/cast/sender/video_bitrate_suggester.h b/media/cast/sender/video_bitrate_suggester.h
index ba8b995..c62f5f3 100644
--- a/media/cast/sender/video_bitrate_suggester.h
+++ b/media/cast/sender/video_bitrate_suggester.h
@@ -6,14 +6,15 @@
 #define MEDIA_CAST_SENDER_VIDEO_BITRATE_SUGGESTER_H_
 
 #include "media/cast/cast_config.h"
-#include "media/cast/sender/frame_sender.h"
 
 namespace media::cast {
 
 class VideoBitrateSuggester {
  public:
+  using GetVideoNetworkBandwidthCB = base::RepeatingCallback<int()>;
+
   VideoBitrateSuggester(const FrameSenderConfig& config,
-                        FrameSender::GetSuggestedVideoBitrateCB get_bitrate_cb);
+                        GetVideoNetworkBandwidthCB get_bitrate_cb);
   VideoBitrateSuggester(VideoBitrateSuggester&& other) = delete;
   VideoBitrateSuggester& operator=(VideoBitrateSuggester&& other) = delete;
   VideoBitrateSuggester(const VideoBitrateSuggester&) = delete;
@@ -33,19 +34,19 @@
   void UpdateSuggestionUsingLinearAlgorithm();
 
   // The method for getting the recommended bitrate.
-  FrameSender::GetSuggestedVideoBitrateCB get_bitrate_cb_;
+  GetVideoNetworkBandwidthCB get_bandwidth_cb_;
 
   // The minimum and maximum bitrates set from the config.
-  int min_bitrate_ = 0;
-  int max_bitrate_ = 0;
+  const int min_bitrate_ = 0;
+  const int max_bitrate_ = 0;
 
-  // The suggested maximum bitrate, factoring in frame drops.
-  int suggested_max_bitrate_ = 0;
+  // The suggested bitrate, factoring in frame drops.
+  int suggested_bitrate_ = 0;
 
   // We keep track of how many frames get dropped in order to lower the video
   // bitrate when appropriate.
-  int number_of_frames_requested_ = 0;
-  int number_of_frames_dropped_ = 0;
+  int frames_requested_ = 0;
+  int frames_dropped_ = 0;
 };
 
 }  // namespace media::cast
diff --git a/media/cast/sender/video_bitrate_suggester_unittest.cc b/media/cast/sender/video_bitrate_suggester_unittest.cc
index 569f36d6..969d529 100644
--- a/media/cast/sender/video_bitrate_suggester_unittest.cc
+++ b/media/cast/sender/video_bitrate_suggester_unittest.cc
@@ -54,7 +54,7 @@
 
  protected:
   VideoBitrateSuggesterTest() {
-    video_bitrate_suggester_ = std::make_unique<VideoBitrateSuggester>(
+    suggester_ = std::make_unique<VideoBitrateSuggester>(
         kVideoConfig,
         base::BindRepeating(&VideoBitrateSuggesterTest::get_suggested_bitrate,
                             // Safe because we destroy the audio sender before
@@ -63,14 +63,12 @@
   }
 
   void RecordShouldDropNextFrame(bool should_drop) {
-    video_bitrate_suggester_->RecordShouldDropNextFrame(should_drop);
+    suggester_->RecordShouldDropNextFrame(should_drop);
   }
 
   void set_suggested_bitrate(int bitrate) { suggested_bitrate_ = bitrate; }
 
-  VideoBitrateSuggester& video_bitrate_suggester() {
-    return *video_bitrate_suggester_;
-  }
+  VideoBitrateSuggester& suggester() { return *suggester_; }
 
   void UseExponentialAlgorithm() {
     feature_list_.InitAndEnableFeature(
@@ -83,18 +81,26 @@
   }
 
  private:
-  std::unique_ptr<VideoBitrateSuggester> video_bitrate_suggester_;
+  std::unique_ptr<VideoBitrateSuggester> suggester_;
   base::test::ScopedFeatureList feature_list_;
   int suggested_bitrate_ = 0;
 };
 
+TEST_F(VideoBitrateSuggesterTest, StaysWithinBounds) {
+  set_suggested_bitrate(10000000);
+  EXPECT_EQ(kDefaultMaxVideoBitrate, suggester().GetSuggestedBitrate());
+
+  set_suggested_bitrate(1);
+  EXPECT_EQ(kDefaultMinVideoBitrate, suggester().GetSuggestedBitrate());
+}
+
 TEST_F(VideoBitrateSuggesterTest,
        SuggestsBitratesCorrectlyWithExponentialAlgorithm) {
   UseExponentialAlgorithm();
 
   // We should start with the maximum video bitrate.
   set_suggested_bitrate(5000001);
-  EXPECT_EQ(5000000, video_bitrate_suggester().GetSuggestedBitrate());
+  EXPECT_EQ(kDefaultMaxVideoBitrate, suggester().GetSuggestedBitrate());
 
   // After a period with multiple frame drops, this should go down.
   RecordShouldDropNextFrame(true);
@@ -103,44 +109,42 @@
     RecordShouldDropNextFrame(false);
   }
 
-  // It should now go down.
-  EXPECT_EQ(4000000, video_bitrate_suggester().GetSuggestedBitrate());
-
   // It should continue to go down to the minimum as long as frames are being
   // dropped.
-  int last_suggestion = 4685120;
-  for (int i = 0; i < 12; ++i) {
+  int last_suggestion = suggester().GetSuggestedBitrate();
+  EXPECT_EQ(3500000, last_suggestion);
+  while (last_suggestion > kDefaultMinVideoBitrate) {
     RecordShouldDropNextFrame(true);
     for (int j = 0; j < 29; ++j) {
       RecordShouldDropNextFrame(false);
     }
 
     // It should drop every time.
-    const int suggestion = video_bitrate_suggester().GetSuggestedBitrate();
+    const int suggestion = suggester().GetSuggestedBitrate();
     EXPECT_LT(suggestion, last_suggestion);
     last_suggestion = suggestion;
   }
 
   // And then stabilize at the bottom.
-  EXPECT_EQ(300000, video_bitrate_suggester().GetSuggestedBitrate());
+  EXPECT_EQ(kDefaultMinVideoBitrate, suggester().GetSuggestedBitrate());
 
   // It should increase once we stop dropping frames.
-  last_suggestion = 300000;
-  for (int i = 0; i < 30; ++i) {
+  last_suggestion = kDefaultMinVideoBitrate;
+  while (last_suggestion < kDefaultMaxVideoBitrate) {
     for (int j = 0; j < 30; ++j) {
       RecordShouldDropNextFrame(false);
     }
-    const int suggestion = video_bitrate_suggester().GetSuggestedBitrate();
+    const int suggestion = suggester().GetSuggestedBitrate();
     EXPECT_GT(suggestion, last_suggestion);
     last_suggestion = suggestion;
   }
 
   // And stop at the maximum.
-  EXPECT_EQ(5000000, video_bitrate_suggester().GetSuggestedBitrate());
+  EXPECT_EQ(kDefaultMaxVideoBitrate, suggester().GetSuggestedBitrate());
 
   // Finally, it should cap at the bitrate suggested by Open Screen.
   set_suggested_bitrate(4998374);
-  EXPECT_EQ(4998374, video_bitrate_suggester().GetSuggestedBitrate());
+  EXPECT_EQ(4998374, suggester().GetSuggestedBitrate());
 }
 
 TEST_F(VideoBitrateSuggesterTest,
@@ -149,7 +153,7 @@
 
   // We should start with the maximum video bitrate.
   set_suggested_bitrate(5000001);
-  EXPECT_EQ(5000000, video_bitrate_suggester().GetSuggestedBitrate());
+  EXPECT_EQ(kDefaultMaxVideoBitrate, suggester().GetSuggestedBitrate());
 
   // After a period with multiple frame drops, this should go down.
   RecordShouldDropNextFrame(true);
@@ -159,42 +163,40 @@
   }
 
   // It should now go down.
-  EXPECT_EQ(4412500, video_bitrate_suggester().GetSuggestedBitrate());
+  int last_suggestion = suggester().GetSuggestedBitrate();
+  EXPECT_EQ(4412500, last_suggestion);
 
   // It should continue to go down to the minimum as long as frames are being
   // dropped.
-  int last_suggestion = 4412500;
-  for (int i = 0; i < 7; ++i) {
+  while (last_suggestion > kDefaultMinVideoBitrate) {
     RecordShouldDropNextFrame(true);
     for (int j = 0; j < 99; ++j) {
       RecordShouldDropNextFrame(false);
     }
 
     // It should drop every time.
-    const int suggestion = video_bitrate_suggester().GetSuggestedBitrate();
+    const int suggestion = suggester().GetSuggestedBitrate();
     EXPECT_LT(suggestion, last_suggestion);
     last_suggestion = suggestion;
   }
 
-  // And then stabilize at the bottom.
-  EXPECT_EQ(300000, video_bitrate_suggester().GetSuggestedBitrate());
-
   // It should increase once we stop dropping frames.
-  last_suggestion = 300000;
-  for (int i = 0; i < 8; ++i) {
+  last_suggestion = suggester().GetSuggestedBitrate();
+  EXPECT_EQ(kDefaultMinVideoBitrate, last_suggestion);
+  while (last_suggestion < kDefaultMaxVideoBitrate) {
     for (int j = 0; j < 100; ++j) {
       RecordShouldDropNextFrame(false);
     }
-    const int suggestion = video_bitrate_suggester().GetSuggestedBitrate();
+    const int suggestion = suggester().GetSuggestedBitrate();
     EXPECT_GT(suggestion, last_suggestion);
     last_suggestion = suggestion;
   }
 
   // And stop at the maximum.
-  EXPECT_EQ(5000000, video_bitrate_suggester().GetSuggestedBitrate());
+  EXPECT_EQ(kDefaultMaxVideoBitrate, suggester().GetSuggestedBitrate());
 
   // Finally, it should cap at the bitrate suggested by Open Screen.
   set_suggested_bitrate(4998374);
-  EXPECT_EQ(4998374, video_bitrate_suggester().GetSuggestedBitrate());
+  EXPECT_EQ(4998374, suggester().GetSuggestedBitrate());
 }
 }  // namespace media::cast
diff --git a/media/cast/sender/video_sender.cc b/media/cast/sender/video_sender.cc
index b13caed..56481d1 100644
--- a/media/cast/sender/video_sender.cc
+++ b/media/cast/sender/video_sender.cc
@@ -23,6 +23,7 @@
 #include "media/cast/encoding/video_encoder.h"
 #include "media/cast/sender/openscreen_frame_sender.h"
 #include "media/cast/sender/performance_metrics_overlay.h"
+#include "media/cast/sender/video_bitrate_suggester.h"
 #include "third_party/openscreen/src/cast/streaming/public/encoded_frame.h"
 #include "third_party/openscreen/src/cast/streaming/public/sender.h"
 
@@ -111,16 +112,18 @@
     std::unique_ptr<openscreen::cast::Sender> sender,
     std::unique_ptr<media::VideoEncoderMetricsProvider>
         encoder_metrics_provider,
-    PlayoutDelayChangeCB playout_delay_change_cb,
+    VideoSender::PlayoutDelayChangeCB playout_delay_change_cb,
     media::VideoCaptureFeedbackCB feedback_cb,
-    FrameSender::GetSuggestedVideoBitrateCB get_bitrate_cb,
+    VideoBitrateSuggester::GetVideoNetworkBandwidthCB get_bandwidth_cb,
     media::GpuVideoAcceleratorFactories* gpu_factories)
     : frame_sender_(FrameSender::Create(cast_environment,
                                         video_config,
                                         std::move(sender),
-                                        *this,
-                                        std::move(get_bitrate_cb))),
+                                        *this)),
       cast_environment_(cast_environment),
+      bitrate_suggester_(
+          std::make_unique<VideoBitrateSuggester>(video_config,
+                                                  std::move(get_bandwidth_cb))),
       min_playout_delay_(video_config.min_playout_delay),
       max_playout_delay_(video_config.max_playout_delay),
       playout_delay_change_cb_(std::move(playout_delay_change_cb)),
@@ -214,7 +217,10 @@
   number_of_frames_inserted_++;
   const CastStreamingFrameDropReason reason =
       frame_sender_->ShouldDropNextFrame(duration_added_by_next_frame);
-  if (reason != CastStreamingFrameDropReason::kNotDropped) {
+  const bool should_drop_frame =
+      reason != CastStreamingFrameDropReason::kNotDropped;
+  bitrate_suggester_->RecordShouldDropNextFrame(should_drop_frame);
+  if (should_drop_frame) {
     base::TimeDelta new_target_delay =
         std::min(frame_sender_->CurrentRoundTripTime() * kRoundTripsNeeded +
                      base::Milliseconds(kConstantTimeMs),
@@ -250,9 +256,7 @@
     return;
   }
 
-  const int bitrate = frame_sender_->GetSuggestedBitrate(
-      reference_time + frame_sender_->TargetPlayoutDelay(),
-      frame_sender_->TargetPlayoutDelay());
+  const int bitrate = bitrate_suggester_->GetSuggestedBitrate();
   if (bitrate != last_bitrate_) {
     video_encoder_->SetBitRate(bitrate);
     last_bitrate_ = bitrate;
diff --git a/media/cast/sender/video_sender.h b/media/cast/sender/video_sender.h
index 3e19967..81efd5c 100644
--- a/media/cast/sender/video_sender.h
+++ b/media/cast/sender/video_sender.h
@@ -17,6 +17,7 @@
 #include "media/cast/cast_config.h"
 #include "media/cast/common/rtp_time.h"
 #include "media/cast/sender/frame_sender.h"
+#include "media/cast/sender/video_bitrate_suggester.h"
 
 namespace openscreen::cast {
 class Sender;
@@ -32,7 +33,6 @@
 
 class VideoEncoder;
 
-using PlayoutDelayChangeCB = base::RepeatingCallback<void(base::TimeDelta)>;
 
 // Not thread safe. Only called from the main cast thread.
 // This class owns all objects related to sending video, objects that create RTP
@@ -42,19 +42,22 @@
 // timeouts.
 class VideoSender : public FrameSender::Client {
  public:
+  using PlayoutDelayChangeCB = base::RepeatingCallback<void(base::TimeDelta)>;
+
   // NOTE: Since the `Sender` instance is destroyed when renegotiation is
   // complete, `this` is also invalid and should be immediately torn down.
-  VideoSender(scoped_refptr<CastEnvironment> cast_environment,
-              const FrameSenderConfig& video_config,
-              StatusChangeCallback status_change_cb,
-              const CreateVideoEncodeAcceleratorCallback& create_vea_cb,
-              std::unique_ptr<openscreen::cast::Sender> sender,
-              std::unique_ptr<media::VideoEncoderMetricsProvider>
-                  encoder_metrics_provider,
-              PlayoutDelayChangeCB playout_delay_change_cb,
-              media::VideoCaptureFeedbackCB feedback_cb,
-              FrameSender::GetSuggestedVideoBitrateCB get_bitrate_cb,
-              media::GpuVideoAcceleratorFactories* gpu_factories);
+  VideoSender(
+      scoped_refptr<CastEnvironment> cast_environment,
+      const FrameSenderConfig& video_config,
+      StatusChangeCallback status_change_cb,
+      const CreateVideoEncodeAcceleratorCallback& create_vea_cb,
+      std::unique_ptr<openscreen::cast::Sender> sender,
+      std::unique_ptr<media::VideoEncoderMetricsProvider>
+          encoder_metrics_provider,
+      PlayoutDelayChangeCB playout_delay_change_cb,
+      media::VideoCaptureFeedbackCB feedback_cb,
+      VideoBitrateSuggester::GetVideoNetworkBandwidthCB get_bandwidth_cb,
+      media::GpuVideoAcceleratorFactories* gpu_factories);
 
   VideoSender(const VideoSender&) = delete;
   VideoSender& operator=(const VideoSender&) = delete;
@@ -108,6 +111,9 @@
   // we get the same value.
   int last_bitrate_ = 0;
 
+  // Keeps track of frame drops and uses that to drive the video bitrate.
+  std::unique_ptr<VideoBitrateSuggester> bitrate_suggester_;
+
   // The total amount of time between a frame's capture/recording on the sender
   // and its playback on the receiver (i.e., shown to a user).
   base::TimeDelta min_playout_delay_;
diff --git a/media/cast/sender/video_sender_unittest.cc b/media/cast/sender/video_sender_unittest.cc
index b82f61a..a8298e2 100644
--- a/media/cast/sender/video_sender_unittest.cc
+++ b/media/cast/sender/video_sender_unittest.cc
@@ -81,7 +81,7 @@
 
 void IgnorePlayoutDelayChanges(base::TimeDelta unused_playout_delay) {}
 
-int GetSuggestedVideoBitrate() {
+int GetVideoNetworkBandwidth() {
   return openscreen::cast::kDefaultVideoMinBitRate;
 }
 
@@ -203,7 +203,7 @@
         base::BindRepeating(&IgnorePlayoutDelayChanges),
         base::BindRepeating(&VideoSenderTest::HandleVideoCaptureFeedback,
                             base::Unretained(this)),
-        base::BindRepeating(&GetSuggestedVideoBitrate),
+        base::BindRepeating(&GetVideoNetworkBandwidth),
         mock_gpu_factories_.get());
 
     RunTasksAndAdvanceClock();
diff --git a/mojo/core/ipcz_driver/transport.cc b/mojo/core/ipcz_driver/transport.cc
index 0789ea1..96918f8 100644
--- a/mojo/core/ipcz_driver/transport.cc
+++ b/mojo/core/ipcz_driver/transport.cc
@@ -186,6 +186,12 @@
       // is connected to a known elevated process.)
       return PlatformHandle();
     }
+    // Verify that this is a handle to a valid object. We do not yet know the
+    // expected type of the handle (region, file, etc.) so cannot validate that.
+    DWORD dummy;
+    if (!::GetHandleInformation(handle, &dummy)) {
+      return PlatformHandle();
+    }
     return PlatformHandle(base::win::ScopedHandle(handle));
   }
 
@@ -208,6 +214,11 @@
 
 }  // namespace
 
+// static
+size_t Transport::FirstHandleOffsetForTesting() {
+  return sizeof(ObjectHeader);
+}
+
 Transport::Transport(EndpointTypes endpoint_types,
                      PlatformChannelEndpoint endpoint,
                      base::Process remote_process,
diff --git a/mojo/core/ipcz_driver/transport.h b/mojo/core/ipcz_driver/transport.h
index ae400f3..6e69734 100644
--- a/mojo/core/ipcz_driver/transport.h
+++ b/mojo/core/ipcz_driver/transport.h
@@ -161,6 +161,9 @@
   void OnChannelError(Channel::Error error) override;
   void OnChannelDestroyed() override;
 
+  // Allow tests to nerf serialized handles to validate recipient behavior.
+  static size_t FirstHandleOffsetForTesting();
+
  private:
   struct PendingTransmission {
     PendingTransmission();
diff --git a/mojo/core/ipcz_driver/transport_test.cc b/mojo/core/ipcz_driver/transport_test.cc
index 376bf9c..49267b5 100644
--- a/mojo/core/ipcz_driver/transport_test.cc
+++ b/mojo/core/ipcz_driver/transport_test.cc
@@ -496,5 +496,59 @@
   });
 }
 
+#if BUILDFLAG(IS_WIN)
+constexpr std::string_view kGotInvalid = "got an invalid handle as expected";
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(InvalidHandleClient,
+                                  MojoIpczTransportTest,
+                                  h) {
+  scoped_refptr<Transport> transport = ReceiveTransport(h);
+
+  TransportListener listener(*transport);
+  TestMessage message = listener.WaitForNextMessage();
+  scoped_refptr<ObjectBase> object;
+  // We nerfed the handle between serialization and sending so this fails.
+  const IpczResult result = transport->DeserializeObject(
+      base::span(message.bytes), base::span(message.handles), object);
+  EXPECT_EQ(result, IPCZ_RESULT_INVALID_ARGUMENT);
+  TestMessage(kGotInvalid).Transmit(*transport);
+  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h));
+}
+
+TEST_F(MojoIpczTransportTest, InvalidHandle) {
+  RunTestClientWithController("InvalidHandleClient", [&](ClientController& c) {
+    scoped_refptr<Transport> transport =
+        CreateAndSendTransport(c.pipe(), c.process());
+
+    TransportListener listener(*transport);
+    auto region = base::UnsafeSharedMemoryRegion::Create(kGotInvalid.size());
+    auto fake_buffer = SharedBuffer::MakeForRegion(std::move(region));
+    size_t num_bytes = 0;
+    size_t num_handles = 0;
+    TestMessage message;
+    message.handles.resize(num_handles);
+    EXPECT_EQ(IPCZ_RESULT_RESOURCE_EXHAUSTED,
+              transport->SerializeObject(*fake_buffer, message.bytes.data(),
+                                         &num_bytes, message.handles.data(),
+                                         &num_handles));
+    message.bytes.resize(num_bytes);
+    EXPECT_EQ(IPCZ_RESULT_OK,
+              transport->SerializeObject(*fake_buffer, message.bytes.data(),
+                                         &num_bytes, message.handles.data(),
+                                         &num_handles));
+    // Nerf the handle.
+    uint8_t* handle_ptr =
+        base::span(message.bytes)
+            .subspan(Transport::FirstHandleOffsetForTesting(), sizeof(uint32_t))
+            .data();
+    *reinterpret_cast<uint32_t*>(handle_ptr) = 0x12345678u;
+    // Also close the region in the parent.
+    ::CloseHandle(fake_buffer->region().GetPlatformHandle());
+    message.Transmit(*transport);
+    EXPECT_EQ(kGotInvalid, listener.WaitForNextMessage().as_string());
+    listener.WaitForDisconnect();
+  });
+}
+#endif  // BUILDFLAG(IS_WIN)
+
 }  // namespace
 }  // namespace mojo::core::ipcz_driver
diff --git a/mojo/public/mojom/base/BUILD.gn b/mojo/public/mojom/base/BUILD.gn
index b87af89..4b72a2e3d 100644
--- a/mojo/public/mojom/base/BUILD.gn
+++ b/mojo/public/mojom/base/BUILD.gn
@@ -647,6 +647,19 @@
       ]
     },
   ]
+
+  ts_typemaps = [
+    {
+      types = [
+        {
+          mojom = "mojo_base.mojom.JSTime"
+          ts = "Date"
+          converter = "JsTimeConverter"
+          import = "time_converters.js"
+        },
+      ]
+    },
+  ]
 }
 
 mojom_component("protobuf_support") {
diff --git a/mojo/public/mojom/base/time_converters.ts b/mojo/public/mojom/base/time_converters.ts
new file mode 100644
index 0000000..e862dfb4
--- /dev/null
+++ b/mojo/public/mojom/base/time_converters.ts
@@ -0,0 +1,16 @@
+// 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 type {JSTimeDataView, JSTimeTypeMapper} from './time.mojom-converters.js';
+
+export class JsTimeConverter implements JSTimeTypeMapper<Date> {
+  // Encoding
+  msec(date: Date): number {
+    return date.valueOf();
+  }
+
+  // Decoding
+  convert(view: JSTimeDataView): Date {
+    return new Date(view.msec());
+  }
+}
diff --git a/net/http/http_stream_pool_attempt_manager.cc b/net/http/http_stream_pool_attempt_manager.cc
index eef9322f..20b9fea 100644
--- a/net/http/http_stream_pool_attempt_manager.cc
+++ b/net/http/http_stream_pool_attempt_manager.cc
@@ -750,6 +750,15 @@
   CHECK(!quic_task_result_.has_value());
   quic_task_result_ = rv;
   net_error_details_ = std::move(details);
+
+  // Record completion time only when QuicTask actually attempted QUIC.
+  if (rv != ERR_DNS_NO_MATCHING_SUPPORTED_ALPN) {
+    base::UmaHistogramTimes(
+        base::StrCat({"Net.HttpStreamPool.QuicTaskTime.",
+                      rv == OK ? "Success" : "Failure"}),
+        base::TimeTicks::Now() - quic_task_->attempt_start_time());
+  }
+
   quic_task_.reset();
 
   net_log().AddEvent(
diff --git a/net/http/http_stream_pool_quic_task.cc b/net/http/http_stream_pool_quic_task.cc
index 5c9e989b..2d0a756 100644
--- a/net/http/http_stream_pool_quic_task.cc
+++ b/net/http/http_stream_pool_quic_task.cc
@@ -99,6 +99,9 @@
   net_log_.AddEvent(NetLogEventType::HTTP_STREAM_POOL_QUIC_ATTEMPT_START,
                     [&] { return quic_endpoint->ToValue(); });
 
+  CHECK(attempt_start_time_.is_null());
+  attempt_start_time_ = base::TimeTicks::Now();
+
   session_attempt_ = quic_session_pool()->CreateSessionAttempt(
       this, GetKey().session_key(), std::move(*quic_endpoint),
       cert_verify_flags, dns_resolution_start_time, dns_resolution_end_time,
diff --git a/net/http/http_stream_pool_quic_task.h b/net/http/http_stream_pool_quic_task.h
index e8f5286..4600b97 100644
--- a/net/http/http_stream_pool_quic_task.h
+++ b/net/http/http_stream_pool_quic_task.h
@@ -51,6 +51,10 @@
   // any. Never returns ERR_IO_PENDING.
   std::optional<int> start_result() const { return start_result_; }
 
+  // Returns the start time of a session attempt. Maybe null when no attempt is
+  // made.
+  base::TimeTicks attempt_start_time() const { return attempt_start_time_; }
+
  private:
   const HttpStreamKey& stream_key() const;
 
@@ -78,6 +82,7 @@
 
   // TODO(crbug.com/346835898): Support multiple attempts.
   std::unique_ptr<QuicSessionAttempt> session_attempt_;
+  base::TimeTicks attempt_start_time_;
 
   base::WeakPtrFactory<QuicTask> weak_ptr_factory_{this};
 };
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h
index b78b476..b25fdbb 100644
--- a/net/log/net_log_event_type_list.h
+++ b/net/log/net_log_event_type_list.h
@@ -887,6 +887,15 @@
 //   }
 EVENT_TYPE(TRANSPORT_CONNECT_JOB_CONNECT_ATTEMPT)
 
+// This event is logged whenever the SSLConnectJob attempts a
+// SSLClientSocket::Connect().
+//
+//   {
+//     "ech_enabled": <True when ECH is enabled>,
+//     "ech_config_list": <The binary representation of ECH config list>,
+//   }
+EVENT_TYPE(SSL_CONNECT_JOB_SSL_CONNECT)
+
 // ------------------------------------------------------------------------
 // ClientSocketPoolBaseHelper
 // ------------------------------------------------------------------------
diff --git a/net/socket/ssl_connect_job.cc b/net/socket/ssl_connect_job.cc
index dfce490..90f555f8 100644
--- a/net/socket/ssl_connect_job.cc
+++ b/net/socket/ssl_connect_job.cc
@@ -368,6 +368,12 @@
     }
   }
 
+  net_log().AddEvent(NetLogEventType::SSL_CONNECT_JOB_SSL_CONNECT, [&] {
+    base::Value::Dict dict;
+    dict.Set("ech_enabled", ssl_client_context()->config().ech_enabled);
+    dict.Set("ech_config_list", NetLogBinaryValue(ssl_config.ech_config_list));
+    return dict;
+  });
   ssl_socket_ = client_socket_factory()->CreateSSLClientSocket(
       ssl_client_context(), std::move(nested_socket_), params_->host_and_port(),
       ssl_config);
diff --git a/pdf/mojom/pdf.mojom b/pdf/mojom/pdf.mojom
index 80f7145b..6ca1cbc2 100644
--- a/pdf/mojom/pdf.mojom
+++ b/pdf/mojom/pdf.mojom
@@ -38,6 +38,10 @@
   // Get the text contained on the given page of the PDF. `page_index` should be
   // the range [0, # of pages).
   GetPageText(int32 page_index) => (mojo_base.mojom.String16 text);
+
+  // Returns the index of the most visible page. If no page is visible,
+  // returns nullopt.
+  GetMostVisiblePageIndex() => (uint32? page_index);
 };
 
 // Browser-side interface used by PDF renderers.
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 2c8da49d..b5628eb8 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -1528,6 +1528,16 @@
                           page_count);
 }
 
+void PdfViewWebPlugin::GetMostVisiblePageIndex(
+    GetMostVisiblePageIndexCallback callback) {
+  auto page_index = engine_->GetMostVisiblePage();
+  if (page_index < 0) {
+    std::move(callback).Run(std::nullopt);
+    return;
+  }
+  std::move(callback).Run(page_index);
+}
+
 void PdfViewWebPlugin::GetPageText(int32_t page_index,
                                    GetPageTextCallback callback) {
   if (page_index < 0 || page_index >= engine_->GetNumberOfPages()) {
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index 0d01a56..2f1cfea 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -395,6 +395,7 @@
                           const gfx::PointF& extent) override;
   void GetPdfBytes(uint32_t size_limit, GetPdfBytesCallback callback) override;
   void GetPageText(int32_t page_index, GetPageTextCallback callback) override;
+  void GetMostVisiblePageIndex(GetMostVisiblePageIndexCallback callback) override;
 
   // UrlLoader::Client:
   bool IsValid() const override;
diff --git a/pdf/pdfium/pdfium_page.cc b/pdf/pdfium/pdfium_page.cc
index 3daf6097..72150ac 100644
--- a/pdf/pdfium/pdfium_page.cc
+++ b/pdf/pdfium/pdfium_page.cc
@@ -79,6 +79,21 @@
   kRotate270 = 3,
 };
 
+std::optional<Rotation> GetRotationFromRawValue(int rotation) {
+  switch (rotation) {
+    case 0:
+      return Rotation::kRotate0;
+    case 1:
+      return Rotation::kRotate90;
+    case 2:
+      return Rotation::kRotate180;
+    case 3:
+      return Rotation::kRotate270;
+    default:
+      return std::nullopt;
+  }
+}
+
 gfx::RectF FloatPageRectToPixelRect(FPDF_PAGE page, const gfx::RectF& input) {
   int output_width = FPDF_GetPageWidthF(page);
   int output_height = FPDF_GetPageHeightF(page);
@@ -746,9 +761,14 @@
     return gfx::RectF();
   }
 
+  std::optional<Rotation> rotation =
+      GetRotationFromRawValue(FPDFPage_GetRotation(page));
+  if (!rotation.has_value()) {
+    return gfx::RectF();
+  }
+
   // Page width and height are already swapped based on page rotation.
   gfx::SizeF page_size(FPDF_GetPageWidthF(page), FPDF_GetPageHeightF(page));
-  Rotation rotation = static_cast<Rotation>(FPDFPage_GetRotation(page));
 
   // Start with bounds with the left and bottom values at the max possible
   // bounds and the right and top values at the min possible bounds. Bounds are
@@ -779,10 +799,10 @@
   }
 
   gfx::RectF bounding_box =
-      GetRotatedRectF(rotation, page_size, largest_bounds);
+      GetRotatedRectF(rotation.value(), page_size, largest_bounds);
 
   gfx::RectF effective_crop_box =
-      GetEffectiveCropBox(page, rotation, page_size);
+      GetEffectiveCropBox(page, rotation.value(), page_size);
 
   // If the bounding box is empty, default to the effective crop box.
   if (bounding_box.IsEmpty()) {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index b8dc1a5..fdaa6ee3 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -8511,6 +8511,21 @@
             ]
         }
     ],
+    "DozeModePowerScheduler": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "EnableDozeModePowerScheduler"
+                    ]
+                }
+            ]
+        }
+    ],
     "EagerPrefetchBlockUntilHeadDifferentTimeoutsRetrospective": [
         {
             "platforms": [
@@ -17825,6 +17840,26 @@
             ]
         }
     ],
+    "PrivacySandboxEqualizedPromptButtons": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PrivacySandboxEqualizedPromptButtons"
+                    ]
+                }
+            ]
+        }
+    ],
     "PrivacySandboxInternalsDevUI": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index c10f5e3..c289b30 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit c10f5e3fbf61e024ed298700dbc0b5faf888ec5f
+Subproject commit c289b30f332d55bf0156d7122dac00f1aebe56e0
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index c732cd1..23ddc67 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -1106,6 +1106,10 @@
              "HiddenSelectionBounds",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kIgnoreInputWhileHidden,
+             "IgnoreInputWhileHidden",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 BASE_FEATURE(kImageLoadingPrioritizationFix,
              "ImageLoadingPrioritizationFix",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/third_party/blink/common/storage_key/storage_key.cc b/third_party/blink/common/storage_key/storage_key.cc
index a77ff35..2d2a5d3 100644
--- a/third_party/blink/common/storage_key/storage_key.cc
+++ b/third_party/blink/common/storage_key/storage_key.cc
@@ -8,7 +8,6 @@
 #include <ostream>
 #include <string>
 #include <string_view>
-#include <tuple>
 
 #include "base/feature_list.h"
 #include "base/ranges/algorithm.h"
@@ -852,24 +851,6 @@
              other.top_level_site_if_third_party_enabled_;
 }
 
-bool operator==(const StorageKey& lhs, const StorageKey& rhs) {
-  return std::tie(lhs.origin_, lhs.top_level_site_, lhs.nonce_,
-                  lhs.ancestor_chain_bit_) ==
-         std::tie(rhs.origin_, rhs.top_level_site_, rhs.nonce_,
-                  rhs.ancestor_chain_bit_);
-}
-
-bool operator!=(const StorageKey& lhs, const StorageKey& rhs) {
-  return !(lhs == rhs);
-}
-
-bool operator<(const StorageKey& lhs, const StorageKey& rhs) {
-  return std::tie(lhs.origin_, lhs.top_level_site_, lhs.nonce_,
-                  lhs.ancestor_chain_bit_) <
-         std::tie(rhs.origin_, rhs.top_level_site_, rhs.nonce_,
-                  rhs.ancestor_chain_bit_);
-}
-
 std::ostream& operator<<(std::ostream& ostream, const StorageKey& sk) {
   return ostream << sk.GetDebugString();
 }
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 0286036..e6e8269 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -640,6 +640,12 @@
 // of https://crbug.com/1441243.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kHiddenSelectionBounds);
 
+// When enabled all input arriving will be ignored, and the dispatcher will be
+// notified that the event was not consumed. With the exception of when there
+// is an attached Dev Tools session, during which input will be dispatched even
+// if we are hidden.
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kIgnoreInputWhileHidden);
+
 // If enabled, a fix for image loading prioritization based on visibility is
 // applied. See https://crbug.com/1369823.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kImageLoadingPrioritizationFix);
diff --git a/third_party/blink/public/common/storage_key/storage_key.h b/third_party/blink/public/common/storage_key/storage_key.h
index 6de0d5f..aeb3a37 100644
--- a/third_party/blink/public/common/storage_key/storage_key.h
+++ b/third_party/blink/public/common/storage_key/storage_key.h
@@ -9,6 +9,7 @@
 #include <optional>
 #include <string>
 #include <string_view>
+#include <tuple>
 
 #include "base/unguessable_token.h"
 #include "net/base/isolation_info.h"
@@ -297,12 +298,18 @@
 
   // (7B) Operators.
   // Note that not all must be friends, but all are to consolidate the header.
-  BLINK_COMMON_EXPORT
-  friend bool operator==(const StorageKey& lhs, const StorageKey& rhs);
-  BLINK_COMMON_EXPORT
-  friend bool operator!=(const StorageKey& lhs, const StorageKey& rhs);
-  BLINK_COMMON_EXPORT
-  friend bool operator<(const StorageKey& lhs, const StorageKey& rhs);
+  friend bool operator==(const StorageKey& lhs, const StorageKey& rhs) {
+    return std::tie(lhs.origin_, lhs.top_level_site_, lhs.nonce_,
+                    lhs.ancestor_chain_bit_) ==
+           std::tie(rhs.origin_, rhs.top_level_site_, rhs.nonce_,
+                    rhs.ancestor_chain_bit_);
+  }
+  friend auto operator<=>(const StorageKey& lhs, const StorageKey& rhs) {
+    return std::tie(lhs.origin_, lhs.top_level_site_, lhs.nonce_,
+                    lhs.ancestor_chain_bit_) <=>
+           std::tie(rhs.origin_, rhs.top_level_site_, rhs.nonce_,
+                    rhs.ancestor_chain_bit_);
+  }
   BLINK_COMMON_EXPORT
   friend std::ostream& operator<<(std::ostream& ostream, const StorageKey& sk);
 
diff --git a/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom b/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom
index 943fad0..44033ca 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom
@@ -871,6 +871,7 @@
     kMasonryFill = 812,
     kMasonryDirection = 813,
     kMasonryFlow = 814,
+    kMasonryAutoTracks = 815,
 
     // 1. Add new features above this line (don't change the assigned numbers of
     //    the existing items).
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 391a5c3..ae9b152 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -3807,6 +3807,21 @@
       invalidate: ["paint"],
     },
     {
+      name: "masonry-auto-tracks",
+      property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
+      field_group: "*",
+      field_template: "external",
+      include_paths: ["third_party/blink/renderer/core/style/grid_track_list.h"],
+      default_value: "NGGridTrackList(GridTrackSize(Length::Auto()))",
+      type_name: "NGGridTrackList",
+      converter: "ConvertGridTrackSizeList",
+      keywords: ["auto", "min-content", "max-content"],
+      typedom_types: ["Keyword", "Length", "Percentage", "Flex"],
+      separator: " ",
+      invalidate: ["layout", "paint"],
+      runtime_flag: "CSSMasonryLayout",
+    },
+    {
       name: "masonry-direction",
       property_methods: ["CSSValueFromComputedStyleInternal"],
       field_group: "*",
diff --git a/third_party/blink/renderer/core/css/css_property_equality.cc b/third_party/blink/renderer/core/css/css_property_equality.cc
index afaceac..602bf07 100644
--- a/third_party/blink/renderer/core/css/css_property_equality.cc
+++ b/third_party/blink/renderer/core/css/css_property_equality.cc
@@ -517,6 +517,8 @@
       return a.MarkerStartResource() == b.MarkerStartResource();
     case CSSPropertyID::kMaskType:
       return a.MaskType() == b.MaskType();
+    case CSSPropertyID::kMasonryAutoTracks:
+      return a.MasonryAutoTracks() == b.MasonryAutoTracks();
     case CSSPropertyID::kMasonryDirection:
       return a.MasonryDirection() == b.MasonryDirection();
     case CSSPropertyID::kMasonryFill:
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index 9add48d..a5dc42d 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1236,6 +1236,11 @@
     // luminance
     "match-source",
 
+    // masonry-auto-tracks
+    // min-content
+    // max-content
+    // auto
+
     // masonry-direction
     // row
     // row-reverse
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index 9b3a4b7..f3a70df 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -1819,13 +1819,11 @@
 }
 
 CSSValue* ComputedStyleUtils::ValueForGridAutoTrackList(
-    GridTrackSizingDirection track_direction,
+    const NGGridTrackList& auto_track_list,
     const LayoutObject* layout_object,
     const ComputedStyle& style) {
   CSSValueList* list = CSSValueList::CreateSpaceSeparated();
-  const NGGridTrackList& auto_track_list = track_direction == kForColumns
-                                               ? style.GridAutoColumns()
-                                               : style.GridAutoRows();
+
   if (auto_track_list.RepeaterCount() == 1) {
     for (wtf_size_t i = 0; i < auto_track_list.RepeatSize(0); ++i) {
       list->Append(*SpecifiedValueForGridTrackSize(
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.h b/third_party/blink/renderer/core/css/properties/computed_style_utils.h
index dd9ca2ba..afbabfc 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.h
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.h
@@ -151,7 +151,7 @@
   static CSSValue* ValueForFontPalette(const ComputedStyle&);
   static CSSValue* SpecifiedValueForGridTrackSize(const GridTrackSize&,
                                                   const ComputedStyle&);
-  static CSSValue* ValueForGridAutoTrackList(GridTrackSizingDirection,
+  static CSSValue* ValueForGridAutoTrackList(const NGGridTrackList&,
                                              const LayoutObject*,
                                              const ComputedStyle&);
   static CSSValue* ValueForGridTrackList(GridTrackSizingDirection,
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index e6c2c0b..75221109 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -4342,7 +4342,7 @@
     const LayoutObject* layout_object,
     bool allow_visited_style,
     CSSValuePhase value_phase) const {
-  return ComputedStyleUtils::ValueForGridAutoTrackList(kForColumns,
+  return ComputedStyleUtils::ValueForGridAutoTrackList(style.GridAutoColumns(),
                                                        layout_object, style);
 }
 
@@ -4430,8 +4430,8 @@
     const LayoutObject* layout_object,
     bool allow_visited_style,
     CSSValuePhase value_phase) const {
-  return ComputedStyleUtils::ValueForGridAutoTrackList(kForRows, layout_object,
-                                                       style);
+  return ComputedStyleUtils::ValueForGridAutoTrackList(style.GridAutoRows(),
+                                                       layout_object, style);
 }
 
 const CSSValue* GridAutoRows::InitialValue() const {
@@ -6100,6 +6100,23 @@
   return CSSIdentifierValue::Create(style.MaskType());
 }
 
+const CSSValue* MasonryAutoTracks::ParseSingleValue(
+    CSSParserTokenStream& stream,
+    const CSSParserContext& context,
+    const CSSParserLocalContext&) const {
+  return css_parsing_utils::ConsumeGridTrackList(
+      stream, context, css_parsing_utils::TrackListType::kGridAuto);
+}
+
+const CSSValue* MasonryAutoTracks::CSSValueFromComputedStyleInternal(
+    const ComputedStyle& style,
+    const LayoutObject* layout_object,
+    bool allow_visited_style,
+    CSSValuePhase value_phase) const {
+  return ComputedStyleUtils::ValueForGridAutoTrackList(
+      style.MasonryAutoTracks(), layout_object, style);
+}
+
 const CSSValue* MasonryDirection::CSSValueFromComputedStyleInternal(
     const ComputedStyle& style,
     const LayoutObject*,
diff --git a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
index 404be4b..d9a73c0 100644
--- a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
@@ -306,8 +306,11 @@
 
 void WebDevToolsAgentImpl::AttachSession(DevToolsSession* session,
                                          bool restore) {
-  if (!network_agents_.size())
+  if (!network_agents_.size()) {
     Thread::Current()->AddTaskObserver(this);
+    web_local_frame_impl_->OnDevToolsSessionConnectionChanged(
+        /*attached=*/true);
+  }
 
   InspectedFrames* inspected_frames = inspected_frames_.Get();
   v8::Isolate* isolate =
@@ -460,8 +463,11 @@
   network_agents_.erase(session);
   page_agents_.erase(session);
   overlay_agents_.erase(session);
-  if (!network_agents_.size())
+  if (!network_agents_.size()) {
     Thread::Current()->RemoveTaskObserver(this);
+    web_local_frame_impl_->OnDevToolsSessionConnectionChanged(
+        /*attached=*/false);
+  }
 }
 
 void WebDevToolsAgentImpl::InspectElement(
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 1f39c8d..b0c4780 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
@@ -1564,6 +1564,10 @@
   LayerTreeHost()->RequestNewLocalSurfaceId();
 }
 
+void WebFrameWidgetImpl::OnDevToolsSessionConnectionChanged(bool attached) {
+  widget_base_->OnDevToolsSessionConnectionChanged(attached);
+}
+
 WebInputMethodController*
 WebFrameWidgetImpl::GetActiveWebInputMethodController() const {
   WebLocalFrameImpl* local_frame =
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 07fa5e65..c9a9553 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
@@ -740,6 +740,8 @@
   // Request a new `viz::LocalSurfaceId` on the compositor thread.
   void RequestNewLocalSurfaceId();
 
+  void OnDevToolsSessionConnectionChanged(bool attached);
+
  protected:
   // WidgetBaseClient overrides:
   void ScheduleAnimation() override;
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index d2f653fbf..82bc280 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -3204,6 +3204,12 @@
   return dev_tools_agent_.Get();
 }
 
+void WebLocalFrameImpl::OnDevToolsSessionConnectionChanged(bool attached) {
+  if (frame_widget_) {
+    frame_widget_->OnDevToolsSessionConnectionChanged(attached);
+  }
+}
+
 void WebLocalFrameImpl::WasHidden() {
   if (frame_)
     frame_->WasHidden();
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index 237de99..cbbfad6 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -505,6 +505,7 @@
   void SendOrientationChangeEvent();
 
   WebDevToolsAgentImpl* DevToolsAgentImpl(bool create_if_necessary);
+  void OnDevToolsSessionConnectionChanged(bool attached);
 
   // Instructs devtools to pause loading of the frame as soon as it's shown
   // until explicit command from the devtools client. May only be called on a
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc
index bcebda6..0a74f45 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -730,7 +730,7 @@
     // FIXME: ignore for the moment.
     //
   } else if (params.name == html_names::kSelectedcontentelementAttr) {
-    if (RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
+    if (RuntimeEnabledFeatures::SelectedcontentelementAttributeEnabled()) {
       HTMLSelectedContentElement* old_selectedcontent =
           DynamicTo<HTMLSelectedContentElement>(
               getElementByIdIncludingDisconnected(*this, params.old_value));
diff --git a/third_party/blink/renderer/core/layout/layout_theme.cc b/third_party/blink/renderer/core/layout/layout_theme.cc
index c663a9b..5cd36bf 100644
--- a/third_party/blink/renderer/core/layout/layout_theme.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme.cc
@@ -441,8 +441,7 @@
       return builder.HasAuthorBackground() || builder.HasAuthorBorder();
 
     case AppearanceValue::kMeter:
-      return RuntimeEnabledFeatures::MeterDevolveAppearanceEnabled() &&
-             (builder.HasAuthorBackground() || builder.HasAuthorBorder());
+      return builder.HasAuthorBackground() || builder.HasAuthorBorder();
 
     case AppearanceValue::kMenulist:
     case AppearanceValue::kSearchField:
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.idl b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.idl
index d405b56..259d8821 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.idl
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.idl
@@ -13,7 +13,7 @@
 
 interface mixin BaseRenderingContext2D {
     // state
-    void save(); // push state on state stack
+    [NoAllocDirectCall] void save(); // push state on state stack
     [NoAllocDirectCall, RaisesException] void restore(); // pop state stack if top state was pushed by save, and restore state
     [MeasureAs=Canvas2DLayers, RuntimeEnabled=Canvas2dLayers, CallWith=ScriptState] void beginLayer(); // push state on state stack and creates bitmap for subsequent draw ops
     [MeasureAs=Canvas2DLayers, RuntimeEnabled=Canvas2dLayersWithOptions, CallWith=ScriptState, RaisesException] void beginLayer(BeginLayerOptions options); // push state on state stack and creates bitmap for subsequent draw ops
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 1f2692fb..2c3044b 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
@@ -8,7 +8,7 @@
     Exposed=PaintWorklet
 ] interface PaintRenderingContext2D {
     // state
-    void save(); // push state on state stack
+    [NoAllocDirectCall] void save(); // push state on state stack
     [NoAllocDirectCall, RaisesException] void restore(); // pop state stack if top state was pushed by save, and restore state
     [MeasureAs=Canvas2DLayers, RuntimeEnabled=Canvas2dLayers, CallWith=ScriptState] void beginLayer(); // push state on state stack and creates bitmap for subsequent draw ops
     [MeasureAs=Canvas2DLayers, RuntimeEnabled=Canvas2dLayersWithOptions, CallWith=ScriptState, RaisesException] void beginLayer(BeginLayerOptions options); // push state on state stack and creates bitmap for subsequent draw ops
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 605c8108..d63c8ec 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -2026,6 +2026,7 @@
     "testing/video_frame_utils.cc",
     "testing/video_frame_utils.h",
     "webrtc/testing/mock_webrtc_video_frame_adapter_shared_resources.h",
+    "widget/compositing/test/stub_widget_base_client.h",
   ]
 
   # fuzzed_data_provider may not work with a custom toolchain.
@@ -2364,8 +2365,11 @@
     "widget/input/input_event_prediction_unittest.cc",
     "widget/input/input_handler_proxy_unittest.cc",
     "widget/input/main_thread_event_queue_unittest.cc",
+    "widget/input/mock_input_handler_proxy.h",
+    "widget/input/mock_input_handler_proxy_client.h",
     "widget/input/prediction/filter_factory_unittests.cc",
     "widget/input/scroll_predictor_unittest.cc",
+    "widget/input/widget_input_handler_manager_unittest.cc",
   ]
 
   if (rtc_use_h265) {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index b838d68..0d0ac60 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2781,12 +2781,6 @@
       name: "MetaRefreshNoFractional",
       status: "stable",
     },
-    {
-      // If enabled, the <meter> widget appearance will devolve based on user
-      // declared styles as defined in the css-ui-4 spec
-      name: "MeterDevolveAppearance",
-      status: "stable",
-    },
     // This is enabled by default on Windows only. The only part that's
     // "experimental" is the support on other platforms.
     {
diff --git a/third_party/blink/renderer/platform/storage/blink_storage_key.cc b/third_party/blink/renderer/platform/storage/blink_storage_key.cc
index 61c03dc9..37af1da5 100644
--- a/third_party/blink/renderer/platform/storage/blink_storage_key.cc
+++ b/third_party/blink/renderer/platform/storage/blink_storage_key.cc
@@ -203,20 +203,6 @@
              other.top_level_site_if_third_party_enabled_;
 }
 
-bool operator==(const BlinkStorageKey& lhs, const BlinkStorageKey& rhs) {
-  DCHECK(lhs.origin_);
-  DCHECK(rhs.origin_);
-
-  return lhs.origin_->IsSameOriginWith(rhs.origin_.get()) &&
-         lhs.nonce_ == rhs.nonce_ &&
-         lhs.top_level_site_ == rhs.top_level_site_ &&
-         lhs.ancestor_chain_bit_ == rhs.ancestor_chain_bit_;
-}
-
-bool operator!=(const BlinkStorageKey& lhs, const BlinkStorageKey& rhs) {
-  return !(lhs == rhs);
-}
-
 std::ostream& operator<<(std::ostream& ostream, const BlinkStorageKey& key) {
   return ostream << key.ToDebugString();
 }
diff --git a/third_party/blink/renderer/platform/storage/blink_storage_key.h b/third_party/blink/renderer/platform/storage/blink_storage_key.h
index c20099c..1f1c566 100644
--- a/third_party/blink/renderer/platform/storage/blink_storage_key.h
+++ b/third_party/blink/renderer/platform/storage/blink_storage_key.h
@@ -179,13 +179,17 @@
 
   // (7B) Operators.
   // Note that not all must be friends, but all are to consolidate the header.
-  PLATFORM_EXPORT
   friend bool operator==(const BlinkStorageKey& lhs,
-                         const BlinkStorageKey& rhs);
-  PLATFORM_EXPORT
-  friend bool operator!=(const BlinkStorageKey& lhs,
-                         const BlinkStorageKey& rhs);
-  // If there were a need for an operator< it would go here.
+                         const BlinkStorageKey& rhs) {
+    DCHECK(lhs.origin_);
+    DCHECK(rhs.origin_);
+
+    return lhs.origin_->IsSameOriginWith(rhs.origin_.get()) &&
+           lhs.nonce_ == rhs.nonce_ &&
+           lhs.top_level_site_ == rhs.top_level_site_ &&
+           lhs.ancestor_chain_bit_ == rhs.ancestor_chain_bit_;
+  }
+  // If there were a need for `operator<=>()` it would go here.
   PLATFORM_EXPORT
   friend std::ostream& operator<<(std::ostream& ostream,
                                   const BlinkStorageKey& sk);
diff --git a/third_party/blink/renderer/platform/widget/compositing/test/stub_widget_base_client.h b/third_party/blink/renderer/platform/widget/compositing/test/stub_widget_base_client.h
new file mode 100644
index 0000000..5e2ef18
--- /dev/null
+++ b/third_party/blink/renderer/platform/widget/compositing/test/stub_widget_base_client.h
@@ -0,0 +1,61 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_COMPOSITING_TEST_STUB_WIDGET_BASE_CLIENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_COMPOSITING_TEST_STUB_WIDGET_BASE_CLIENT_H_
+
+#include "cc/input/overscroll_behavior.h"
+#include "cc/trees/layer_tree_frame_sink.h"
+#include "components/viz/common/frame_sinks/begin_frame_args.h"
+#include "third_party/blink/public/common/input/web_gesture_event.h"
+#include "third_party/blink/public/common/input/web_mouse_event.h"
+#include "third_party/blink/public/common/widget/visual_properties.h"
+#include "third_party/blink/public/mojom/input/input_handler.mojom-blink.h"
+#include "third_party/blink/public/platform/web_input_event_result.h"
+#include "third_party/blink/public/web/web_lifecycle_update.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/widget/widget_base_client.h"
+#include "ui/display/screen_infos.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace blink {
+class StubWidgetBaseClient : public WidgetBaseClient {
+ public:
+  void OnCommitRequested() override {}
+  void BeginMainFrame(const viz::BeginFrameArgs& args) override {}
+  void UpdateLifecycle(WebLifecycleUpdate, DocumentUpdateReason) override {}
+  std::unique_ptr<cc::LayerTreeFrameSink> AllocateNewLayerTreeFrameSink()
+      override {
+    return nullptr;
+  }
+  KURL GetURLForDebugTrace() override { return {}; }
+  WebInputEventResult DispatchBufferedTouchEvents() override {
+    return WebInputEventResult::kNotHandled;
+  }
+  WebInputEventResult HandleInputEvent(const WebCoalescedInputEvent&) override {
+    return WebInputEventResult::kNotHandled;
+  }
+  bool SupportsBufferedTouchEvents() override { return false; }
+  void WillHandleGestureEvent(const WebGestureEvent&, bool* suppress) override {
+  }
+  void WillHandleMouseEvent(const WebMouseEvent&) override {}
+  void ObserveGestureEventAndResult(const WebGestureEvent&,
+                                    const gfx::Vector2dF&,
+                                    const cc::OverscrollBehavior&,
+                                    bool) override {}
+  void FocusChanged(mojom::blink::FocusState) override {}
+  void UpdateVisualProperties(
+      const VisualProperties& visual_properties) override {}
+  const display::ScreenInfos& GetOriginalScreenInfos() override {
+    return screen_infos_;
+  }
+  gfx::Rect ViewportVisibleRect() override { return gfx::Rect(); }
+
+ private:
+  display::ScreenInfos screen_infos_;
+};
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_COMPOSITING_TEST_STUB_WIDGET_BASE_CLIENT_H_
diff --git a/third_party/blink/renderer/platform/widget/compositing/widget_compositor_unittest.cc b/third_party/blink/renderer/platform/widget/compositing/widget_compositor_unittest.cc
index 8916fdf..2503b04d 100644
--- a/third_party/blink/renderer/platform/widget/compositing/widget_compositor_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/widget_compositor_unittest.cc
@@ -14,48 +14,13 @@
 #include "cc/trees/layer_tree_host.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
+#include "third_party/blink/renderer/platform/widget/compositing/test/stub_widget_base_client.h"
 #include "third_party/blink/renderer/platform/widget/widget_base.h"
 #include "third_party/blink/renderer/platform/widget/widget_base_client.h"
 #include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
 
 namespace blink {
 
-class StubWidgetBaseClient : public WidgetBaseClient {
- public:
-  void OnCommitRequested() override {}
-  void BeginMainFrame(const viz::BeginFrameArgs& args) override {}
-  void UpdateLifecycle(WebLifecycleUpdate, DocumentUpdateReason) override {}
-  std::unique_ptr<cc::LayerTreeFrameSink> AllocateNewLayerTreeFrameSink()
-      override {
-    return nullptr;
-  }
-  KURL GetURLForDebugTrace() override { return {}; }
-  WebInputEventResult DispatchBufferedTouchEvents() override {
-    return WebInputEventResult::kNotHandled;
-  }
-  WebInputEventResult HandleInputEvent(const WebCoalescedInputEvent&) override {
-    return WebInputEventResult::kNotHandled;
-  }
-  bool SupportsBufferedTouchEvents() override { return false; }
-  void WillHandleGestureEvent(const WebGestureEvent&, bool* suppress) override {
-  }
-  void WillHandleMouseEvent(const WebMouseEvent&) override {}
-  void ObserveGestureEventAndResult(const WebGestureEvent&,
-                                    const gfx::Vector2dF&,
-                                    const cc::OverscrollBehavior&,
-                                    bool) override {}
-  void FocusChanged(mojom::blink::FocusState) override {}
-  void UpdateVisualProperties(
-      const VisualProperties& visual_properties) override {}
-  const display::ScreenInfos& GetOriginalScreenInfos() override {
-    return screen_infos_;
-  }
-  gfx::Rect ViewportVisibleRect() override { return gfx::Rect(); }
-
- private:
-  display::ScreenInfos screen_infos_;
-};
-
 class FakeWidgetCompositor : public WidgetCompositor {
  public:
   static scoped_refptr<FakeWidgetCompositor> Create(
diff --git a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.h b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.h
index 7f29233..5f63586 100644
--- a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.h
+++ b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.h
@@ -154,7 +154,8 @@
       std::unique_ptr<DidOverscrollParams>,
       const blink::WebInputEventAttribution&,
       std::unique_ptr<cc::EventMetrics> metrics)>;
-  void HandleInputEventWithLatencyInfo(
+  // Virtual for mocking in tests.
+  virtual void HandleInputEventWithLatencyInfo(
       std::unique_ptr<blink::WebCoalescedInputEvent> event,
       std::unique_ptr<cc::EventMetrics> metrics,
       EventDispositionCallback callback);
diff --git a/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc b/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc
index 7fdda67..69e0414 100644
--- a/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc
@@ -9,7 +9,6 @@
 #include "base/containers/circular_deque.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
-#include "base/lazy_instance.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
@@ -19,13 +18,8 @@
 #include "base/types/optional_ref.h"
 #include "build/build_config.h"
 #include "cc/base/features.h"
-#include "cc/input/browser_controls_offset_tags_info.h"
 #include "cc/input/main_thread_scrolling_reason.h"
-#include "cc/test/fake_impl_task_runner_provider.h"
-#include "cc/test/fake_layer_tree_host_impl.h"
-#include "cc/test/test_task_graph_runner.h"
-#include "cc/trees/latency_info_swap_promise_monitor.h"
-#include "cc/trees/layer_tree_settings.h"
+#include "cc/test/mock_input_handler.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
@@ -39,6 +33,7 @@
 #include "third_party/blink/renderer/platform/widget/input/event_with_callback.h"
 #include "third_party/blink/renderer/platform/widget/input/input_handler_proxy.h"
 #include "third_party/blink/renderer/platform/widget/input/input_handler_proxy_client.h"
+#include "third_party/blink/renderer/platform/widget/input/mock_input_handler_proxy_client.h"
 #include "third_party/blink/renderer/platform/widget/input/scroll_predictor.h"
 #include "ui/events/types/scroll_input_type.h"
 #include "ui/gfx/geometry/size_f.h"
@@ -87,179 +82,6 @@
   return gesture;
 }
 
-class FakeCompositorDelegateForInput : public cc::CompositorDelegateForInput {
- public:
-  FakeCompositorDelegateForInput()
-      : host_impl_(&task_runner_provider_, &task_graph_runner_) {}
-  void BindToInputHandler(
-      std::unique_ptr<cc::InputDelegateForCompositor> delegate) override {}
-  cc::ScrollTree& GetScrollTree() const override { return scroll_tree_; }
-  bool HasAnimatedScrollbars() const override { return false; }
-  void SetNeedsCommit() override {}
-  void SetNeedsFullViewportRedraw() override {}
-  void SetDeferBeginMainFrame(bool defer_begin_main_frame) const override {}
-  void DidUpdateScrollAnimationCurve() override {}
-  void AccumulateScrollDeltaForTracing(const gfx::Vector2dF& delta) override {}
-  void DidStartPinchZoom() override {}
-  void DidUpdatePinchZoom() override {}
-  void DidEndPinchZoom() override {}
-  void DidStartScroll() override {}
-  void DidEndScroll() override {}
-  void DidMouseLeave() override {}
-  bool IsInHighLatencyMode() const override { return false; }
-  void WillScrollContent(cc::ElementId element_id) override {}
-  void DidScrollContent(cc::ElementId element_id, bool animated) override {}
-  float DeviceScaleFactor() const override { return 0; }
-  float PageScaleFactor() const override { return 0; }
-  gfx::Size VisualDeviceViewportSize() const override { return gfx::Size(); }
-  const cc::LayerTreeSettings& GetSettings() const override {
-    return settings_;
-  }
-  cc::LayerTreeHostImpl& GetImplDeprecated() override { return host_impl_; }
-  const cc::LayerTreeHostImpl& GetImplDeprecated() const override {
-    return host_impl_;
-  }
-  void UpdateBrowserControlsState(
-      cc::BrowserControlsState constraints,
-      cc::BrowserControlsState current,
-      bool animate,
-      base::optional_ref<const cc::BrowserControlsOffsetTagsInfo>
-          offset_tags_info) override {}
-  bool HasScrollLinkedAnimation(cc::ElementId for_scroller) const override {
-    return false;
-  }
-
- private:
-  mutable cc::ScrollTree scroll_tree_;
-  cc::LayerTreeSettings settings_;
-  cc::FakeImplTaskRunnerProvider task_runner_provider_;
-  cc::TestTaskGraphRunner task_graph_runner_;
-  cc::FakeLayerTreeHostImpl host_impl_;
-};
-
-base::LazyInstance<FakeCompositorDelegateForInput>::Leaky
-    g_fake_compositor_delegate = LAZY_INSTANCE_INITIALIZER;
-
-class MockInputHandler : public cc::InputHandler {
- public:
-  MockInputHandler() : cc::InputHandler(g_fake_compositor_delegate.Get()) {}
-  MockInputHandler(const MockInputHandler&) = delete;
-  MockInputHandler& operator=(const MockInputHandler&) = delete;
-
-  ~MockInputHandler() override = default;
-
-  base::WeakPtr<InputHandler> AsWeakPtr() override {
-    return weak_ptr_factory_.GetWeakPtr();
-  }
-
-  MOCK_METHOD2(PinchGestureBegin,
-               void(const gfx::Point& anchor, ui::ScrollInputType type));
-  MOCK_METHOD2(PinchGestureUpdate,
-               void(float magnify_delta, const gfx::Point& anchor));
-  MOCK_METHOD1(PinchGestureEnd, void(const gfx::Point& anchor));
-
-  MOCK_METHOD0(SetNeedsAnimateInput, void());
-
-  MOCK_METHOD2(ScrollBegin,
-               ScrollStatus(cc::ScrollState*, ui::ScrollInputType type));
-  MOCK_METHOD2(RootScrollBegin,
-               ScrollStatus(cc::ScrollState*, ui::ScrollInputType type));
-  MOCK_METHOD2(ScrollUpdate,
-               cc::InputHandlerScrollResult(cc::ScrollState, base::TimeDelta));
-  MOCK_METHOD1(ScrollEnd, void(bool));
-  MOCK_METHOD2(RecordScrollBegin,
-               void(ui::ScrollInputType type,
-                    cc::ScrollBeginThreadState state));
-  MOCK_METHOD1(RecordScrollEnd, void(ui::ScrollInputType type));
-  MOCK_METHOD1(HitTest,
-               cc::PointerResultType(const gfx::PointF& mouse_position));
-  MOCK_METHOD2(MouseDown,
-               cc::InputHandlerPointerResult(const gfx::PointF& mouse_position,
-                                             const bool shift_modifier));
-  MOCK_METHOD1(
-      MouseUp,
-      cc::InputHandlerPointerResult(const gfx::PointF& mouse_position));
-  MOCK_METHOD1(SetIsHandlingTouchSequence, void(bool));
-  void NotifyInputEvent() override {}
-
-  std::unique_ptr<cc::LatencyInfoSwapPromiseMonitor>
-  CreateLatencyInfoSwapPromiseMonitor(ui::LatencyInfo* latency) override {
-    return nullptr;
-  }
-
-  std::unique_ptr<cc::EventsMetricsManager::ScopedMonitor>
-  GetScopedEventMetricsMonitor(
-      cc::EventsMetricsManager::ScopedMonitor::DoneCallback) override {
-    return nullptr;
-  }
-
-  cc::ScrollElasticityHelper* CreateScrollElasticityHelper() override {
-    return nullptr;
-  }
-  void DestroyScrollElasticityHelper() override {}
-
-  bool GetScrollOffsetForLayer(cc::ElementId element_id,
-                               gfx::PointF* offset) override {
-    return false;
-  }
-  bool ScrollLayerTo(cc::ElementId element_id,
-                     const gfx::PointF& offset) override {
-    return false;
-  }
-
-  void BindToClient(cc::InputHandlerClient* client) override {}
-
-  void MouseLeave() override {}
-
-  MOCK_METHOD1(FindFrameElementIdAtPoint, cc::ElementId(const gfx::PointF&));
-
-  cc::InputHandlerPointerResult MouseMoveAt(
-      const gfx::Point& mouse_position) override {
-    return cc::InputHandlerPointerResult();
-  }
-
-  MOCK_CONST_METHOD1(
-      GetEventListenerProperties,
-      cc::EventListenerProperties(cc::EventListenerClass event_class));
-  MOCK_METHOD2(EventListenerTypeForTouchStartOrMoveAt,
-               cc::InputHandler::TouchStartOrMoveEventListenerType(
-                   const gfx::Point& point,
-                   cc::TouchAction* touch_action));
-  MOCK_CONST_METHOD1(HasBlockingWheelEventHandlerAt, bool(const gfx::Point&));
-
-  MOCK_METHOD0(RequestUpdateForSynchronousInputHandler, void());
-  MOCK_METHOD1(SetSynchronousInputHandlerRootScrollOffset,
-               void(const gfx::PointF& root_offset));
-
-  bool IsCurrentlyScrollingViewport() const override {
-    return is_scrolling_root_;
-  }
-  void set_is_scrolling_root(bool is) { is_scrolling_root_ = is; }
-
-  MOCK_METHOD4(GetSnapFlingInfoAndSetAnimatingSnapTarget,
-               bool(const gfx::Vector2dF& current_delta,
-                    const gfx::Vector2dF& natural_displacement,
-                    gfx::PointF* initial_offset,
-                    gfx::PointF* target_offset));
-  MOCK_METHOD1(ScrollEndForSnapFling, void(bool));
-
-  bool ScrollbarScrollIsActive() override { return false; }
-
-  void SetDeferBeginMainFrame(bool defer_begin_main_frame) const override {}
-
-  MOCK_METHOD4(UpdateBrowserControlsState,
-               void(cc::BrowserControlsState constraints,
-                    cc::BrowserControlsState current,
-                    bool animate,
-                    base::optional_ref<const cc::BrowserControlsOffsetTagsInfo>
-                        offset_tags_info));
-
- private:
-  bool is_scrolling_root_ = true;
-
-  base::WeakPtrFactory<MockInputHandler> weak_ptr_factory_{this};
-};
-
 class MockSynchronousInputHandler : public SynchronousInputHandler {
  public:
   MOCK_METHOD6(UpdateRootLayerState,
@@ -271,33 +93,6 @@
                     float max_page_scale_factor));
 };
 
-class MockInputHandlerProxyClient : public InputHandlerProxyClient {
- public:
-  MockInputHandlerProxyClient() {}
-  MockInputHandlerProxyClient(const MockInputHandlerProxyClient&) = delete;
-  MockInputHandlerProxyClient& operator=(const MockInputHandlerProxyClient&) =
-      delete;
-
-  ~MockInputHandlerProxyClient() override {}
-
-  void WillShutdown() override {}
-
-  MOCK_METHOD3(GenerateScrollBeginAndSendToMainThread,
-               void(const WebGestureEvent& update_event,
-                    const WebInputEventAttribution&,
-                    const cc::EventMetrics*));
-
-  MOCK_METHOD5(DidOverscroll,
-               void(const gfx::Vector2dF& accumulated_overscroll,
-                    const gfx::Vector2dF& latest_overscroll_delta,
-                    const gfx::Vector2dF& current_fling_velocity,
-                    const gfx::PointF& causal_event_viewport_point,
-                    const cc::OverscrollBehavior& overscroll_behavior));
-  void DidStartScrollingViewport() override {}
-  MOCK_METHOD1(SetAllowedTouchAction, void(cc::TouchAction touch_action));
-  bool AllowsScrollResampling() override { return true; }
-};
-
 WebTouchPoint CreateWebTouchPoint(WebTouchPoint::State state,
                                   float x,
                                   float y) {
@@ -425,7 +220,7 @@
   void FlingAndSnap();
 
   base::test::SingleThreadTaskEnvironment task_environment_;
-  testing::StrictMock<MockInputHandler> mock_input_handler_;
+  testing::StrictMock<cc::MockInputHandler> mock_input_handler_;
   testing::StrictMock<MockSynchronousInputHandler>
       mock_synchronous_input_handler_;
   std::unique_ptr<TestInputHandlerProxy> input_handler_;
@@ -468,7 +263,7 @@
 
 // This helper forces the CompositorThreadEventQueue to be flushed.
 InputHandlerProxy::EventDisposition HandleInputEventAndFlushEventQueue(
-    testing::StrictMock<MockInputHandler>& mock_input_handler,
+    testing::StrictMock<cc::MockInputHandler>& mock_input_handler,
     TestInputHandlerProxy* input_handler,
     const WebInputEvent& event) {
   EXPECT_CALL(mock_input_handler, SetNeedsAnimateInput())
@@ -597,7 +392,7 @@
 
  protected:
   base::test::SingleThreadTaskEnvironment task_environment_;
-  testing::StrictMock<MockInputHandler> mock_input_handler_;
+  testing::StrictMock<cc::MockInputHandler> mock_input_handler_;
   testing::StrictMock<MockInputHandlerProxyClient> mock_client_;
   TestInputHandlerProxy input_handler_proxy_;
   std::vector<InputHandlerProxy::EventDisposition> event_disposition_recorder_;
@@ -1900,7 +1695,7 @@
 
  protected:
   base::test::SingleThreadTaskEnvironment task_environment_;
-  NiceMock<MockInputHandler> mock_input_handler_;
+  NiceMock<cc::MockInputHandler> mock_input_handler_;
   NiceMock<MockInputHandlerProxyClient> mock_client_;
 
  private:
@@ -2291,7 +2086,7 @@
 
 TEST(SynchronousInputHandlerProxyTest, StartupShutdown) {
   base::test::SingleThreadTaskEnvironment task_environment;
-  testing::StrictMock<MockInputHandler> mock_input_handler;
+  testing::StrictMock<cc::MockInputHandler> mock_input_handler;
   testing::StrictMock<MockInputHandlerProxyClient> mock_client;
   testing::StrictMock<MockSynchronousInputHandler>
       mock_synchronous_input_handler;
@@ -2318,7 +2113,7 @@
 
 TEST(SynchronousInputHandlerProxyTest, UpdateRootLayerState) {
   base::test::SingleThreadTaskEnvironment task_environment;
-  testing::NiceMock<MockInputHandler> mock_input_handler;
+  testing::NiceMock<cc::MockInputHandler> mock_input_handler;
   testing::StrictMock<MockInputHandlerProxyClient> mock_client;
   testing::StrictMock<MockSynchronousInputHandler>
       mock_synchronous_input_handler;
@@ -2342,7 +2137,7 @@
 
 TEST(SynchronousInputHandlerProxyTest, SetOffset) {
   base::test::SingleThreadTaskEnvironment task_environment;
-  testing::NiceMock<MockInputHandler> mock_input_handler;
+  testing::NiceMock<cc::MockInputHandler> mock_input_handler;
   testing::StrictMock<MockInputHandlerProxyClient> mock_client;
   testing::StrictMock<MockSynchronousInputHandler>
       mock_synchronous_input_handler;
diff --git a/third_party/blink/renderer/platform/widget/input/mock_input_handler_proxy.h b/third_party/blink/renderer/platform/widget/input/mock_input_handler_proxy.h
new file mode 100644
index 0000000..48b155c4
--- /dev/null
+++ b/third_party/blink/renderer/platform/widget/input/mock_input_handler_proxy.h
@@ -0,0 +1,32 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_INPUT_MOCK_INPUT_HANDLER_PROXY_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_INPUT_MOCK_INPUT_HANDLER_PROXY_H_
+
+#include <memory>
+
+#include "cc/metrics/event_metrics.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
+#include "third_party/blink/renderer/platform/widget/input/input_handler_proxy.h"
+
+namespace blink::test {
+
+class MockInputHandlerProxy : public InputHandlerProxy {
+ public:
+  MockInputHandlerProxy(cc::InputHandler& input_handler,
+                        InputHandlerProxyClient* client)
+      : InputHandlerProxy(input_handler, client) {}
+  ~MockInputHandlerProxy() override = default;
+
+  MOCK_METHOD3(HandleInputEventWithLatencyInfo,
+               void(std::unique_ptr<blink::WebCoalescedInputEvent> event,
+                    std::unique_ptr<cc::EventMetrics> metrics,
+                    EventDispositionCallback callback));
+};
+
+}  // namespace blink::test
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_INPUT_MOCK_INPUT_HANDLER_PROXY_H_
diff --git a/third_party/blink/renderer/platform/widget/input/mock_input_handler_proxy_client.h b/third_party/blink/renderer/platform/widget/input/mock_input_handler_proxy_client.h
new file mode 100644
index 0000000..34908aa
--- /dev/null
+++ b/third_party/blink/renderer/platform/widget/input/mock_input_handler_proxy_client.h
@@ -0,0 +1,47 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_INPUT_MOCK_INPUT_HANDLER_PROXY_CLIENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_INPUT_MOCK_INPUT_HANDLER_PROXY_CLIENT_H_
+
+#include "cc/input/overscroll_behavior.h"
+#include "cc/input/touch_action.h"
+#include "cc/metrics/event_metrics.h"
+#include "third_party/blink/public/common/input/web_gesture_event.h"
+#include "third_party/blink/public/common/input/web_input_event_attribution.h"
+#include "third_party/blink/renderer/platform/widget/input/input_handler_proxy_client.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace blink::test {
+
+class MockInputHandlerProxyClient : public InputHandlerProxyClient {
+ public:
+  MockInputHandlerProxyClient() = default;
+  MockInputHandlerProxyClient(const MockInputHandlerProxyClient&) = delete;
+  MockInputHandlerProxyClient& operator=(const MockInputHandlerProxyClient&) =
+      delete;
+
+  ~MockInputHandlerProxyClient() override = default;
+
+  void WillShutdown() override {}
+
+  MOCK_METHOD3(GenerateScrollBeginAndSendToMainThread,
+               void(const WebGestureEvent& update_event,
+                    const WebInputEventAttribution&,
+                    const cc::EventMetrics*));
+
+  MOCK_METHOD5(DidOverscroll,
+               void(const gfx::Vector2dF& accumulated_overscroll,
+                    const gfx::Vector2dF& latest_overscroll_delta,
+                    const gfx::Vector2dF& current_fling_velocity,
+                    const gfx::PointF& causal_event_viewport_point,
+                    const cc::OverscrollBehavior& overscroll_behavior));
+  void DidStartScrollingViewport() override {}
+  MOCK_METHOD1(SetAllowedTouchAction, void(cc::TouchAction touch_action));
+  bool AllowsScrollResampling() override { return true; }
+};
+
+}  // namespace blink::test
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_INPUT_MOCK_INPUT_HANDLER_PROXY_CLIENT_H_
diff --git a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
index 5908aec..41d4108 100644
--- a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
+++ b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
@@ -4,9 +4,13 @@
 
 #include "third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h"
 
+#include <sys/types.h>
+
+#include <cstdint>
 #include <utility>
 
 #include "base/check_op.h"
+#include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
@@ -16,6 +20,7 @@
 #include "base/notreached.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
+#include "base/trace_event/traced_value.h"
 #include "base/tracing/protos/chrome_track_event.pbzero.h"
 #include "base/types/optional_ref.h"
 #include "base/types/pass_key.h"
@@ -26,6 +31,7 @@
 #include "cc/trees/layer_tree_host.h"
 #include "cc/trees/paint_holding_reason.h"
 #include "components/viz/common/features.h"
+#include "components/viz/common/switches.h"
 #include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/input/web_coalesced_input_event.h"
@@ -119,6 +125,60 @@
   }
 }
 
+bool IgnoreHiddenInput() {
+  return base::FeatureList::IsEnabled(features::kIgnoreInputWhileHidden) &&
+         !base::CommandLine::ForCurrentProcess()->HasSwitch(
+             ::switches::kRunAllCompositorStagesBeforeDraw);
+}
+
+std::unique_ptr<base::trace_event::TracedValue> SuppressInputToTracedValue(
+    uint16_t suppress_input) {
+  auto dict = std::make_unique<base::trace_event::TracedValue>();
+  dict->SetBoolean("DeferMainFrameUpdates",
+                   (suppress_input &
+                    static_cast<uint16_t>(
+                        WidgetInputHandlerManager::SuppressingInputEventsBits::
+                            kDeferMainFrameUpdates)));
+  dict->SetBoolean(
+      "DeferCommits",
+      (suppress_input &
+       static_cast<uint16_t>(WidgetInputHandlerManager::
+                                 SuppressingInputEventsBits::kDeferCommits)));
+  dict->SetBoolean(
+      "HasNotPainted",
+      (suppress_input &
+       static_cast<uint16_t>(WidgetInputHandlerManager::
+                                 SuppressingInputEventsBits::kHasNotPainted)));
+  dict->SetBoolean(
+      "Hidden",
+      (suppress_input &
+       static_cast<uint16_t>(
+           WidgetInputHandlerManager::SuppressingInputEventsBits::kHidden)));
+  return dict;
+}
+
+void SuppressEvent(
+    uint16_t suppress_input,
+    std::unique_ptr<WebCoalescedInputEvent> event,
+    mojom::blink::WidgetInputHandler::DispatchEventCallback callback) {
+  TRACE_EVENT("input", "Input Suppressed", "suppress_input",
+              SuppressInputToTracedValue(suppress_input));
+
+  int64_t trace_id = event->latency_info().trace_id();
+  TRACE_EVENT("input,benchmark,latencyInfo", "LatencyInfo.Flow",
+              [&](perfetto::EventContext ctx) {
+                base::TaskAnnotator::EmitTaskTimingDetails(ctx);
+                ui::LatencyInfo::FillTraceEvent(
+                    ctx, trace_id,
+                    ChromeLatencyInfo2::Step::STEP_HANDLE_INPUT_EVENT_IMPL);
+              });
+  if (callback) {
+    std::move(callback).Run(
+        mojom::blink::InputEventResultSource::kMainThread, ui::LatencyInfo(),
+        mojom::blink::InputEventResultState::kNotConsumed, nullptr, nullptr);
+  }
+}
+
 }  // namespace
 
 #if BUILDFLAG(IS_ANDROID)
@@ -264,7 +324,8 @@
           widget_scheduler_->InputTaskRunner(),
           widget_scheduler_,
           /*allow_raf_aligned_input=*/!never_composited)),
-      allow_scroll_resampling_(allow_scroll_resampling) {
+      allow_scroll_resampling_(allow_scroll_resampling),
+      ignore_hidden_input_(IgnoreHiddenInput()) {
 #if BUILDFLAG(IS_ANDROID)
   if (compositor_thread_default_task_runner_) {
     synchronous_compositor_registry_ =
@@ -295,6 +356,21 @@
   }
 }
 
+void WidgetInputHandlerManager::SetHidden(bool hidden) {
+  if (hidden) {
+    suppressing_input_events_state_ |=
+        static_cast<uint16_t>(SuppressingInputEventsBits::kHidden);
+  } else {
+    suppressing_input_events_state_ &=
+        ~static_cast<uint16_t>(SuppressingInputEventsBits::kHidden);
+  }
+}
+
+void WidgetInputHandlerManager::OnDevToolsSessionConnectionChanged(
+    bool attached) {
+  dev_tools_session_attached_ = attached;
+}
+
 void WidgetInputHandlerManager::InitInputHandler() {
   bool sync_compositing = false;
 #if BUILDFLAG(IS_ANDROID)
@@ -581,6 +657,12 @@
                                 std::move(event), std::move(callback)));
 }
 
+void WidgetInputHandlerManager::SetInputHandlerProxyForTesting(
+    std::unique_ptr<InputHandlerProxy> input_handler_proxy) {
+  input_handler_proxy_ = std::move(input_handler_proxy);
+  uses_input_handler_ = input_handler_proxy_.get();
+}
+
 void WidgetInputHandlerManager::DispatchEvent(
     std::unique_ptr<WebCoalescedInputEvent> event,
     mojom::blink::WidgetInputHandler::DispatchEventCallback callback) {
@@ -649,13 +731,14 @@
         ~static_cast<uint16_t>(SuppressingInputEventsBits::kHasNotPainted);
   }
 
+  if (dev_tools_session_attached_ || !ignore_hidden_input_) {
+    suppress_input &=
+        ~static_cast<uint16_t>(SuppressingInputEventsBits::kHidden);
+  }
+
   if (suppress_input && !allow_pre_commit_input_ &&
       !event_is_mouse_or_pointer_move) {
-    if (callback) {
-      std::move(callback).Run(
-          mojom::blink::InputEventResultSource::kMainThread, ui::LatencyInfo(),
-          mojom::blink::InputEventResultState::kNotConsumed, nullptr, nullptr);
-    }
+    SuppressEvent(suppress_input, std::move(event), std::move(callback));
     return;
   }
 
diff --git a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h
index bb5174e..a2ef32b 100644
--- a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h
+++ b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h
@@ -66,6 +66,7 @@
     kMaxValue = kAfterFirstPaint
   };
 
+ public:
   // For use in bitfields to keep track of why we should keep suppressing input
   // events. Maybe the rendering pipeline is currently deferring something, or
   // we are still waiting for the user to see some non empty paint. And we use
@@ -78,9 +79,10 @@
     kDeferCommits = 1 << 1,
     // if set, we have not painted a main frame from the current navigation yet
     kHasNotPainted = 1 << 2,
+    // if set, we are not visible, and should not accept any input
+    kHidden = 1 << 3,
   };
 
- public:
   // The `widget` and `frame_widget_input_handler` should be invalidated
   // at the same time.
   static scoped_refptr<WidgetInputHandlerManager> Create(
@@ -124,6 +126,8 @@
   bool RequestedMainFramePending() override;
 
   void DidFirstVisuallyNonEmptyPaint(const base::TimeTicks& first_paint_time);
+  void SetHidden(bool hidden);
+  void OnDevToolsSessionConnectionChanged(bool attached);
 
   // InputHandlerProxyClient overrides.
   void WillShutdown() override;
@@ -211,10 +215,17 @@
       std::unique_ptr<blink::WebCoalescedInputEvent> event,
       mojom::blink::WidgetInputHandler::DispatchEventCallback callback);
 
+  void SetInputHandlerProxyForTesting(
+      std::unique_ptr<InputHandlerProxy> input_handler_proxy);
+
   base::WeakPtr<WidgetInputHandlerManager> AsWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
   }
 
+  uint16_t suppressing_input_events_state() const {
+    return suppressing_input_events_state_;
+  }
+
  private:
   friend class ThreadSafeRefCounted<WidgetInputHandlerManager>;
   ~WidgetInputHandlerManager() override;
@@ -438,6 +449,9 @@
   // web_tests to ensure that scroll deltas are not timing-dependent.
   const bool allow_scroll_resampling_ = true;
 
+  std::atomic<bool> dev_tools_session_attached_ = false;
+  const bool ignore_hidden_input_;
+
   base::WeakPtrFactory<WidgetInputHandlerManager> weak_ptr_factory_{this};
 };
 
diff --git a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager_unittest.cc b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager_unittest.cc
new file mode 100644
index 0000000..db538d2
--- /dev/null
+++ b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager_unittest.cc
@@ -0,0 +1,230 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h"
+
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "cc/test/fake_impl_task_runner_provider.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/mock_input_handler.h"
+#include "cc/test/test_task_graph_runner.h"
+#include "cc/trees/layer_tree_settings.h"
+#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/widget/platform_widget.mojom-blink.h"
+#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
+#include "third_party/blink/renderer/platform/scheduler/test/fake_widget_scheduler.h"
+#include "third_party/blink/renderer/platform/widget/compositing/test/stub_widget_base_client.h"
+#include "third_party/blink/renderer/platform/widget/input/frame_widget_input_handler_impl.h"
+#include "third_party/blink/renderer/platform/widget/input/input_handler_proxy.h"
+#include "third_party/blink/renderer/platform/widget/input/mock_input_handler_proxy.h"
+#include "third_party/blink/renderer/platform/widget/input/mock_input_handler_proxy_client.h"
+#include "third_party/blink/renderer/platform/widget/widget_base.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
+
+namespace blink ::test {
+
+class WidgetInputHandlerManagerTest : public testing::Test,
+                                      public testing::WithParamInterface<bool> {
+ public:
+  WidgetInputHandlerManagerTest();
+  ~WidgetInputHandlerManagerTest() override = default;
+
+  bool IgnoreInputWhileHidden() const { return GetParam(); }
+
+  // Generates and empty touch start, and invokes `DispatchEvent`. Will validate
+  // that `HandleInputEventWithLatencyInfo` is called `expected_times_called`.
+  void DispatchTouchEvent(
+      int expected_times_called,
+      mojom::blink::WidgetInputHandler::DispatchEventCallback callback);
+
+  // Validates that `HandleInputEventWithLatencyInfo` is not called, and that
+  // the `DispatchEventCallback` is notified that the event was not consumed.
+  void ExpectNotConsumedDispatchEvent();
+
+  scoped_refptr<WidgetInputHandlerManager> widget_input_handler_manager() {
+    return widget_input_handler_manager_;
+  }
+
+  // testing::Test:
+  void SetUp() override;
+
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+
+  scoped_refptr<WidgetInputHandlerManager> widget_input_handler_manager_;
+  StubWidgetBaseClient client_;
+  scoped_refptr<scheduler::FakeWidgetScheduler> widget_scheduler_ =
+      base::MakeRefCounted<scheduler::FakeWidgetScheduler>();
+  std::unique_ptr<WidgetBase> widget_base_;
+  base::WeakPtr<mojom::blink::FrameWidgetInputHandler>
+      frame_widget_input_handler_;
+
+  testing::StrictMock<cc::MockInputHandler> mock_input_handler_;
+  raw_ptr<MockInputHandlerProxy> input_handler_proxy_;
+  testing::StrictMock<MockInputHandlerProxyClient> mock_client_;
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+WidgetInputHandlerManagerTest::WidgetInputHandlerManagerTest() {
+  if (IgnoreInputWhileHidden()) {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kIgnoreInputWhileHidden);
+  } else {
+    scoped_feature_list_.InitAndDisableFeature(
+        features::kIgnoreInputWhileHidden);
+  }
+}
+
+void WidgetInputHandlerManagerTest::DispatchTouchEvent(
+    int expected_times_called,
+    mojom::blink::WidgetInputHandler::DispatchEventCallback callback) {
+  EXPECT_CALL(*input_handler_proxy_, HandleInputEventWithLatencyInfo(
+                                         testing::_, testing::_, testing::_))
+      .Times(expected_times_called);
+  widget_input_handler_manager_->DispatchEvent(
+      std::make_unique<WebCoalescedInputEvent>(
+          WebTouchEvent(WebInputEvent::Type::kTouchStart,
+                        WebInputEvent::kNoModifiers,
+                        WebInputEvent::GetStaticTimeStampForTests()),
+          ui::LatencyInfo()),
+      std::move(callback));
+  testing::Mock::VerifyAndClearExpectations(input_handler_proxy_);
+}
+
+void WidgetInputHandlerManagerTest::ExpectNotConsumedDispatchEvent() {
+  DispatchTouchEvent(
+      /*expected_times_called=*/0,
+      WTF::BindOnce([](mojom::blink::InputEventResultSource,
+                       const ui::LatencyInfo&,
+                       mojom::blink::InputEventResultState result_state,
+                       mojom::blink::DidOverscrollParamsPtr,
+                       mojom::blink::TouchActionOptionalPtr) {
+        EXPECT_EQ(result_state,
+                  mojom::blink::InputEventResultState::kNotConsumed);
+      }));
+}
+
+void WidgetInputHandlerManagerTest::SetUp() {
+  mojo::AssociatedRemote<mojom::blink::Widget> widget_remote;
+  mojo::PendingAssociatedReceiver<mojom::blink::Widget> widget_receiver =
+      widget_remote.BindNewEndpointAndPassDedicatedReceiver();
+
+  mojo::AssociatedRemote<mojom::blink::WidgetHost> widget_host_remote;
+  std::ignore = widget_host_remote.BindNewEndpointAndPassDedicatedReceiver();
+
+  const bool never_composited = false;
+
+  widget_base_ = std::make_unique<WidgetBase>(
+      /*widget_base_client=*/&client_, widget_host_remote.Unbind(),
+      std::move(widget_receiver),
+      scheduler::GetSingleThreadTaskRunnerForTesting(),
+      /*is_hidden=*/false, never_composited,
+      /*is_for_child_local_root=*/false,
+      /*is_for_scalable_page=*/true);
+
+  mojo::AssociatedRemote<mojom::blink::FrameWidgetInputHandler>
+      frame_widget_input_handler_remote;
+  mojo::PendingAssociatedReceiver<mojom::blink::FrameWidgetInputHandler>
+      frame_widget_input_handler_receiver =
+          frame_widget_input_handler_remote
+              .BindNewEndpointAndPassDedicatedReceiver();
+
+  mojo::MakeSelfOwnedAssociatedReceiver(
+      std::make_unique<FrameWidgetInputHandlerImpl>(
+          widget_base_->GetWeakPtr(), frame_widget_input_handler_,
+          /*input_event_queue=*/nullptr),
+      std::move(frame_widget_input_handler_receiver));
+
+  // WidgetBase isn't setup with full mocks. Were we to call this with
+  // `uses_input_hanlder_=true` we'd attempt to access `LayerHostTree` which
+  // we can't mock in WidgetBase. So we set to false, and use test override
+  // to enable the desired dispach mode.
+  widget_input_handler_manager_ = WidgetInputHandlerManager::Create(
+      widget_base_->GetWeakPtr(), frame_widget_input_handler_, never_composited,
+      /*compositor_thread_scheduler=*/nullptr, widget_scheduler_,
+      /*needs_input_handler=*/false,
+      /*allow_scroll_resampling=*/false,
+      /*io_thread_id=*/base::kInvalidThreadId,
+      /*main_thread_id=*/base::PlatformThread::CurrentId());
+
+  auto unique_input_handler_proxy = std::make_unique<MockInputHandlerProxy>(
+      mock_input_handler_, &mock_client_);
+  input_handler_proxy_ = unique_input_handler_proxy.get();
+  widget_input_handler_manager_->SetInputHandlerProxyForTesting(
+      std::move(unique_input_handler_proxy));
+}
+
+// Tests that while we are hidden, that input is neither dispatched nor
+// consumed. Becoming visible should remove this suppression.
+TEST_P(WidgetInputHandlerManagerTest, InputWhileHidden) {
+  auto manager = widget_input_handler_manager();
+  EXPECT_EQ(
+      manager->suppressing_input_events_state(),
+      static_cast<uint16_t>(WidgetInputHandlerManager::
+                                SuppressingInputEventsBits::kHasNotPainted));
+  manager->DidFirstVisuallyNonEmptyPaint(base::TimeTicks::Now());
+  EXPECT_EQ(manager->suppressing_input_events_state(), 0u);
+
+  manager->SetHidden(true);
+  EXPECT_EQ(
+      manager->suppressing_input_events_state(),
+      static_cast<uint16_t>(
+          WidgetInputHandlerManager::SuppressingInputEventsBits::kHidden));
+
+  if (GetParam()) {
+    ExpectNotConsumedDispatchEvent();
+  }
+
+  manager->SetHidden(false);
+  EXPECT_EQ(manager->suppressing_input_events_state(), 0u);
+  DispatchTouchEvent(/*expected_times_called=*/1,
+                     mojom::blink::WidgetInputHandler::DispatchEventCallback());
+}
+
+// Tests that while we are hidden, and attached DevTools sessions witll override
+// the input suppression. Events should be dispatched. Upon the session
+// detaching we should resume neither dispatching nor consuming events.
+TEST_P(WidgetInputHandlerManagerTest, DevToolsSessionOverridesSuppression) {
+  auto manager = widget_input_handler_manager();
+  EXPECT_EQ(
+      manager->suppressing_input_events_state(),
+      static_cast<uint16_t>(WidgetInputHandlerManager::
+                                SuppressingInputEventsBits::kHasNotPainted));
+  manager->DidFirstVisuallyNonEmptyPaint(base::TimeTicks::Now());
+  EXPECT_EQ(manager->suppressing_input_events_state(), 0u);
+
+  manager->SetHidden(true);
+  EXPECT_EQ(
+      manager->suppressing_input_events_state(),
+      static_cast<uint16_t>(
+          WidgetInputHandlerManager::SuppressingInputEventsBits::kHidden));
+
+  manager->OnDevToolsSessionConnectionChanged(/*attached=*/true);
+  DispatchTouchEvent(/*expected_times_called=*/1,
+                     mojom::blink::WidgetInputHandler::DispatchEventCallback());
+
+  manager->OnDevToolsSessionConnectionChanged(/*attached=*/false);
+  if (GetParam()) {
+    ExpectNotConsumedDispatchEvent();
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         WidgetInputHandlerManagerTest,
+                         testing::Bool(),
+                         [](auto& info) {
+                           return info.param ? "IgnoreInputWhileHidden"
+                                             : "ProcessInputWhileHidden";
+                         });
+}  // namespace blink::test
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index e9acc8dde..e558cd72 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -1431,6 +1431,10 @@
     FlushInputProcessedCallback();
 
   SetCompositorVisible(!is_hidden_);
+
+  if (widget_input_handler_manager_) {
+    widget_input_handler_manager_->SetHidden(is_hidden_);
+  }
 }
 
 ui::TextInputType WidgetBase::GetTextInputType() {
@@ -1888,4 +1892,10 @@
              : max_render_buffer_bounds_gpu_;
 }
 
+void WidgetBase::OnDevToolsSessionConnectionChanged(bool attached) {
+  if (widget_input_handler_manager_) {
+    widget_input_handler_manager_->OnDevToolsSessionConnectionChanged(attached);
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/widget/widget_base.h b/third_party/blink/renderer/platform/widget/widget_base.h
index fb0c7d1..dbd53ba9 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.h
+++ b/third_party/blink/renderer/platform/widget/widget_base.h
@@ -403,6 +403,8 @@
 
   bool WillBeDestroyed() const { return will_be_destroyed_; }
 
+  void OnDevToolsSessionConnectionChanged(bool attached);
+
  private:
   static void AssertAreCompatible(const WidgetBase& a, const WidgetBase& b);
 
diff --git a/third_party/blink/renderer/platform/widget/widget_base_client.h b/third_party/blink/renderer/platform/widget/widget_base_client.h
index ca38a1f..c0d293f8 100644
--- a/third_party/blink/renderer/platform/widget/widget_base_client.h
+++ b/third_party/blink/renderer/platform/widget/widget_base_client.h
@@ -18,6 +18,7 @@
 #include "third_party/blink/public/platform/web_input_event_result.h"
 #include "third_party/blink/public/platform/web_text_input_type.h"
 #include "third_party/blink/public/web/web_lifecycle_update.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/widget/input/input_handler_proxy.h"
 #include "ui/base/mojom/menu_source_type.mojom-blink-forward.h"
 #include "ui/display/mojom/screen_orientation.mojom-blink.h"
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index be01deeb..5417191 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2682,6 +2682,8 @@
 crbug.com/383880384 [ Win ] external/wpt/css/css-properties-values-api/registered-property-change-style-002.html [ Failure Pass ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/388934352 external/wpt/trusted-types/block-eval-function-constructor.html [ Failure ]
+crbug.com/388934352 external/wpt/trusted-types/WorkerGlobalScope-importScripts.html [ Failure ]
 crbug.com/388592307 external/wpt/css/selectors/invalidation/has-nested-pseudo-002-crash.html [ Crash ]
 crbug.com/388592307 external/wpt/css/selectors/invalidation/has-nested-pseudo-003-crash.html [ Crash ]
 crbug.com/388580242 [ Mac14 ] external/wpt/html/cross-origin-opener-policy/popup-redirect-same-origin-allow-popups.https.html [ Crash Failure ]
@@ -6230,8 +6232,8 @@
 crbug.com/40146374 virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/customizable-select/border-rendering.tentative.html [ Failure ]
 crbug.com/40146374 virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-events.tentative.html [ Failure ]
 crbug.com/40146374 virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-events.tentative.html [ Failure ]
-crbug.com/40146374 virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/select-events.tentative.html [ Failure ]
-crbug.com/40146374 virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/select-events.tentative.html [ Failure ]
+crbug.com/40146374 virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-events-2.tentative.html [ Failure ]
+crbug.com/40146374 virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-events-2.tentative.html [ Failure ]
 crbug.com/40146374 virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/select-keyboard-behavior.tentative.html [ Failure ]
 crbug.com/40146374 virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/select-keyboard-behavior.tentative.html [ Failure ]
 crbug.com/40146374 virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/select-option-focusable.tentative.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestLists/content_shell.filter b/third_party/blink/web_tests/TestLists/content_shell.filter
index e0e55adf..352b7c3 100644
--- a/third_party/blink/web_tests/TestLists/content_shell.filter
+++ b/third_party/blink/web_tests/TestLists/content_shell.filter
@@ -422,7 +422,6 @@
 external/wpt/css/css-scroll-snap-2/scroll-start/scroll-start-fieldset.tentative.html
 external/wpt/css/css-scroll-snap-2/scroll-initial-target/scroll-initial-target-rtl.tentative.html
 external/wpt/css/css-scroll-snap/input/snap-area-overflow-boundary-viewport-covering.tentative.html
-external/wpt/css/css-scroll-snap/selection-target.html
 external/wpt/css/css-scroll-snap/snap-after-relayout/changing-scroll-snap-align-nested.tentative.html
 external/wpt/css/css-scroll-snap/snap-after-relayout/multiple-aligned-targets/common-to-both-axes-supercedes-first-in-tree-order.html
 external/wpt/css/css-scroll-snap/snap-after-relayout/multiple-aligned-targets/prefer-common-to-both-axes.html
@@ -630,7 +629,6 @@
 external/wpt/fledge/tentative/component-ads.https.window.html?11-15
 external/wpt/fledge/tentative/reporting-arguments.https.window.html?16-last
 external/wpt/fs/FileSystemObserver-sync-access-handle.https.tentative.*
-external/wpt/gyroscope/Gyroscope.https.html
 external/wpt/gyroscope/Gyroscope-iframe-access.https.html
 external/wpt/html-aam/names.html
 external/wpt/html-aam/roles.html
@@ -882,7 +880,6 @@
 external/wpt/partitioned-popins/partitioned-popins.cookies-blocked.tentative.sub.https.window.html
 external/wpt/partitioned-popins/partitioned-popins.partitions.tentative.https.window.html
 external/wpt/payment-request/onpaymentmethodchange-attribute.https.html
-external/wpt/payment-request/payment-request-disallowed-when-hidden.https.html
 external/wpt/payment-request/show-consume-activation.https.html
 external/wpt/performance-timeline/navigation-id-reset.tentative.html
 external/wpt/performance-timeline/not-restored-reasons/abort-block-bfcache.window.html
@@ -891,13 +888,11 @@
 external/wpt/pointerevents/capturing_boundary_event_handler_at_ua_shadowdom.html*
 external/wpt/pointerevents/coalesced_events_attributes_under_load.https.optional.html*
 external/wpt/pointerevents/compat/pointerevent_mouse-pointer-on-scrollbar.html
-external/wpt/pointerevents/compat/pointerevent_mouseevent_key_pressed.html
 external/wpt/pointerevents/compat/pointerevent_touch_target_after_pointerdown_target_removed.tentative.html
 external/wpt/pointerevents/pointerevent_capture_mouse_and_release_and_capture_again.html
 external/wpt/pointerevents/pointerevent_contextmenu_is_a_pointerevent.html?touch
 external/wpt/pointerevents/pointerevent_pointer_boundary_events_after_removing_last_over_element.html
 external/wpt/pointerevents/pointerevent_pointercapture_in_frame.html?pen
-external/wpt/pointerevents/pointerevent_pointermove_after_pointerup_target_removed.html
 external/wpt/pointerevents/pointerevent_sequence_at_implicit_release_on_drag.html
 external/wpt/pointerevents/pointerevent_to_slotted_target.html?pen
 external/wpt/pointerevents/pointerevent_touch-action-modified_touch.html
@@ -908,11 +903,6 @@
 external/wpt/resize-observer/notify.html
 external/wpt/resize-observer/scrollbars.html
 external/wpt/resize-observer/svg.html
-external/wpt/screen-orientation/event-before-promise.html
-external/wpt/screen-orientation/lock-basic.html
-external/wpt/screen-orientation/onchange-event.html
-external/wpt/screen-wake-lock/chrome-bug-1348019.https.html
-external/wpt/screen-wake-lock/wakelock-document-hidden.https.html
 external/wpt/screen-wake-lock/wakelock-enabled-by-permissions-policy-attribute.https.html
 external/wpt/screen-wake-lock/wakelock-enabled-by-permissions-policy.https.html
 external/wpt/scroll-animations/css/animation-fill-outside-range-test.html
@@ -1011,7 +1001,6 @@
 external/wpt/websockets/Close-1005.any.worker.html?wpt_flags=h2
 external/wpt/websockets/Close-3000-reason.any.worker.html?wpt_flags=h2
 external/wpt/websockets/Close-3000-verify-code.any.worker.html?wpt_flags=h2
-external/wpt/websockets/cookies/006.html?wss&wpt_flags=https
 external/wpt/websockets/Create-extensions-empty.any.html?wpt_flags=h2
 external/wpt/websockets/Create-valid-url.any.worker.html?wpt_flags=h2
 external/wpt/websockets/Create-valid-url-array-protocols.any.html?wpt_flags=h2
@@ -1090,9 +1079,7 @@
 virtual/fractional-scroll-offsets/external/wpt/css/css-position/sticky/position-sticky-inline.html
 virtual/fractional-scroll-offsets/external/wpt/css/css-position/sticky/position-sticky-large-top*
 virtual/fractional-scroll-offsets/external/wpt/css/css-position/sticky/position-sticky-nested-inline.html
-virtual/generic-sensor-extra-classes/external/wpt/ambient-light/AmbientLightSensor.https.html
 virtual/generic-sensor-extra-classes/external/wpt/ambient-light/AmbientLightSensor-iframe-access.https.html
-virtual/generic-sensor-extra-classes/external/wpt/magnetometer/Magnetometer.https.html
 virtual/generic-sensor-extra-classes/external/wpt/magnetometer/Magnetometer-iframe-access.https.html
 virtual/gpu-rasterization/external/wpt/css/css-images/cross-fade-premultiplied-alpha.html
 virtual/gpu-rasterization/external/wpt/css/css-images/object-fit-*
@@ -1206,7 +1193,6 @@
 virtual/threaded/external/wpt/css/css-backgrounds/table-cell-background-local-003.html
 virtual/threaded/external/wpt/css/css-view-transitions/*
 virtual/threaded/external/wpt/long-animation-frame/tentative/loaf-iframe-self.html
-virtual/threaded/external/wpt/long-animation-frame/tentative/loaf-visibility.html
 virtual/threaded-composited-iframes/external/wpt/is-input-pending/tentative/*
 virtual/threaded-prefer-compositing/external/wpt/css/cssom-view/getBoundingClientRect-shy.html
 virtual/threaded-prefer-compositing/external/wpt/css/cssom-view/range-bounding-client-rect-with-display-contents.html
diff --git a/third_party/blink/web_tests/TestLists/highdpi b/third_party/blink/web_tests/TestLists/highdpi
index a84ea94..2b3e853 100644
--- a/third_party/blink/web_tests/TestLists/highdpi
+++ b/third_party/blink/web_tests/TestLists/highdpi
@@ -849,7 +849,7 @@
 external/wpt/html/semantics/forms/the-output-element/output-setcustomvalidity.html
 external/wpt/html/semantics/forms/the-progress-element/progress.html
 external/wpt/html/semantics/forms/the-select-element/select-add.html
-external/wpt/html/semantics/forms/the-select-element/select-events.tentative.html
+external/wpt/html/semantics/forms/the-select-element/customizable-select/select-events-2.tentative.html
 external/wpt/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive.html
 external/wpt/html/semantics/grouping-content/the-dd-element/grouping-dd.html
 external/wpt/html/semantics/grouping-content/the-div-element/grouping-div.html
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 8fc44d38..f6eebba 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -3895,6 +3895,13 @@
        {}
       ]
      ],
+     "multicol-loads-indefinitely-001-crash.html": [
+      "c00891c14c16485ce88a3bfe56d0432658eb8e61",
+      [
+       null,
+       {}
+      ]
+     ],
      "nested-balanced-monolithic-multicol-crash.html": [
       "082bf70691c955e1ba7fac9e2c077c71a7d52f84",
       [
@@ -20405,6 +20412,21 @@
   },
   "print-reftest": {
    "css": {
+    "css-anchor-position": {
+     "anchor-position-005-print.html": [
+      "eeae841405a774cfa77012b7f01b304ea8c89632",
+      [
+       null,
+       [
+        [
+         "/css/css-anchor-position/anchor-position-005-print-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ]
+    },
     "css-break": {
      "abspos-in-clipped-overflow-print.html": [
       "4510dd3cbc87aa549c0b81fdcfd5ccdafc4fa6b0",
@@ -62360,6 +62382,58 @@
         {}
        ]
       ],
+      "collapsing-border-model-011.html": [
+       "9c03ddeda9051c6e823ca1f31729eed0d8a8ac7b",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square-only.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "collapsing-border-model-012.html": [
+       "39b176ce375994ece4ca6c05b7484ef59fcd1c79",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square-only.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "collapsing-border-model-013.html": [
+       "5e5f57694c9015c5a74082a9525bb2ce44d39bfc",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square-only.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "collapsing-border-model-014.html": [
+       "18177526b824cfebbc95467031c30d04b7b3ff79",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square-only.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "column-visibility-004.xht": [
        "60d233fa9402f0b57a45f299405dacca5abc8ee6",
        [
@@ -73517,6 +73591,19 @@
        {}
       ]
      ],
+     "anchor-position-multicol-011.html": [
+      "eab57bfdb841bc488708ad001f2534cdae2a5188",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "anchor-position-top-layer-001.html": [
       "f40cc0dccf3ccbef010629ebc809229ba83c9b45",
       [
@@ -312992,6 +313079,20 @@
      "34edb7b82b9657e0beaf9b1669d0854d365770ed",
      []
     ],
+    "permissions-policy": {
+     "ch-ua-high-entropy-values-disabled-by-permissions-policy.https.sub.html.headers": [
+      "fcf474880843e220faff943f8946f83b514208a4",
+      []
+     ],
+     "ch-ua-high-entropy-values-enabled-by-permissions-policy.https.sub.html.headers": [
+      "ac74cfd647f07a2e276b5913a139b7728189d7c9",
+      []
+     ],
+     "ch-ua-high-entropy-values-enabled-on-self-origin-by-permissions-policy.https.sub.html.headers": [
+      "9c876c03f3cf59614059e818acab6e39210a5122",
+      []
+     ]
+    },
     "resources": {
      "2x3-svg-scaled-by-sec-ch-width.py": [
       "d53574c361213ddf0949f906a08ebe388f9aa2d0",
@@ -323689,6 +323790,10 @@
       "5f741b46bb7c2ea65709b61212b0931a9574c7f8",
       []
      ],
+     "anchor-position-005-print-ref.html": [
+      "94a1f8e55d30e2de6607ca741142d4bdbdf87098",
+      []
+     ],
      "anchor-position-multicol-003-expected.txt": [
       "7f03f52a900220153e28834fdbb2cca9f2d99da5",
       []
@@ -385708,6 +385813,12 @@
          "8557441f7e2a9f788475f63c8cb936daaa2afb6d",
          []
         ]
+       },
+       "the-offscreen-canvas": {
+        "offscreencanvas.transferrable.sw.js": [
+         "fc7265a48c393566ad8c9fe7b73281e4f5137b21",
+         []
+        ]
        }
       },
       "path-objects": {
@@ -400751,7 +400862,7 @@
      []
     ],
     "WebCryptoAPI.idl": [
-     "ae85c1cfe4684fe778038f579223ee7a5606150c",
+     "ff7a89cd0d51be01760b9bfdb709393f4db5fd31",
      []
     ],
     "accelerometer.idl": [
@@ -401031,7 +401142,7 @@
      []
     ],
     "digital-credentials.idl": [
-     "2207b25dd57a0b4d47552faaf0de5cc3cd892021",
+     "e20079efa14f9a894181d1cafb66129a1634c627",
      []
     ],
     "digital-goods.idl": [
@@ -401055,7 +401166,7 @@
      []
     ],
     "element-timing.idl": [
-     "586b5084bb00e902dd3b1a80d09847423bbdc509",
+     "ef73ca6c0f610ff8cdb14a8bd0859efb8eca743b",
      []
     ],
     "encoding.idl": [
@@ -401551,7 +401662,7 @@
      []
     ],
     "speech-api.idl": [
-     "74085481525c943296f023e94a6532e5bc9f5b15",
+     "f3967b873ffc597380585a0718c4f43406b2f281",
      []
     ],
     "storage-access.idl": [
@@ -401595,7 +401706,7 @@
      []
     ],
     "turtledove.idl": [
-     "c416760d5956c4d2b818d8fda9da1384604919b0",
+     "05072974ec94e3a386aa74e7c0cd6ef98e2b4f04",
      []
     ],
     "ua-client-hints.idl": [
@@ -401635,7 +401746,7 @@
      []
     ],
     "wai-aria.idl": [
-     "78083f03f91fd987b6e19da391e640bf9816d701",
+     "deebc5626e2a925c4b7c7bad92c8492ebb4dcb08",
      []
     ],
     "wasm-js-api.idl": [
@@ -401687,7 +401798,7 @@
      []
     ],
     "webauthn.idl": [
-     "46e2418281e4355bd60e493f9b6af20b7ec4b19b",
+     "a33c85e7bad86753211fa7aa9270abac18b1e54e",
      []
     ],
     "webcodecs-aac-codec-registration.idl": [
@@ -406975,6 +407086,10 @@
       "2265bd01d2bd66cfa079e22a749e03e836e45785",
       []
      ],
+     "permissions-policy-ch-ua-high-entropy-values.html": [
+      "842cf590293dade359df1243b3f3f8b1d804091b",
+      []
+     ],
      "permissions-policy-clipboard-read.html": [
       "10fc45fd933ef0f77e5d53d4fac9ec70d372ce48",
       []
@@ -407454,12 +407569,8 @@
      "98e4d4ad6702f8c847f92fddaa16cdd1b9940632",
      []
     ],
-    "pointerevent_pointercapture_in_frame_mouse-expected.txt": [
-     "be6da2668f5106e74c5e86b211b944e125d71cd7",
-     []
-    ],
     "pointerevent_pointercapture_in_frame_touch-expected.txt": [
-     "e94d02d6b0677b91bbfa65b3204e1ad0277a1bb4",
+     "a9b817ec4e51ba4b29149ab81d112c8dec732f87",
      []
     ],
     "pointerevent_pointermove_after_pointerup_target_removed-expected.txt": [
@@ -417784,7 +417895,7 @@
     ],
     "support": {
      "touch.js": [
-      "f4bc0467db8a215fa85fb7e1c483b3d99a458ef7",
+      "af0fefb67002a02910aee82ea7f02839c967d321",
       []
      ]
     }
@@ -417830,6 +417941,10 @@
      "6ea31f1ef8d3838b3b9d84f6dac839b22083ee20",
      []
     ],
+    "WorkerGlobalScope-worker-constructor-expected.txt": [
+     "17ab3eb534a2de51218cd16da334bbf79b47e8e2",
+     []
+    ],
     "block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval-expected.txt": [
      "278967b2138dba63046eb9ae71ffb702f826fd2e",
      []
@@ -417929,6 +418044,14 @@
       "7d574a6a1387a9e7bdaaf596f46e9d616ca94988",
       []
      ],
+     "ServiceWorkerContainer-register.https.js": [
+      "150da428779516deb10176e908772d16a1a27f3b",
+      []
+     ],
+     "ServiceWorkerContainer-register.https.js.headers": [
+      "604e765da46d85fe8ab85d3097fe7c2cbe00a930",
+      []
+     ],
      "WorkerGlobalScope-eval.https.js": [
       "95ac1ff844b3e4e6f01a4abb3cf4846df514bf70",
       []
@@ -417938,7 +418061,7 @@
       []
      ],
      "WorkerGlobalScope-importScripts.https.js": [
-      "c40e8550dd659e2656564f5b3b61a0e5cb591710",
+      "a3ecfc481923b5e92e0eca287498086b4448458c",
       []
      ],
      "WorkerGlobalScope-importScripts.https.js.headers": [
@@ -417946,13 +418069,21 @@
       []
      ],
      "WorkerGlobalScope-worker-constructor.js": [
-      "7306b186d61904a60c082d168dc8cd843bcfbf30",
+      "e45a92d4817c1c81c2b1e893cb4ecd20cc238de1",
       []
      ],
      "WorkerGlobalScope-worker-constructor.js.headers": [
       "af6596b29a8080e5cd8d688d0d6933caf49a2090",
       []
      ],
+     "block-eval-function-constructor-worker.js": [
+      "0a74a1cdef31c93cd854f6abe0220319002d0303",
+      []
+     ],
+     "block-eval-function-constructor.js": [
+      "83bb606388305d9c9e20cb11623f379ef005316f",
+      []
+     ],
      "block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval-worker.js": [
       "456780247f2c329ddd20ebffe5f7b441e5451b28",
       []
@@ -418005,6 +418136,10 @@
       "45053d43e362e223e0ce5e6dffb4da09c0ce3f34",
       []
      ],
+     "worker.https.js": [
+      "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
+      []
+     ],
      "worker.js": [
       "4079f7e9c7933cf9ee195fe0e7a54e0f56f184ab",
       []
@@ -421716,26 +421851,6 @@
      "ede50f41555a2b4543d2b8d98207855f22cc0371",
      []
     ],
-    "audioDecoder-codec-specific.https.any_pcm_f32-expected.txt": [
-     "0aa83d1b0ca5fbe42c10ff1148f1eb8988b428c7",
-     []
-    ],
-    "audioDecoder-codec-specific.https.any_pcm_s16-expected.txt": [
-     "2380e74abe8af7bc308723aa43ca3289477478cd",
-     []
-    ],
-    "audioDecoder-codec-specific.https.any_pcm_s24-expected.txt": [
-     "7fe6747c3ee21f2bc6085f47200b0e838a94b7fa",
-     []
-    ],
-    "audioDecoder-codec-specific.https.any_pcm_s32-expected.txt": [
-     "fe4e5430ce379b8f9a265b05fd4d8244828966ce",
-     []
-    ],
-    "audioDecoder-codec-specific.https.any_pcm_u8-expected.txt": [
-     "141d485febff01c84079bb0dd59df850898a5579",
-     []
-    ],
     "audioDecoder-codec-specific.https.any_vorbis-expected.txt": [
      "02d1bda8b892d4fd7e0889ba9af8362a3ac523b3",
      []
@@ -423099,7 +423214,7 @@
        []
       ],
       "fixtures_bidi.py": [
-       "241ba528ddfaae95c4474dba578417813a189bcb",
+       "ec36eb953ff391610d23930dc558d10d11c4b242",
        []
       ],
       "fixtures_http.py": [
@@ -424912,6 +425027,10 @@
      "server-read-then-close.py": [
       "7f992e0dcca3ae62277cac0fa39355fce3e57be0",
       []
+     ],
+     "token-count.py": [
+      "8cdfd802b1edb23bf038a2dab82e9913c3654a74",
+      []
      ]
     },
     "idlharness.https.any-expected.txt": [
@@ -436372,38 +436491,462 @@
       {}
      ]
     ],
-    "nested-cloning-basic.html": [
-     "df4848b69374c89bc7dc372c1adc5365bccc096e",
+    "nested-cloning-basic.any.js": [
+     "91e6630d8545ec5d9b34900dfaa121aff15dfb8c",
      [
-      null,
+      "IndexedDB/nested-cloning-basic.any.html",
       {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: basic objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-basic.any.serviceworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: basic objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-basic.any.sharedworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: basic objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-basic.any.worker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: basic objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
        "timeout": "long"
       }
      ]
     ],
-    "nested-cloning-large-multiple.html": [
-     "97bcaddfb2d64bfe89aaf0e7df574a654e009153",
+    "nested-cloning-large-multiple.any.js": [
+     "7473d33068da24d2da1e3b439528aca872e482b5",
      [
-      null,
+      "IndexedDB/nested-cloning-large-multiple.any.html",
       {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: large nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-large-multiple.any.serviceworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: large nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-large-multiple.any.sharedworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: large nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-large-multiple.any.worker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: large nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
        "timeout": "long"
       }
      ]
     ],
-    "nested-cloning-large.html": [
-     "0cd8cb48ceec704ef70ea70dae8da76d8928ef12",
+    "nested-cloning-large.any.js": [
+     "07a9e2711390f9ad1f3ddf1e65e3852ad6ddc7a5",
      [
-      null,
+      "IndexedDB/nested-cloning-large.any.html",
       {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: large nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-large.any.serviceworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: large nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-large.any.sharedworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: large nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-large.any.worker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: large nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
        "timeout": "long"
       }
      ]
     ],
-    "nested-cloning-small.html": [
-     "e5105a999f57e6bc49828ce886ab138117c9c8f6",
+    "nested-cloning-small.any.js": [
+     "7cef76275e52512121ebc05ede382cff39b7ae23",
      [
-      null,
+      "IndexedDB/nested-cloning-small.any.html",
       {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: small nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-small.any.serviceworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: small nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-small.any.sharedworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: small nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
+       "timeout": "long"
+      }
+     ],
+     [
+      "IndexedDB/nested-cloning-small.any.worker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: small nested objects are cloned correctly"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support-promises.js"
+        ],
+        [
+         "script",
+         "resources/nested-cloning-common.js"
+        ],
+        [
+         "timeout",
+         "long"
+        ]
+       ],
        "timeout": "long"
       }
      ]
@@ -437559,25 +438102,162 @@
       {}
      ]
     ],
-    "transaction-lifetime-blocked.htm": [
-     "760b6b9bdbe91b987b02495a2cd7af4328520c90",
+    "transaction-lifetime-empty.any.js": [
+     "9ef27d2812dd144e14727b7e122fa22a373b3ea0",
      [
-      null,
-      {}
+      "IndexedDB/transaction-lifetime-empty.any.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: Commit ordering of empty transactions"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support.js"
+        ]
+       ]
+      }
+     ],
+     [
+      "IndexedDB/transaction-lifetime-empty.any.serviceworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: Commit ordering of empty transactions"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support.js"
+        ]
+       ]
+      }
+     ],
+     [
+      "IndexedDB/transaction-lifetime-empty.any.sharedworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: Commit ordering of empty transactions"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support.js"
+        ]
+       ]
+      }
+     ],
+     [
+      "IndexedDB/transaction-lifetime-empty.any.worker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "IndexedDB: Commit ordering of empty transactions"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support.js"
+        ]
+       ]
+      }
      ]
     ],
-    "transaction-lifetime-empty.html": [
-     "ba299fdcd041802b49e41cb87cf4d52a577bab81",
+    "transaction-lifetime.any.js": [
+     "969960c4aecbe9cbe1248e5dfee756420b273ebb",
      [
-      null,
-      {}
-     ]
-    ],
-    "transaction-lifetime.htm": [
-     "996f62937f76fa2b97fd3b66ead3993d234773f5",
+      "IndexedDB/transaction-lifetime.any.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "Event order when opening a second database when one connection is open already"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support.js"
+        ]
+       ]
+      }
+     ],
      [
-      null,
-      {}
+      "IndexedDB/transaction-lifetime.any.serviceworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "Event order when opening a second database when one connection is open already"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support.js"
+        ]
+       ]
+      }
+     ],
+     [
+      "IndexedDB/transaction-lifetime.any.sharedworker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "Event order when opening a second database when one connection is open already"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support.js"
+        ]
+       ]
+      }
+     ],
+     [
+      "IndexedDB/transaction-lifetime.any.worker.html",
+      {
+       "script_metadata": [
+        [
+         "title",
+         "Event order when opening a second database when one connection is open already"
+        ],
+        [
+         "global",
+         "window,worker"
+        ],
+        [
+         "script",
+         "resources/support.js"
+        ]
+       ]
+      }
      ]
     ],
     "transaction-relaxed-durability.any.js": [
@@ -447019,7 +447699,7 @@
    "ai": {
     "language_detection": {
      "capabilities.tentative.https.any.js": [
-      "2a0698695d00ce2f8a50ade3adbcd34212051b8f",
+      "3eca5119a378741fc48adb44f4bd83ba7a7e06bd",
       [
        "ai/language_detection/capabilities.tentative.https.any.html",
        {
@@ -447027,6 +447707,40 @@
          [
           "title",
           "capabilities test"
+         ],
+         [
+          "global",
+          "window,worker"
+         ]
+        ]
+       }
+      ],
+      [
+       "ai/language_detection/capabilities.tentative.https.any.serviceworker.html",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "capabilities test"
+         ],
+         [
+          "global",
+          "window,worker"
+         ]
+        ]
+       }
+      ],
+      [
+       "ai/language_detection/capabilities.tentative.https.any.sharedworker.html",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "capabilities test"
+         ],
+         [
+          "global",
+          "window,worker"
          ]
         ]
        }
@@ -447038,13 +447752,17 @@
          [
           "title",
           "capabilities test"
+         ],
+         [
+          "global",
+          "window,worker"
          ]
         ]
        }
       ]
      ],
      "detector.https.tentative.any.js": [
-      "4f962de8339de0a8eb1b0444dfcf668c05f76b88",
+      "6bd3a41b8cd8396d9bb890fe17bd753adc4f232e",
       [
        "ai/language_detection/detector.https.tentative.any.html",
        {
@@ -447052,6 +447770,40 @@
          [
           "title",
           "Detect english"
+         ],
+         [
+          "global",
+          "window,worker"
+         ]
+        ]
+       }
+      ],
+      [
+       "ai/language_detection/detector.https.tentative.any.serviceworker.html",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "Detect english"
+         ],
+         [
+          "global",
+          "window,worker"
+         ]
+        ]
+       }
+      ],
+      [
+       "ai/language_detection/detector.https.tentative.any.sharedworker.html",
+       {
+        "script_metadata": [
+         [
+          "title",
+          "Detect english"
+         ],
+         [
+          "global",
+          "window,worker"
          ]
         ]
        }
@@ -447063,6 +447815,10 @@
          [
           "title",
           "Detect english"
+         ],
+         [
+          "global",
+          "window,worker"
          ]
         ]
        }
@@ -456184,6 +456940,50 @@
       {}
      ]
     ],
+    "permissions-policy": {
+     "ch-ua-high-entropy-values-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html": [
+      "da66bd0feb9ea8960aef6e076366b974751ecfbb",
+      [
+       null,
+       {}
+      ]
+     ],
+     "ch-ua-high-entropy-values-default-permissions-policy.https.sub.html": [
+      "d73d61d5ac31e2cb7b4deefb78beb7168599d45a",
+      [
+       null,
+       {}
+      ]
+     ],
+     "ch-ua-high-entropy-values-disabled-by-permissions-policy.https.sub.html": [
+      "c86d3829d5c9fb2613fa16d1757c039aa2d3ce1e",
+      [
+       null,
+       {}
+      ]
+     ],
+     "ch-ua-high-entropy-values-enabled-by-permissions-policy.https.sub.html": [
+      "7e05749816173a90a8e710698993b5df071daf34",
+      [
+       null,
+       {}
+      ]
+     ],
+     "ch-ua-high-entropy-values-enabled-on-self-origin-by-permissions-policy.https.sub.html": [
+      "24161b3cc02540c7c74a3883e11958542d59b3cc",
+      [
+       null,
+       {}
+      ]
+     ],
+     "ch-ua-high-entropy-values-permissions-policy-attribute.https.sub.html": [
+      "a6f9bbf73ee6e0ee511bea827e6ff51591df9d00",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
     "sandbox": {
      "iframe-csp-same-origin.https.html": [
       "a5f094af9d4354527ed4ac2401d0b4ef5075e340",
@@ -524396,6 +525196,15 @@
        }
       ]
      ],
+     "keep-collapsible-white-space-after-web-app-delete-padding-br.html": [
+      "7f38f6514ac685b29e0964044196cc32559779e3",
+      [
+       null,
+       {
+        "testdriver": true
+       }
+      ]
+     ],
      "keep-typed-collapsible-white-space-visible-after-muation.html": [
       "9bc02223db5ff5c1b04d93d7cf13562d401bfbf8",
       [
@@ -588300,7 +589109,7 @@
          ]
         ],
         "getContextAttributes.html": [
-         "47b3d96233acf6b879959f48450be36beff99830",
+         "9c98863896573541590351b25b716c2c02aa4497",
          [
           null,
           {}
@@ -599560,6 +600369,28 @@
           {}
          ]
         ],
+        "offscreencanvas.transferrable.sw.https.window.js": [
+         "caa79bafaec50f060e5c4bfa2cc7ba05c9c1436f",
+         [
+          "html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.sw.https.window.html",
+          {
+           "script_metadata": [
+            [
+             "script",
+             "/resources/testdriver.js"
+            ],
+            [
+             "script",
+             "/resources/testdriver-vendor.js"
+            ],
+            [
+             "script",
+             "/service-workers/service-worker/resources/test-helpers.sub.js"
+            ]
+           ]
+          }
+         ]
+        ],
         "offscreencanvas.transferrable.w.html": [
          "38f981e8f09aab76f0ed5131c83e0ee8882f1788",
          [
@@ -624004,6 +624835,13 @@
           {}
          ]
         ],
+        "option-img-alt-text.tentative.html": [
+         "0d47b7fe4826cff7d6598d53df1971091f8dd009",
+         [
+          null,
+          {}
+         ]
+        ],
         "select-accessibility-minimum-target-size.tentative.html": [
          "3155849a5863bf1fd205630317bc7319097c765d",
          [
@@ -654154,6 +654992,17 @@
       {}
      ]
     ],
+    "buffered-flag-with-entryTypes-observer.tentative.any.js": [
+     "493d1dc7a2d4805e56d5f82800eeef84892ed24c",
+     [
+      "performance-timeline/buffered-flag-with-entryTypes-observer.tentative.any.html",
+      {}
+     ],
+     [
+      "performance-timeline/buffered-flag-with-entryTypes-observer.tentative.any.worker.html",
+      {}
+     ]
+    ],
     "case-sensitivity.any.js": [
      "3a98505ae67f7df6f617d6b9fde4af367503278e",
      [
@@ -657722,7 +658571,7 @@
      ]
     ],
     "pointerevent_pointercapture_in_frame.html": [
-     "9c26c4d5a3869bde8e201653f185a6cb023cb779",
+     "2d20a055904ec2be238e7b037522867156813772",
      [
       "pointerevents/pointerevent_pointercapture_in_frame.html?mouse",
       {
@@ -677346,7 +678195,7 @@
      ]
     ],
     "fire-selectionchange-event-on-pressing-backspace.html": [
-     "98d2cc6f365512b18a3fdd977d550bec1901e7c8",
+     "a6b8fd0c9cecbeb082fd796ee72bae64086d9367",
      [
       null,
       {
@@ -704765,7 +705614,7 @@
      ]
     ],
     "single-touch.html": [
-     "57c5cdda7ce3e2f44e4bf5e278a73a04dc6f64a0",
+     "9e8dc0d7bb6a6ee457274ed648e765c10e1b1e89",
      [
       null,
       {
@@ -704968,6 +705817,13 @@
       {}
      ]
     ],
+    "ServiceWorkerContainer-register-from-Worker.https.html": [
+     "a808c0c12b50b1c2d06f74ea45310e8c2a5558df",
+     [
+      null,
+      {}
+     ]
+    ],
     "TrustedType-AttributeNodes.html": [
      "f4269a5d40d02d372c57dd09486f3ff8b3b499a9",
      [
@@ -705148,7 +706004,7 @@
      ]
     ],
     "WorkerGlobalScope-worker-constructor.html": [
-     "86612b9d1d1c3098041963a9b5a66a9c7d415616",
+     "8964c72780b8fb2a575c73a90dd8230f733da8c6",
      [
       null,
       {}
@@ -705161,6 +706017,13 @@
       {}
      ]
     ],
+    "block-eval-function-constructor.html": [
+     "a7d61c86dc0e736ef9a7754b80500714bbee93a4",
+     [
+      null,
+      {}
+     ]
+    ],
     "block-string-assignment-to-DOMParser-parseFromString.html": [
      "6dbebd29a43486c7c9affe8f0962c97d34c062b7",
      [
@@ -705231,6 +706094,13 @@
       {}
      ]
     ],
+    "block-string-assignment-to-HTMLIFrameElement-srcdoc.html": [
+     "b71d838b8512384ec09b73e74ae0de310e85ba1a",
+     [
+      null,
+      {}
+     ]
+    ],
     "block-string-assignment-to-Range-createContextualFragment.html": [
      "55566589deadc94e62eff817bbefbb90c7574fb7",
      [
@@ -705460,7 +706330,7 @@
      ]
     ],
     "trusted-types-createHTMLDocument.html": [
-     "cf209cca80060d709bf975dc69ac158eb3e92835",
+     "38223dac7016eb766d4c3369a4d1c912290bd14b",
      [
       null,
       {}
@@ -705637,7 +706507,7 @@
      ]
     ],
     "useragentdata.https.any.js": [
-     "fa588355181a18f9413f1d13129d1f991837e316",
+     "73059152bd37dfec6a7d31fce605dd6b460982af",
      [
       "ua-client-hints/useragentdata.https.any.html",
       {
@@ -809758,7 +810628,7 @@
        },
        "fetch_error": {
         "fetch_error.py": [
-         "872b4877a8e8e229f5c5e049426dd7370a59b721",
+         "6beb33f7d69a189cd92d9e9ca371002e2b68369c",
          [
           null,
           {}
@@ -810325,6 +811195,13 @@
           null,
           {}
          ]
+        ],
+        "subscription_id.py": [
+         "36a57111f6d503aad3467162c2db102a5608a89d",
+         [
+          null,
+          {}
+         ]
         ]
        },
        "unsubscribe": {
@@ -810343,7 +811220,7 @@
          ]
         ],
         "invalid.py": [
-         "c286bc09ee04d72aa4048e67078084d985bdcdc3",
+         "0b13e949673c198f89aba72388b4230cd8bc0a5b",
          [
           null,
           {}
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-011.html b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-011.html
new file mode 100644
index 0000000..9c03dde
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-011.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>CSS Test: Tables under the collapsing borders model don't have padding</title>
+<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<style>
+table {
+  border-collapse: collapse;
+  box-sizing: content-box;
+  width: 100px;
+  height: 100px;
+  padding: 100px;
+  background: green;
+}
+</style>
+<p>Test passes if there is a filled green square.</p>
+<table></table>
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-012.html b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-012.html
new file mode 100644
index 0000000..39b176ce
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-012.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>CSS Test: Tables under the collapsing borders model don't have padding</title>
+<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<style>
+table {
+  border-collapse: collapse;
+  box-sizing: border-box;
+  width: 100px;
+  height: 100px;
+  padding: 100px;
+  background: green;
+}
+</style>
+<p>Test passes if there is a filled green square.</p>
+<table></table>
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-013.html b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-013.html
new file mode 100644
index 0000000..5e5f576
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-013.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>CSS Test: Tables under the collapsing borders model don't have padding</title>
+<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<style>
+div {
+  float: left;
+  background: green;
+}
+table {
+  border-collapse: collapse;
+  box-sizing: content-box;
+  width: 100px;
+  height: 100px;
+  padding: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div>
+  <table></table>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-014.html b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-014.html
new file mode 100644
index 0000000..1817752
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/tables/collapsing-border-model-014.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>CSS Test: Tables under the collapsing borders model don't have padding</title>
+<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#collapsing-borders">
+<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html">
+<style>
+div {
+  float: left;
+  background: green;
+}
+table {
+  border-collapse: collapse;
+  box-sizing: border-box;
+  width: 100px;
+  height: 100px;
+  padding: 100px;
+}
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div>
+  <table></table>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-auto-tracks-computed.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-auto-tracks-computed.html
new file mode 100644
index 0000000..2581a35
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-auto-tracks-computed.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masonry: masonry-auto-tracks getComputedStyle()</title>
+<link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
+<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>
+</head>
+<body>
+<div id="target"></div>
+<style>
+  #target {
+    font-size: 40px;
+  }
+</style>
+<script>
+// <track-breadth>
+// <track-breadth> = <length-percentage> | <flex> | min-content | max-content | auto
+test_computed_value("masonry-auto-tracks", "1px");
+test_computed_value("masonry-auto-tracks", "calc(10px + 0.5em)", "30px");
+test_computed_value("masonry-auto-tracks", "calc(10px - 0.5em)", "0px");
+test_computed_value("masonry-auto-tracks", "4%");
+test_computed_value("masonry-auto-tracks", "5fr");
+test_computed_value("masonry-auto-tracks", "min-content");
+test_computed_value("masonry-auto-tracks", "max-content");
+test_computed_value("masonry-auto-tracks", "auto");
+
+// minmax( <inflexible-breadth> , <track-breadth> )
+// <inflexible-breadth> = <length-percentage> | min-content | max-content | auto
+test_computed_value("masonry-auto-tracks", "minmax(1px, 5fr)");
+test_computed_value("masonry-auto-tracks", "minmax(calc(10px + 0.5em), max-content)", "minmax(30px, max-content)");
+test_computed_value("masonry-auto-tracks", "minmax(calc(10px - 0.5em), max-content)", "minmax(0px, max-content)");
+test_computed_value("masonry-auto-tracks", "minmax(4%, auto)");
+test_computed_value("masonry-auto-tracks", "minmax(min-content, calc(10px + 0.5em))", "minmax(min-content, 30px)");
+test_computed_value("masonry-auto-tracks", "minmax(auto, 4%)");
+
+// fit-content( <length-percentage> )
+test_computed_value("masonry-auto-tracks", "fit-content(1px)");
+test_computed_value("masonry-auto-tracks", "fit-content(calc(10px + 0.5em))", "fit-content(30px)");
+test_computed_value("masonry-auto-tracks", "fit-content(calc(10px - 0.5em))", "fit-content(0px)");
+test_computed_value("masonry-auto-tracks", "fit-content(4%)");
+
+// 0
+test_computed_value("masonry-auto-tracks", "0px");
+test_computed_value("masonry-auto-tracks", "0%");
+test_computed_value("masonry-auto-tracks", "0fr");
+test_computed_value("masonry-auto-tracks", "minmax(auto, 0%)");
+test_computed_value("masonry-auto-tracks", "fit-content(0px)");
+
+// <track-size>+
+test_computed_value("masonry-auto-tracks", "1px 2px 3px 0px");
+test_computed_value("masonry-auto-tracks", "fit-content(1px) minmax(2px, 3px) 4px");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-auto-tracks-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-auto-tracks-invalid.html
new file mode 100644
index 0000000..e23933e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-auto-tracks-invalid.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masonry: masonry-auto-tracks with invalid values</title>
+<link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
+<meta name="assert" content="masonry-auto-tracks supports only the grammar '<track-size>+'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// <track-breadth>
+test_invalid_value("masonry-auto-tracks", "none");
+test_invalid_value("masonry-auto-tracks", "-1px");
+test_invalid_value("masonry-auto-tracks", "-4%");
+
+// minmax( <inflexible-breadth> , <track-breadth> )
+test_invalid_value("masonry-auto-tracks", "minmax(1px)");
+test_invalid_value("masonry-auto-tracks", "minmax(1px, 2px, 3px)");
+test_invalid_value("masonry-auto-tracks", "minmax(5fr, 1px)");
+test_invalid_value("masonry-auto-tracks", "minmax(6px, -7%)");
+test_invalid_value("masonry-auto-tracks", "minmax(8px, -9fr)");
+
+// fit-content( <length-percentage> )
+test_invalid_value("masonry-auto-tracks", "fit-content(-1px)");
+test_invalid_value("masonry-auto-tracks", "fit-content(1px, 2px)");
+test_invalid_value("masonry-auto-tracks", "fit-content(1px auto)");
+
+// <track-size>+
+test_invalid_value("masonry-auto-tracks", "2em / 3em");
+test_invalid_value("masonry-auto-tracks", "auto, 10%");
+test_invalid_value("masonry-auto-tracks", "1px [a] 1px");
+test_invalid_value("masonry-auto-tracks", "[] 1px []");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-auto-tracks-valid.html b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-auto-tracks-valid.html
new file mode 100644
index 0000000..b0c1424
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masonry/tentative/parsing/masonry-auto-tracks-valid.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masonry: masonry-auto-tracks with valid values</title>
+<link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-3">
+<meta name="assert" content="masonry-auto-tracks supports the full grammar '<track-size>+'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+// <track-breadth>
+// <track-breadth> = <length-percentage> | <flex> | min-content | max-content | auto
+test_valid_value("masonry-auto-tracks", "1px");
+test_valid_value("masonry-auto-tracks", "2em");
+test_valid_value("masonry-auto-tracks", "calc(2em + 3ex)");
+test_valid_value("masonry-auto-tracks", "4%");
+test_valid_value("masonry-auto-tracks", "5fr");
+test_valid_value("masonry-auto-tracks", "min-content");
+test_valid_value("masonry-auto-tracks", "max-content");
+test_valid_value("masonry-auto-tracks", "auto");
+test_valid_value("masonry-auto-tracks", "auto /**/", "auto");
+
+// minmax( <inflexible-breadth> , <track-breadth> )
+// <inflexible-breadth> = <length-percentage> | min-content | max-content | auto
+test_valid_value("masonry-auto-tracks", "minmax(1px, 5fr)");
+test_valid_value("masonry-auto-tracks", "minmax(2em, min-content)");
+test_valid_value("masonry-auto-tracks", "minmax(calc(2em + 3ex), max-content)");
+test_valid_value("masonry-auto-tracks", "minmax(4%, auto)");
+test_valid_value("masonry-auto-tracks", "minmax(5vmin, 1px)");
+test_valid_value("masonry-auto-tracks", "minmax(min-content, 2em)");
+test_valid_value("masonry-auto-tracks", "minmax(max-content, calc(2em + 3ex))");
+test_valid_value("masonry-auto-tracks", "minmax(auto, 4%)");
+
+// fit-content( <length-percentage> )
+test_valid_value("masonry-auto-tracks", "fit-content(1px)");
+test_valid_value("masonry-auto-tracks", "fit-content(2em)");
+test_valid_value("masonry-auto-tracks", "fit-content(calc(2em + 3ex))");
+test_valid_value("masonry-auto-tracks", "fit-content(4%)");
+
+test_valid_value("masonry-auto-tracks", "0px");
+test_valid_value("masonry-auto-tracks", "0%");
+test_valid_value("masonry-auto-tracks", "0fr");
+test_valid_value("masonry-auto-tracks", "minmax(auto, 0%)");
+test_valid_value("masonry-auto-tracks", "fit-content(0px)");
+
+// <track-size>+
+test_valid_value("masonry-auto-tracks", "auto auto");
+test_valid_value("masonry-auto-tracks", "auto 10px");
+test_valid_value("masonry-auto-tracks", "1px 2px 3px 0px");
+test_valid_value("masonry-auto-tracks", "fit-content(1px) minmax(2px, 3px) 4px");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-multicol/multicol-loads-indefinitely-001-crash.html b/third_party/blink/web_tests/external/wpt/css/css-multicol/multicol-loads-indefinitely-001-crash.html
new file mode 100644
index 0000000..c00891c1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-multicol/multicol-loads-indefinitely-001-crash.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1927270">
+
+<style>
+* {
+  max-height: 10vh;
+  border-top-style: dotted;
+  word-break: break-all;
+  columns: 1 0px;
+}
+.a {
+  float: right;
+}
+</style>
+
+<ol>
+<li>
+<div class="a">Cj&quot;hWJ_/[8s</dt>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/selection-target-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/selection-target-expected.txt
deleted file mode 100644
index 15f9d06..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/selection-target-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Test scrolling into view when typing
-  assert_true: Scroll-padding should be respected expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/editing/other/keep-collapsible-white-space-after-web-app-delete-padding-br.html b/third_party/blink/web_tests/external/wpt/editing/other/keep-collapsible-white-space-after-web-app-delete-padding-br.html
new file mode 100644
index 0000000..7f38f65
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/editing/other/keep-collapsible-white-space-after-web-app-delete-padding-br.html
@@ -0,0 +1,94 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>If browsers inserts a br element to make preceding collapsible white-space visible,
+it should be maintained even if the web app deletes the br element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="../include/editor-test-utils.js"></script>
+<script>
+"use strict";
+
+document.addEventListener("DOMContentLoaded", () => {
+  const editingHost = document.querySelector("div[contenteditable]");
+  editingHost.focus();
+  const utils = new EditorTestUtils(editingHost);
+
+  promise_test(async t => {
+    utils.setupEditingHost("abc d[]");
+    await utils.sendBackspaceKey();
+    assert_in_array(
+      editingHost.innerHTML,
+      ["abc <br>", "abc&nbsp;"],
+      `${t.name}: Deleting the first char of the second word should not make the preceding white-space invisible`
+    );
+    const br = editingHost.querySelector("br");
+    if (br) {
+      br.remove();
+      assert_in_array(
+        editingHost.innerHTML,
+        ["abc <br>", "abc&nbsp;"],
+        `${t.name}: Browser should keep the collapsible white-space as visible even if the padding <br> is removed`
+      );
+    }
+    await utils.sendKey("d");
+    assert_equals(
+      editingHost.innerHTML,
+      "abc d",
+      `${t.name}: Typing a character should make the last white-space as an ASCII space and delete the unnecessary <br>`
+    );
+  }, "The last ASCII white-space should be replaced with an NBSP even if <br> is removed by web app");
+
+  promise_test(async t => {
+    utils.setupEditingHost("abc d[]<div>ef</div>");
+    await utils.sendBackspaceKey();
+    assert_in_array(
+      editingHost.innerHTML,
+      ["abc <br><div>ef</div>", "abc&nbsp;<div>ef</div>"],
+      `${t.name}: Deleting the first char of the second word should not make the preceding white-space invisible`
+    );
+    const br = editingHost.querySelector("br");
+    if (br) {
+      br.remove();
+      assert_in_array(
+        editingHost.innerHTML,
+        ["abc <br><div>ef</div>", "abc&nbsp;<div>ef</div>"],
+        `${t.name}: Browser should keep the collapsible white-space as visible even if the padding <br> is removed`
+      );
+    }
+    await utils.sendKey("d");
+    assert_equals(
+      editingHost.innerHTML,
+      "abc d<div>ef</div>",
+      `${t.name}: Typing a character should make the last white-space as an ASCII space and delete the unnecessary <br>`
+    );
+  }, "The last ASCII white-space should be replaced with an NBSP even if <br> followed by a child block boundary is removed by web app");
+
+  promise_test(async t => {
+    utils.setupEditingHost("abc <br>def");
+    editingHost.querySelector("br").remove();
+    assert_equals(
+      editingHost.innerHTML,
+      "abc def"
+    );
+  }, "The last ASCII white-space should not be replaced with an NBSP if following <br> is not a padding <br>");
+
+  promise_test(async t => {
+    utils.setupEditingHost(`<div contenteditable="false">abc <br></div>`);
+    editingHost.querySelector("br").remove();
+    assert_equals(
+      editingHost.innerHTML,
+      `<div contenteditable="false">abc </div>`
+    );
+  }, "The last ASCII white-space in non-editable Text node should not be replaced with an NBSP if following <br> is not a padding <br>");
+}, {once: true});
+</script>
+</head>
+<body>
+  <div contenteditable></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.sw.https.window.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.sw.https.window.js
new file mode 100644
index 0000000..caa79baf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.sw.https.window.js
@@ -0,0 +1,33 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+
+function unregisterAllServiceWorker() {
+  return navigator.serviceWorker.getRegistrations().then(registrations => {
+    return Promise.all(registrations.map(r => r.unregister()));
+  });
+}
+
+async function prepareActiveServiceWorker(script) {
+  await unregisterAllServiceWorker();
+  const reg = await navigator.serviceWorker.register(script);
+  add_completion_callback(() => reg.unregister());
+  await navigator.serviceWorker.ready;
+  return reg;
+}
+
+let registration;
+
+promise_setup(async () => {
+  registration = await prepareActiveServiceWorker("offscreencanvas.transferrable.sw.js");
+});
+
+promise_test(async () => {
+  const canvas = new OffscreenCanvas(100, 100);
+  registration.active.postMessage({ canvas }, { transfer: [canvas] });
+  const data = await new Promise(resolve => navigator.serviceWorker.addEventListener("message", ev => {
+    resolve(ev.data);
+  }, { once: true }));
+  assert_equals(data.constructorName, "OffscreenCanvas", "Should get OffscreenCanvas from the window")
+  assert_true(data.canvas instanceof OffscreenCanvas, "Should get OffscreenCanvas from the service worker");
+}, "Sending and receiving OffscreenCanvas between window and service worker");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.sw.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.sw.js
new file mode 100644
index 0000000..fc7265a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.sw.js
@@ -0,0 +1,12 @@
+onmessage = (ev) => {
+  const constructorName = ev.data.canvas?.constructor.name;
+  const canvas = new OffscreenCanvas(100, 100);
+  ev.source.postMessage({
+    constructorName,
+    canvas
+  }, { transfer: [canvas] });
+}
+
+onmessageerror = (ev) => {
+  ev.source.postMessage({ constructorName: null });
+}
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/select-events.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-events-2.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/select-events.tentative.html
rename to third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-events-2.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/select-pseudo-open-closed.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-pseudo-open.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/select-pseudo-open-closed.tentative.html
rename to third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-pseudo-open.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/select-value-selectedOption.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-value-selectedOption.tentative.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/select-value-selectedOption.tentative.html
rename to third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-value-selectedOption.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/WebCryptoAPI.idl b/third_party/blink/web_tests/external/wpt/interfaces/WebCryptoAPI.idl
index ae85c1c..ff7a89c 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/WebCryptoAPI.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/WebCryptoAPI.idl
@@ -42,52 +42,77 @@
 
 [SecureContext,Exposed=(Window,Worker)]
 interface SubtleCrypto {
-  Promise<any> encrypt(AlgorithmIdentifier algorithm,
-                       CryptoKey key,
-                       BufferSource data);
-  Promise<any> decrypt(AlgorithmIdentifier algorithm,
-                       CryptoKey key,
-                       BufferSource data);
-  Promise<any> sign(AlgorithmIdentifier algorithm,
-                    CryptoKey key,
-                    BufferSource data);
-  Promise<any> verify(AlgorithmIdentifier algorithm,
-                      CryptoKey key,
-                      BufferSource signature,
-                      BufferSource data);
-  Promise<any> digest(AlgorithmIdentifier algorithm,
-                      BufferSource data);
+  Promise<ArrayBuffer> encrypt(
+    AlgorithmIdentifier algorithm,
+    CryptoKey key,
+    BufferSource data
+  );
+  Promise<ArrayBuffer> decrypt(
+    AlgorithmIdentifier algorithm,
+    CryptoKey key,
+    BufferSource data
+  );
+  Promise<ArrayBuffer> sign(
+    AlgorithmIdentifier algorithm,
+    CryptoKey key,
+    BufferSource data
+  );
+  Promise<boolean> verify(
+    AlgorithmIdentifier algorithm,
+    CryptoKey key,
+    BufferSource signature,
+    BufferSource data
+  );
+  Promise<ArrayBuffer> digest(
+    AlgorithmIdentifier algorithm,
+    BufferSource data
+  );
 
-  Promise<any> generateKey(AlgorithmIdentifier algorithm,
-                          boolean extractable,
-                          sequence<KeyUsage> keyUsages );
-  Promise<any> deriveKey(AlgorithmIdentifier algorithm,
-                         CryptoKey baseKey,
-                         AlgorithmIdentifier derivedKeyType,
-                         boolean extractable,
-                         sequence<KeyUsage> keyUsages );
-  Promise<ArrayBuffer> deriveBits(AlgorithmIdentifier algorithm,
-                          CryptoKey baseKey,
-                          optional unsigned long? length = null);
+  Promise<(CryptoKey or CryptoKeyPair)> generateKey(
+    AlgorithmIdentifier algorithm,
+    boolean extractable,
+    sequence<KeyUsage> keyUsages
+  );
+  Promise<CryptoKey> deriveKey(
+    AlgorithmIdentifier algorithm,
+    CryptoKey baseKey,
+    AlgorithmIdentifier derivedKeyType,
+    boolean extractable,
+    sequence<KeyUsage> keyUsages
+  );
+  Promise<ArrayBuffer> deriveBits(
+    AlgorithmIdentifier algorithm,
+    CryptoKey baseKey,
+    optional unsigned long? length = null
+  );
 
-  Promise<CryptoKey> importKey(KeyFormat format,
-                         (BufferSource or JsonWebKey) keyData,
-                         AlgorithmIdentifier algorithm,
-                         boolean extractable,
-                         sequence<KeyUsage> keyUsages );
-  Promise<any> exportKey(KeyFormat format, CryptoKey key);
+  Promise<CryptoKey> importKey(
+    KeyFormat format,
+    (BufferSource or JsonWebKey) keyData,
+    AlgorithmIdentifier algorithm,
+    boolean extractable,
+    sequence<KeyUsage> keyUsages
+  );
+  Promise<(ArrayBuffer or JsonWebKey)> exportKey(
+    KeyFormat format,
+    CryptoKey key
+  );
 
-  Promise<any> wrapKey(KeyFormat format,
-                       CryptoKey key,
-                       CryptoKey wrappingKey,
-                       AlgorithmIdentifier wrapAlgorithm);
-  Promise<CryptoKey> unwrapKey(KeyFormat format,
-                         BufferSource wrappedKey,
-                         CryptoKey unwrappingKey,
-                         AlgorithmIdentifier unwrapAlgorithm,
-                         AlgorithmIdentifier unwrappedKeyAlgorithm,
-                         boolean extractable,
-                         sequence<KeyUsage> keyUsages );
+  Promise<ArrayBuffer> wrapKey(
+    KeyFormat format,
+    CryptoKey key,
+    CryptoKey wrappingKey,
+    AlgorithmIdentifier wrapAlgorithm
+  );
+  Promise<CryptoKey> unwrapKey(
+    KeyFormat format,
+    BufferSource wrappedKey,
+    CryptoKey unwrappingKey,
+    AlgorithmIdentifier unwrapAlgorithm,
+    AlgorithmIdentifier unwrappedKeyAlgorithm,
+    boolean extractable,
+    sequence<KeyUsage> keyUsages
+  );
 };
 
 dictionary RsaOtherPrimesInfo {
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/digital-credentials.idl b/third_party/blink/web_tests/external/wpt/interfaces/digital-credentials.idl
index 2207b25..e20079e 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/digital-credentials.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/digital-credentials.idl
@@ -8,10 +8,10 @@
 };
 
 dictionary DigitalCredentialRequestOptions {
-  sequence<DigitalCredentialsRequest> requests;
+  sequence<DigitalCredentialRequest> requests;
 };
 
-dictionary DigitalCredentialsRequest {
+dictionary DigitalCredentialRequest {
   required DOMString protocol;
   required object data;
 };
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/element-timing.idl b/third_party/blink/web_tests/external/wpt/interfaces/element-timing.idl
index 586b508..ef73ca6 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/element-timing.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/element-timing.idl
@@ -1,7 +1,7 @@
 // GENERATED CONTENT - DO NOT EDIT
 // Content was automatically extracted by Reffy into webref
 // (https://github.com/w3c/webref)
-// Source: Element Timing API (https://wicg.github.io/element-timing/)
+// Source: Element Timing API (https://w3c.github.io/element-timing/)
 
 [Exposed=Window]
 interface PerformanceElementTiming : PerformanceEntry {
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/speech-api.idl b/third_party/blink/web_tests/external/wpt/interfaces/speech-api.idl
index 7408548..f3967b8 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/speech-api.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/speech-api.idl
@@ -1,7 +1,7 @@
 // GENERATED CONTENT - DO NOT EDIT
 // Content was automatically extracted by Reffy into webref
 // (https://github.com/w3c/webref)
-// Source: Web Speech API (https://wicg.github.io/speech-api/)
+// Source: Web Speech API (https://webaudio.github.io/web-speech-api/)
 
 [Exposed=Window]
 interface SpeechRecognition : EventTarget {
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/turtledove.idl b/third_party/blink/web_tests/external/wpt/interfaces/turtledove.idl
index c416760..0507297 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/turtledove.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/turtledove.idl
@@ -41,6 +41,7 @@
   sequence<USVString> trustedBiddingSignalsKeys;
   DOMString trustedBiddingSignalsSlotSizeMode = "none";
   long maxTrustedBiddingSignalsURLLength;
+  USVString trustedBiddingSignalsCoordinator;
   any userBiddingSignals;
   sequence<AuctionAd> ads;
   sequence<AuctionAd> adComponents;
@@ -104,6 +105,7 @@
 
   USVString trustedScoringSignalsURL;
   long maxTrustedScoringSignalsURLLength;
+  USVString trustedScoringSignalsCoordinator;
   sequence<USVString> interestGroupBuyers;
   Promise<any> auctionSignals;
   Promise<any> sellerSignals;
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/wai-aria.idl b/third_party/blink/web_tests/external/wpt/interfaces/wai-aria.idl
index 78083f0..deebc562 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/wai-aria.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/wai-aria.idl
@@ -42,7 +42,7 @@
   [CEReactions] attribute DOMString? ariaPosInSet;
   [CEReactions] attribute DOMString? ariaPressed;
   [CEReactions] attribute DOMString? ariaReadOnly;
-
+  [CEReactions] attribute DOMString? ariaRelevant;
   [CEReactions] attribute DOMString? ariaRequired;
   [CEReactions] attribute DOMString? ariaRoleDescription;
   [CEReactions] attribute DOMString? ariaRowCount;
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl b/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl
index 46e2418..a33c85e 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webauthn.idl
@@ -309,7 +309,7 @@
 };
 
 partial dictionary AuthenticationExtensionsClientInputs {
-  USVString appid;
+  DOMString appid;
 };
 
 partial dictionary AuthenticationExtensionsClientOutputs {
@@ -317,7 +317,7 @@
 };
 
 partial dictionary AuthenticationExtensionsClientInputs {
-  USVString appidExclude;
+  DOMString appidExclude;
 };
 
 partial dictionary AuthenticationExtensionsClientOutputs {
@@ -343,7 +343,7 @@
 
 dictionary AuthenticationExtensionsPRFInputs {
     AuthenticationExtensionsPRFValues eval;
-    record<USVString, AuthenticationExtensionsPRFValues> evalByCredential;
+    record<DOMString, AuthenticationExtensionsPRFValues> evalByCredential;
 };
 
 partial dictionary AuthenticationExtensionsClientInputs {
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/buffered-flag-with-entryTypes-observer.tentative.any.js b/third_party/blink/web_tests/external/wpt/performance-timeline/buffered-flag-with-entryTypes-observer.tentative.any.js
new file mode 100644
index 0000000..493d1dc7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/buffered-flag-with-entryTypes-observer.tentative.any.js
@@ -0,0 +1,13 @@
+async_test(t => {
+  performance.mark('foo');
+  // Use a timeout to ensure the remainder of the test runs after the entry is created.
+  t.step_timeout(() => {
+    // `buffered` flag set to true but with entryTypes so that
+    // the `buffered` flag should be ignored, thus there should be no entry.
+    new PerformanceObserver(() => {
+      assert_unreached('Should not have observed any entry!');
+    }).observe({entryTypes: ['mark'], buffered: true});
+    // Use a timeout to give time to the observer.
+    t.step_timeout(t.step_func_done(() => {}), 100);
+  }, 0);
+}, 'PerformanceObserver without buffered flag set to false cannot see past entries.');
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouseevent_key_pressed-expected.txt b/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouseevent_key_pressed-expected.txt
deleted file mode 100644
index 74c68e81..0000000
--- a/third_party/blink/web_tests/external/wpt/pointerevents/compat/pointerevent_mouseevent_key_pressed-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Pointer events correctly show Alt pressed
-  promise_test: Unhandled rejection with value: object "Error: We do not support keydown and keyup mixed with other actions, please use test_driver.send_keys. See crbug.com/893480."
-[FAIL] Pointer events correctly show Control pressed
-  promise_test: Unhandled rejection with value: object "Error: We do not support keydown and keyup mixed with other actions, please use test_driver.send_keys. See crbug.com/893480."
-[FAIL] Pointer events correctly show Meta pressed
-  promise_test: Unhandled rejection with value: object "Error: We do not support keydown and keyup mixed with other actions, please use test_driver.send_keys. See crbug.com/893480."
-[FAIL] Pointer events correctly show Shift pressed
-  promise_test: Unhandled rejection with value: object "Error: We do not support keydown and keyup mixed with other actions, please use test_driver.send_keys. See crbug.com/893480."
-[FAIL] Pointer events correctly show Alt,Control,Meta,Shift pressed
-  promise_test: Unhandled rejection with value: object "Error: We do not support keydown and keyup mixed with other actions, please use test_driver.send_keys. See crbug.com/893480."
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointermove_after_pointerup_target_removed-expected.txt b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointermove_after_pointerup_target_removed-expected.txt
deleted file mode 100644
index 8d5fc17..0000000
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointermove_after_pointerup_target_removed-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] pointermove after pointerup which deletes the overlay should not keep expanding selection
-  promise_test: Unhandled rejection with value: object "Error: element event-dispatch intercepted error"
-[FAIL] pointermove after pointerup which deletes the overlay and move focus to the button should not keep expanding selection
-  promise_test: Unhandled rejection with value: object "Error: element event-dispatch intercepted error"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/screen-orientation/event-before-promise-expected.txt b/third_party/blink/web_tests/external/wpt/screen-orientation/event-before-promise-expected.txt
deleted file mode 100644
index fe1fb673..0000000
--- a/third_party/blink/web_tests/external/wpt/screen-orientation/event-before-promise-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] The 'change' event must fire before the [[orientationPendingPromise]] is resolved.
-  assert_true: Expected an instance of Event expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/screen-orientation/lock-basic-expected.txt b/third_party/blink/web_tests/external/wpt/screen-orientation/lock-basic-expected.txt
deleted file mode 100644
index da1b5878..0000000
--- a/third_party/blink/web_tests/external/wpt/screen-orientation/lock-basic-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Test that screen.orientation.lock returns a pending promise.
-  assert_true: Expected landscape orientation for "landscape", got "portrait-primary" expected true got false
-[FAIL] Test that screen.orientation.lock() is actually async
-  assert_true: Expected type to start with landscape, got "portrait-primary" expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/screen-orientation/onchange-event-expected.txt b/third_party/blink/web_tests/external/wpt/screen-orientation/onchange-event-expected.txt
deleted file mode 100644
index 052fa4e..0000000
--- a/third_party/blink/web_tests/external/wpt/screen-orientation/onchange-event-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Test that orientationchange event is fired when the orientation changes.
-  assert_true: The event must be fired first. expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/ServiceWorkerContainer-register-from-Worker.https.html b/third_party/blink/web_tests/external/wpt/trusted-types/ServiceWorkerContainer-register-from-Worker.https.html
new file mode 100644
index 0000000..a808c0c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/ServiceWorkerContainer-register-from-Worker.https.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html>
+<head>
+  <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script';">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id=log></div>
+
+<script>
+
+// This test checks ServiceWorkerContainer.register() from Worker scopes and
+// follows the same logic as WorkerGlobalScope-importScripts/eval. For the case
+// when it's called from Window scope, see worker-constructor.https.html.
+
+const test_setup_policy = trustedTypes.createPolicy("hurrayanythinggoes", {
+  createScriptURL: x => x});
+const test_url =
+  test_setup_policy.createScriptURL("support/ServiceWorkerContainer-register.https.js");
+
+fetch_tests_from_worker(new Worker(test_url));
+
+fetch_tests_from_worker(new SharedWorker(test_url));
+
+if ('serviceWorker' in navigator) {
+  (async function() {
+      const scope = 'support/some/scope/for/this/test';
+      let reg = await navigator.serviceWorker.getRegistration(scope);
+      if (reg) await reg.unregister();
+      reg = await navigator.serviceWorker.register(test_url, {scope});
+      fetch_tests_from_worker(reg.installing);
+  })();
+}
+
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-worker-constructor-expected.txt b/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-worker-constructor-expected.txt
new file mode 100644
index 0000000..17ab3eb5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-worker-constructor-expected.txt
@@ -0,0 +1,9 @@
+This is a testharness.js-based test.
+[FAIL] Creating a Worker from a string should throw (shared worker scope)
+  assert_throws_js: Creating a Worker threw function "() => { new Worker("w"); }" threw object "ReferenceError: Worker is not defined" ("ReferenceError") expected instance of function "function TypeError() { [native code] }" ("TypeError")
+[FAIL] Creating a Worker from a TrustedScriptURL should not throw (shared worker scope)
+  Worker is not defined
+[FAIL] Creating a Worker from a string with a default policy should not throw (shared worker scope)
+  Worker is not defined
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-worker-constructor.html b/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-worker-constructor.html
index 86612b9..8964c72 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-worker-constructor.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-worker-constructor.html
@@ -9,6 +9,9 @@
   <script>
     fetch_tests_from_worker(new Worker(
       "support/WorkerGlobalScope-worker-constructor.js"));
+
+    fetch_tests_from_worker(new SharedWorker(
+      "support/WorkerGlobalScope-worker-constructor.js"));
   </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-eval-function-constructor.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-eval-function-constructor.html
new file mode 100644
index 0000000..a7d61c8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-eval-function-constructor.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<link rel="help" href="https://w3c.github.io/webappsec-csp/#can-compile-strings">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/helper.sub.js"></script>
+<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'">
+<script src="support/block-eval-function-constructor.js"></script>
+<script>
+  const testSetupPolicy = trustedTypes.createPolicy("p",
+    { createScriptURL: s => s }
+  );
+
+  fetch_tests_from_worker(new Worker(
+    testSetupPolicy.createScriptURL("support/block-eval-function-constructor-worker.js")
+  ));
+
+  fetch_tests_from_worker(new SharedWorker(
+    testSetupPolicy.createScriptURL("support/block-eval-function-constructor-worker.js")
+  ));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-HTMLIFrameElement-srcdoc.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-HTMLIFrameElement-srcdoc.html
new file mode 100644
index 0000000..b71d838
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-HTMLIFrameElement-srcdoc.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/helper.sub.js"></script>
+
+<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script';">
+<body>
+<script>
+  // TrustedHTML assignments do not throw.
+  test(t => {
+    let p = createHTML_policy(window, 1);
+    let html = p.createHTML(INPUTS.HTML);
+    let iframe = document.createElement("iframe");
+    iframe.srcdoc = html;
+    assert_equals(iframe.srcdoc, RESULTS.HTML);
+  }, "iframe.srcdoc assigned via policy (successful HTML transformation).");
+
+  // String assignments throw.
+  test(t => {
+    let iframe = document.createElement("iframe");
+    assert_throws_js(TypeError, _ => {
+      iframe.srcdoc = "A string";
+    });
+  }, "`iframe.srcdoc = string` throws.");
+
+  // Null assignment throws.
+  test(t => {
+    let iframe = document.createElement("iframe");
+    assert_throws_js(TypeError, _ => {
+      iframe.srcdoc = null;
+    });
+  }, "`iframe.srcdoc = null` throws.");
+
+  // After default policy creation string assignment implicitly calls createHTML
+  test(t => {
+    let p = window.trustedTypes.createPolicy("default", { createHTML:
+      (value, _, sink) => {
+        assert_equals(sink, "HTMLIFrameElement srcdoc");
+        return createHTMLJS(value);
+      }
+    });
+
+    let iframe = document.createElement("iframe");
+    iframe.srcdoc = INPUTS.HTML;
+    assert_equals(iframe.srcdoc, RESULTS.HTML);
+  }, "`iframe.srcdoc = string` assigned via default policy (successful HTML transformation).");
+
+  // After default policy creation null assignment implicitly calls createHTML.
+  test(t => {
+    let iframe = document.createElement("iframe");
+    iframe.srcdoc = null;
+    assert_equals(iframe.srcdoc, "null");
+  }, "`iframe.srcdoc = null` assigned via default policy does not throw");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/ServiceWorkerContainer-register.https.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/ServiceWorkerContainer-register.https.js
new file mode 100644
index 0000000..150da42
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/ServiceWorkerContainer-register.https.js
@@ -0,0 +1,66 @@
+let test_setup_policy = trustedTypes.createPolicy("hurrayanythinggoes", {
+  createScriptURL: x => x
+});
+importScripts(test_setup_policy.createScriptURL("/resources/testharness.js"));
+
+// Determine worker type (for better logging)
+let worker_type = "unknown";
+if (this.DedicatedWorkerGlobalScope !== undefined) {
+  worker_type = "dedicated worker";
+} else if (this.SharedWorkerGlobalScope !== undefined) {
+  worker_type = "shared worker";
+} else if (this.ServiceWorkerGlobalScope !== undefined) {
+  worker_type = "service worker";
+}
+
+let test_policy = trustedTypes.createPolicy("xxx", {
+  createScriptURL: url => url.replace("play", "work")
+});
+
+promise_test(async t => {
+  assert_true("navigator" in self);
+  assert_true(self.navigator instanceof WorkerNavigator);
+}, `WorkerNavigator exposed in ${worker_type}`);
+
+if ('serviceWorker' in navigator) {
+
+  // Passing a trusted type to register() should work.
+  promise_test(async t => {
+    let trusted_url = test_policy.createScriptURL("player.https.js");
+    assert_true(this.trustedTypes.isScriptURL(trusted_url));
+    const scope = `scope1/for/${worker_type}`;
+    let reg = await self.navigator.serviceWorker.getRegistration(scope);
+    if (reg) await reg.unregister();
+    reg = await self.navigator.serviceWorker.register(trusted_url, {scope});
+    await new Promise(r => reg.addEventListener("updatefound", r));
+  }, `register() with TrustedScriptURL works in ${worker_type}`);
+
+  // Passing a plain string to register() should fail.
+  promise_test(async t => {
+    let untrusted_url = "worker.https.js";
+    const scope = `scope2/for/${worker_type}`;
+    let reg = await self.navigator.serviceWorker.getRegistration(scope);
+    if (reg) await reg.unregister();
+    promise_rejects_js(t, TypeError, self.navigator.serviceWorker.register(untrusted_url, {scope}));
+  }, `register() fails with plain string in ${worker_type}`);
+
+  // Passing a plain string to register() should work after registering a
+  // default policy.
+  promise_test(async t => {
+    trustedTypes.createPolicy("default", {
+      createScriptURL: (url, _, sink) => {
+        assert_equals(sink, "ServiceWorkerContainer register");
+        return url.replace("play", "work");
+      }
+    });
+
+    let untrusted_url = "player.https.js";
+    const scope = `scope3/for/${worker_type}`;
+    let reg = await self.navigator.serviceWorker.getRegistration(scope);
+    if (reg) await reg.unregister();
+    reg = await self.navigator.serviceWorker.register(untrusted_url, {scope});
+    await new Promise(r => reg.addEventListener("updatefound", r));
+  }, `register() fails with plain string in ${worker_type} with a default policy`);
+}
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/ServiceWorkerContainer-register.https.js.headers b/third_party/blink/web_tests/external/wpt/trusted-types/support/ServiceWorkerContainer-register.https.js.headers
new file mode 100644
index 0000000..604e765
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/ServiceWorkerContainer-register.https.js.headers
@@ -0,0 +1 @@
+Content-Security-Policy: require-trusted-types-for 'script';
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-importScripts.https.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-importScripts.https.js
index c40e8550..a3ecfc48 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-importScripts.https.js
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-importScripts.https.js
@@ -64,7 +64,10 @@
 
 // Test default policy application:
 trustedTypes.createPolicy("default", {
-  createScriptURL: url => url.replace("play", "work")
+  createScriptURL: (url, _, sink) => {
+    assert_equals(sink, "Worker importScripts");
+    return url.replace("play", "work");
+  }
 }, true);
 test(t => {
   self.result = "Fail";
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-worker-constructor.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-worker-constructor.js
index 7306b18..e45a92d 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-worker-constructor.js
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-worker-constructor.js
@@ -4,20 +4,33 @@
 
 importScripts(test_setup_policy.createScriptURL("/resources/testharness.js"));
 
+// Determine worker type (for better logging)
+let worker_type = "unknown";
+if (this.DedicatedWorkerGlobalScope !== undefined) {
+  worker_type = "dedicated worker";
+} else if (this.SharedWorkerGlobalScope !== undefined) {
+  worker_type = "shared worker";
+}
+
 test(() => {
   assert_throws_js(TypeError, () => { new Worker("w"); },
     "Creating a Worker threw");
-}, "Creating a Worker from a string should throw");
+}, `Creating a Worker from a string should throw (${worker_type} scope)`);
 
 test(() => {
   new Worker(test_setup_policy.createScriptURL("u"));
-}, "Creating a Worker from a TrustedScriptURL should not throw");
+}, `Creating a Worker from a TrustedScriptURL should not throw (${worker_type} scope)`);
 
 test(() => {
   trustedTypes.createPolicy("default",
-    { createScriptURL: s => "defaultValue" });
+    { createScriptURL: (s, _, sink) => {
+        assert_equals(sink, 'Worker constructor');
+        return "defaultValue";
+      }
+    }
+  );
 
   new Worker("s");
-}, "Creating a Worker from a string with a default policy should not throw");
+}, `Creating a Worker from a string with a default policy should not throw (${worker_type} scope)`);
 
 done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/block-eval-function-constructor-worker.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/block-eval-function-constructor-worker.js
new file mode 100644
index 0000000..0a74a1cd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/block-eval-function-constructor-worker.js
@@ -0,0 +1,10 @@
+const testSetupPolicy = trustedTypes.createPolicy("p", { createScriptURL: s => s });
+
+importScripts(testSetupPolicy.createScriptURL("/resources/testharness.js"));
+importScripts(testSetupPolicy.createScriptURL("helper.sub.js"));
+
+importScripts(testSetupPolicy.createScriptURL(
+    "block-eval-function-constructor.js"
+));
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/block-eval-function-constructor.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/block-eval-function-constructor.js
new file mode 100644
index 0000000..83bb6063
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/block-eval-function-constructor.js
@@ -0,0 +1,33 @@
+const globalThisStr = getGlobalThisStr();
+
+let compilationSink = null;
+function resetSinkName() { compilationSink = null; }
+
+trustedTypes.createPolicy("default", { createScript: (s, _, sink) => {
+  compilationSink = sink;
+  return `modified '${s}'`;
+}});
+
+test(t => {
+  t.add_cleanup(resetSinkName);
+  assert_throws_js(EvalError, _ => eval("'42'"));
+  assert_equals(compilationSink, "eval");
+}, `Blocked eval in ${globalThisStr}.`);
+
+test(t => {
+  t.add_cleanup(resetSinkName);
+  assert_throws_js(EvalError, _ => eval?.("'42'"));
+  assert_equals(compilationSink, "eval");
+}, `Blocked indirect eval in ${globalThisStr}.`);
+
+const AsyncFunction = async function() {}.constructor;
+const GeneratorFunction = function*() {}.constructor;
+const AsyncGeneratorFunction = async function*() {}.constructor;
+
+[Function, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction].forEach(functionConstructor => {
+  test(t => {
+    t.add_cleanup(resetSinkName);
+    assert_throws_js(EvalError, _ => new functionConstructor("return;"));
+    assert_equals(compilationSink, "Function");
+  }, `Blocked ${functionConstructor.name} constructor in ${globalThisStr}.`);
+});
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/worker.https.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/worker.https.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/worker.https.js
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-createHTMLDocument.html b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-createHTMLDocument.html
index cf209cc..38223da 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-createHTMLDocument.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-createHTMLDocument.html
@@ -43,10 +43,13 @@
   doc_test(doc_type, doc => {
     const policy = trustedTypes.createPolicy("policy", {createHTML: x => x });
     const value = policy.createHTML("hello");
-    doc.body.innerHTML = value;
-    assert_equals(doc.body.textContent, "hello");
+    const div = doc.createElement("div");
+    doc.body.appendChild(div);
+    div.innerHTML = value;
+    assert_equals(div.textContent, "hello");
     assert_throws_js(TypeError,
-                     _ => { doc.body.innerHTML = "world"; });
+                     _ => { div.innerHTML = "world"; });
+    div.remove();
   }, "Trusted Type instances created in the main doc can be used.");
 }
 
@@ -67,8 +70,11 @@
 
 for (let doc_type in doc_types) {
   doc_test(doc_type, doc => {
-    doc.body.innerHTML = "shouldpass";
-    assert_equals(doc.body.textContent, "shouldpass [default]");
+    const div = doc.createElement("div");
+    doc.body.appendChild(div);
+    div.innerHTML = "shouldpass";
+    assert_equals(div.textContent, "shouldpass [default]");
+    div.remove();
   },  "Default policy applies.");
 }
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/fetch_error/fetch_error.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/fetch_error/fetch_error.py
index 872b487..6beb33f 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/fetch_error/fetch_error.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/network/fetch_error/fetch_error.py
@@ -86,9 +86,14 @@
     )
     on_fetch_error = wait_for_event(FETCH_ERROR_EVENT)
     asyncio.ensure_future(
-        fetch(PAGE_INVALID_URL, context=new_tab, timeout_in_seconds=0)
+        fetch(slow_url, context=new_tab, timeout_in_seconds=0)
     )
     fetch_error_event = await wait_for_future_safe(on_fetch_error)
+    assert_fetch_error_event(
+        fetch_error_event,
+        expected_request={"url": slow_url},
+        context=new_tab["context"],
+    )
 
 
 @pytest.mark.asyncio
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/session/subscribe/subscription_id.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/session/subscribe/subscription_id.py
new file mode 100644
index 0000000..36a5711
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/session/subscribe/subscription_id.py
@@ -0,0 +1,8 @@
+import pytest
+import uuid
+
+@pytest.mark.asyncio
+async def test_subscribe_subscription_id(subscribe_events):
+    result = await subscribe_events(events=["browsingContext"])
+    assert isinstance(result['subscription'], str)
+    uuid.UUID(hex=result['subscription'])
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/session/unsubscribe/invalid.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/session/unsubscribe/invalid.py
index c286bc09..0b13e949 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/session/unsubscribe/invalid.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/session/unsubscribe/invalid.py
@@ -198,3 +198,17 @@
     # Try to unsubscribe from one context
     with pytest.raises(InvalidArgumentException):
         await bidi_session.session.unsubscribe(events=["log.entryAdded"], contexts=[top_context["context"]])
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("subscriptions", [None, True, 42, {}, "foo"])
+async def test_params_subscriptions_invalid_type(bidi_session, subscriptions):
+    with pytest.raises(InvalidArgumentException):
+        await bidi_session.session.unsubscribe(subscriptions=subscriptions)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("subscriptions", [[""], ["12345678-1234-5678-1234-567812345678"]])
+async def test_params_subscriptions_invalid_value(bidi_session, subscriptions):
+    with pytest.raises(InvalidArgumentException):
+        await bidi_session.session.unsubscribe(subscriptions=subscriptions)
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/fixtures_bidi.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/fixtures_bidi.py
index 241ba528..ec36eb9 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/fixtures_bidi.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/fixtures_bidi.py
@@ -65,8 +65,9 @@
     subscriptions = []
 
     async def subscribe_events(events, contexts=None):
-        await bidi_session.session.subscribe(events=events, contexts=contexts)
+        result = await bidi_session.session.subscribe(events=events, contexts=contexts)
         subscriptions.append((events, contexts))
+        return result
 
     yield subscribe_events
 
diff --git a/third_party/blink/web_tests/external/wpt/websockets/cookies/006_wss_wpt_flags=https-expected.txt b/third_party/blink/web_tests/external/wpt/websockets/cookies/006_wss_wpt_flags=https-expected.txt
deleted file mode 100644
index 07c06008..0000000
--- a/third_party/blink/web_tests/external/wpt/websockets/cookies/006_wss_wpt_flags=https-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] WebSockets: setting Secure cookie with document.cookie, checking ws request
-  assert_true: cookie should have been visible to wss expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
index 6384093..0630ac2 100644
--- a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
+++ b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
@@ -245,6 +245,7 @@
 mask-repeat: repeat
 mask-size: auto
 mask-type: luminance
+masonry-auto-tracks: auto
 masonry-direction: column
 masonry-fill: normal
 masonry-slack: normal
diff --git a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
index 050e8c77..cdd797e5 100644
--- a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
+++ b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
@@ -245,6 +245,7 @@
 mask-repeat: repeat
 mask-size: auto
 mask-type: luminance
+masonry-auto-tracks: auto
 masonry-direction: column
 masonry-fill: normal
 masonry-slack: normal
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/session/subscribe/subscription_id-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/session/subscribe/subscription_id-expected.txt
new file mode 100644
index 0000000..9982474
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/session/subscribe/subscription_id-expected.txt
@@ -0,0 +1,5 @@
+This is a wdspec test.
+[FAIL] test_subscribe_subscription_id
+  KeyError: 'subscription'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/session/unsubscribe/invalid-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/session/unsubscribe/invalid-expected.txt
new file mode 100644
index 0000000..fcef247
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/webdriver/tests/bidi/session/unsubscribe/invalid-expected.txt
@@ -0,0 +1,17 @@
+This is a wdspec test.
+[FAIL] test_params_subscriptions_invalid_type[None]
+  TypeError: unsubscribe() got an unexpected keyword argument 'subscriptions'
+[FAIL] test_params_subscriptions_invalid_type[True]
+  TypeError: unsubscribe() got an unexpected keyword argument 'subscriptions'
+[FAIL] test_params_subscriptions_invalid_type[42]
+  TypeError: unsubscribe() got an unexpected keyword argument 'subscriptions'
+[FAIL] test_params_subscriptions_invalid_type[subscriptions3]
+  TypeError: unsubscribe() got an unexpected keyword argument 'subscriptions'
+[FAIL] test_params_subscriptions_invalid_type[foo]
+  TypeError: unsubscribe() got an unexpected keyword argument 'subscriptions'
+[FAIL] test_params_subscriptions_invalid_value[subscriptions0]
+  TypeError: unsubscribe() got an unexpected keyword argument 'subscriptions'
+[FAIL] test_params_subscriptions_invalid_value[subscriptions1]
+  TypeError: unsubscribe() got an unexpected keyword argument 'subscriptions'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt b/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
index 7c9711a..6b94d8a 100644
--- a/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
+++ b/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
@@ -245,6 +245,7 @@
 mask-repeat: repeat
 mask-size: auto
 mask-type: luminance
+masonry-auto-tracks: auto
 masonry-direction: column
 masonry-fill: normal
 masonry-slack: normal
diff --git a/third_party/blink/web_tests/virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/select-pseudo-open-closed.tentative-expected.txt b/third_party/blink/web_tests/virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-pseudo-open.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/select-pseudo-open-closed.tentative-expected.txt
rename to third_party/blink/web_tests/virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-pseudo-open.tentative-expected.txt
diff --git a/third_party/blink/web_tests/virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/select-value-selectedOption.tentative-expected.txt b/third_party/blink/web_tests/virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-value-selectedOption.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/select-value-selectedOption.tentative-expected.txt
rename to third_party/blink/web_tests/virtual/customizable-select-disabled/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-value-selectedOption.tentative-expected.txt
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/select-pseudo-open-closed.tentative-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-pseudo-open.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/select-pseudo-open-closed.tentative-expected.txt
rename to third_party/blink/web_tests/virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-pseudo-open.tentative-expected.txt
diff --git a/third_party/blink/web_tests/virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/select-value-selectedOption.tentative-expected.txt b/third_party/blink/web_tests/virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-value-selectedOption.tentative-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/select-value-selectedOption.tentative-expected.txt
rename to third_party/blink/web_tests/virtual/select-parser-relaxation/external/wpt/html/semantics/forms/the-select-element/customizable-select/select-value-selectedOption.tentative-expected.txt
diff --git a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
index 6ca754d7..132812f26 100644
--- a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
+++ b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
@@ -287,6 +287,7 @@
 maskRepeat
 maskSize
 maskType
+masonryAutoTracks
 masonryDirection
 masonryFill
 masonryFlow
diff --git a/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt b/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt
index 68891c94..0e9a41a 100644
--- a/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/css-property-listing-expected.txt
@@ -259,6 +259,7 @@
     mask-repeat
     mask-size
     mask-type
+    masonry-auto-tracks
     masonry-direction
     masonry-fill
     masonry-slack
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index 9be189a..7b74300 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit 9be189a60865bf5fed52f2d6dd76ceb54e231ddb
+Subproject commit 7b74300faa5f3675b06c4f10bfaef33c760918e9
diff --git a/third_party/dawn b/third_party/dawn
index 07cbbfb..ab9f198 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 07cbbfbf05a3b1e9f22b3d0ac3203a5a70c15689
+Subproject commit ab9f198d52730b69f4a208c5afd39abb0236f76a
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index ed19c1e..8245ed1 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit ed19c1e8985293025be2e812b86ea7619185fcfd
+Subproject commit 8245ed152847c99e3313079a696637bca1d5bdd7
diff --git a/third_party/fuzztest/src b/third_party/fuzztest/src
index 032f0bd..c99c121 160000
--- a/third_party/fuzztest/src
+++ b/third_party/fuzztest/src
@@ -1 +1 @@
-Subproject commit 032f0bdd8c0a3800eb49131d212142a61df81b0c
+Subproject commit c99c121225fcc175bdc084d83c30f3c806b75afd
diff --git a/third_party/llvm-libc/src b/third_party/llvm-libc/src
index 9ee8901..cf32ae3 160000
--- a/third_party/llvm-libc/src
+++ b/third_party/llvm-libc/src
@@ -1 +1 @@
-Subproject commit 9ee890194fe9d4f39b1d5114c6e291b72e6062dd
+Subproject commit cf32ae379c8968df8be7b8b9b1d69115402bccc4
diff --git a/third_party/perfetto b/third_party/perfetto
index 0893e2a..e324242 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 0893e2af69caf8592f6e38f34ccdd4ad6615de9d
+Subproject commit e324242074e2e64a65e90a2933afd3ca4413554f
diff --git a/third_party/skia b/third_party/skia
index dba7f46..1398cbd 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit dba7f46122ba3b1cdb8890eec92aa7b3534781b6
+Subproject commit 1398cbd6b7f9af9eca3b0b5277fece4cb33a45ff
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index 1874295..9730681 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit 18742954f642e134d9080840bdb2a884aad09776
+Subproject commit 973068171048a6ab4c9d0762d5efdae8c2c1c8c4
diff --git a/third_party/webrtc b/third_party/webrtc
index 2c96934..35b6757 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 2c96934699b1f75572f1dd6f508e85ab0c96a356
+Subproject commit 35b67572f28b865e81bdddfc370214c329e2f285
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 49d11cc..dabdf19 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -17637,6 +17637,7 @@
   <int value="-1134420065" label="CriticalPersistedTabData:disabled"/>
   <int value="-1134412904" label="PrivacySandboxSettings:disabled"/>
   <int value="-1134307340" label="stop-loading-in-background:enabled"/>
+  <int value="-1133983181" label="AccessibilityManifestV3BrailleIme:disabled"/>
   <int value="-1133285806" label="MacLoopbackAudioForScreenShare:disabled"/>
   <int value="-1132704128" label="AndroidPaymentAppsFilter:disabled"/>
   <int value="-1132486267" label="ProductSpecificationsMultiSpecifics:enabled"/>
@@ -24692,6 +24693,7 @@
   <int value="1670161209" label="ClickToOpenPDFPlaceholder:enabled"/>
   <int value="1670799163" label="ChromeHomeShowGoogleGWhenUrlCleared:enabled"/>
   <int value="1671021713" label="AutofillImportDynamicForms:disabled"/>
+  <int value="1671604455" label="AccessibilityManifestV3BrailleIme:enabled"/>
   <int value="1671985641" label="WebViewVulkan:enabled"/>
   <int value="1672062546" label="PasswordStrengthIndicator:disabled"/>
   <int value="1672637465" label="OsIntegrationSubManagers:disabled"/>
@@ -26942,6 +26944,7 @@
   <int value="812" label="masonry-fill"/>
   <int value="813" label="masonry-direction"/>
   <int value="814" label="masonry-flow"/>
+  <int value="815" label="masonry-auto-tracks"/>
 </enum>
 
 <!-- LINT.ThenChange(//third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom:CSSSampleId) -->
diff --git a/tools/metrics/histograms/metadata/ash/enums.xml b/tools/metrics/histograms/metadata/ash/enums.xml
index 9dd25cc4..f525b26 100644
--- a/tools/metrics/histograms/metadata/ash/enums.xml
+++ b/tools/metrics/histograms/metadata/ash/enums.xml
@@ -1610,6 +1610,12 @@
   <int value="8" label="Unexpected click"/>
 </enum>
 
+<enum name="OverviewBasedScreenshotKeyboardType">
+  <int value="0" label="Non-ChromeOS Keyboard"/>
+  <int value="1" label="ChromeOS Keyboard WITH Screenshot key"/>
+  <int value="2" label="ChromeOS Keyboard WITHOUT Screenshot key"/>
+</enum>
+
 <enum name="OverviewDragAction">
   <summary>
     The words clamshell and tablet here refer to the mode at the end of the drag
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 429647dc..3ecfb3a6 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -495,6 +495,23 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.Accelerators.OverviewBasedScreenshot.{ActionName}"
+    enum="OverviewBasedScreenshotKeyboardType" expires_after="2025-05-11">
+  <owner>dpad@google.com</owner>
+  <owner>jimmyxgong@chromium.org</owner>
+  <owner>cros-device-enablement@google.com</owner>
+  <summary>
+    Record which type of keyboard activates a given screenshot action utilizing
+    the overview key. The options are [Non-ChromeOS Keyboard, ChromeOS Keyboard
+    with a screenshot key, or ChromeOS Keyboard without a screesnhot key].
+  </summary>
+  <token key="ActionName">
+    <variant name="TakePartialScreenshot"/>
+    <variant name="TakeScreenshot"/>
+    <variant name="TakeWindowScreenshot"/>
+  </token>
+</histogram>
+
 <histogram name="Ash.Accelerators.Rotation.Usage"
     enum="ScreenRotationAcceleratorAction" expires_after="2023-10-08">
   <owner>jimmyxgong@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 5d0c988..160f8ea 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -27,6 +27,14 @@
   <variant name="PassthroughProgramCache"/>
 </variants>
 
+<variants name="ExoSurfaceCodePaths">
+  <variant name="AppendContentsToFrame"/>
+  <variant name="Occluded"/>
+  <variant name="SolidColorDrawQuad"/>
+  <variant name="TextureDrawQuad"/>
+  <variant name="TileDrawQuad"/>
+</variants>
+
 <variants name="GPUProtectedVideoType">
   <variant name="Clear" summary="Clear"/>
   <variant name="HardwareProtected" summary="HardwareProtected"/>
@@ -1781,6 +1789,19 @@
   </token>
 </histogram>
 
+<histogram name="Graphics.Exo.Surface.{ExoSurfaceCodePaths}" enum="Boolean"
+    expires_after="2025-06-01">
+  <owner>harthuang@google.com</owner>
+  <owner>fangzhoug@chromium.org</owner>
+  <owner>petermcneeley@chromium.org</owner>
+  <owner>chromeos-gfx-display@google.com</owner>
+  <summary>
+    Track usage of different code paths in Exo surface code. We should monitor
+    and remove all deprecated code path if they have never been executed.
+  </summary>
+  <token key="ExoSurfaceCodePaths" variants="ExoSurfaceCodePaths"/>
+</histogram>
+
 <histogram name="Skia.Graphite.PipelineCreationRace"
     enum="SkiaPipelineCreationRace" expires_after="2025-11-06">
   <owner>robertphillips@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index efc1113c4..4501f54 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -2495,6 +2495,21 @@
   </summary>
 </histogram>
 
+<histogram name="Net.HttpStreamPool.QuicTaskTime.{Result}" units="ms"
+    expires_after="2025-02-27">
+  <owner>bashi@chromium.org</owner>
+  <owner>blink-network-stack@google.com</owner>
+  <summary>
+    Time from when a HttpStreamPool::QuicTask attempted a QUIC session to when
+    the task is completed. Recorded for each QuicTask if the task actually
+    attempted a session.
+  </summary>
+  <token key="Result">
+    <variant name="Failure" summary="Attempt failed"/>
+    <variant name="Success" summary="Attempt succeeded"/>
+  </token>
+</histogram>
+
 <histogram name="Net.HttpStreamPool.StreamAttemptCancelCount.{Reason}"
     units="attempts" expires_after="2025-02-27">
   <owner>bashi@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index da5c8f1..317c22e 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -566,7 +566,7 @@
 </histogram>
 
 <histogram name="Network.Ash.Cellular.SimLock.Policy.Notification.Event"
-    enum="SimLockNotificationEvent" expires_after="2025-02-20">
+    enum="SimLockNotificationEvent" expires_after="2025-11-20">
   <owner>hsuregan@chromium.org</owner>
   <owner>nikhilcn@chromium.org</owner>
   <owner>cros-device-enablement@google.com</owner>
@@ -590,7 +590,7 @@
 
 <histogram
     name="Network.Ash.Cellular.SimLock.Policy.{PinLockPolicy}.ActiveSIMLockStatus"
-    enum="SimPinLockType" expires_after="2025-02-20">
+    enum="SimPinLockType" expires_after="2025-11-20">
   <owner>hsuregan@chromium.org</owner>
   <owner>nikhilcn@chromium.org</owner>
   <owner>cros-device-enablement@google.com</owner>
@@ -1180,7 +1180,7 @@
 </histogram>
 
 <histogram name="Network.Cellular.ESim.Installation.NonUserErrorSuccessRate"
-    enum="HermesResponseStatus" expires_after="2025-02-20">
+    enum="HermesResponseStatus" expires_after="2025-11-20">
   <owner>nikhilcn@chromium.org</owner>
   <owner>hsuregan@chromium.org</owner>
   <owner>cros-device-enablement@google.com</owner>
@@ -1395,7 +1395,7 @@
 </histogram>
 
 <histogram name="Network.Cellular.ESim.RefreshInstalledProfilesLatency"
-    units="ms" expires_after="2025-02-20">
+    units="ms" expires_after="2025-11-20">
   <owner>nikhilcn@chromium.org</owner>
   <owner>cros-device-enablement@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index b86d164..f8b50e5 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -1439,7 +1439,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.GmsSafeBrowsingApi.ThreatAttribute{Protocol}"
-    enum="SafeBrowsingApiThreatAttribute" expires_after="2025-01-22">
+    enum="SafeBrowsingApiThreatAttribute" expires_after="2026-01-10">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -1600,7 +1600,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.HPRT.CanGetReputationOfUrl"
-    enum="BooleanCanCheckUrl" expires_after="2025-01-26">
+    enum="BooleanCanCheckUrl" expires_after="2026-01-10">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -2131,7 +2131,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.PageLoadToken.ClearReason"
-    enum="SafeBrowsingPageLoadTokenClearReason" expires_after="2025-01-22">
+    enum="SafeBrowsingPageLoadTokenClearReason" expires_after="2026-01-10">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -3347,7 +3347,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.V4LocalDatabaseManager.ThreatInfoSize"
-    units="verdicts" expires_after="2025-01-22">
+    units="verdicts" expires_after="2026-01-10">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -3423,7 +3423,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.V4ProcessFullUpdate.RemovalsHashesCount"
-    units="entries" expires_after="2025-01-22">
+    units="entries" expires_after="2026-01-10">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/sb_client/histograms.xml b/tools/metrics/histograms/metadata/sb_client/histograms.xml
index bcc4f25c..a09bcbe 100644
--- a/tools/metrics/histograms/metadata/sb_client/histograms.xml
+++ b/tools/metrics/histograms/metadata/sb_client/histograms.xml
@@ -263,23 +263,6 @@
   </summary>
 </histogram>
 
-<histogram name="SBClientDownload.SafeDownloadOpenedLatency2.{ShowAction}"
-    units="ms" expires_after="2025-02-10">
-  <owner>xinghuilu@chromium.org</owner>
-  <owner>chrome-counter-abuse-alerts@google.com</owner>
-  <summary>
-    Records the latency between when a safe download is completed and when the
-    user {ShowAction}. Users can open the download either from the download
-    shelf or from the downloads page. Note that if the user opens the same
-    download for several times, this metric is recorded each time. Excludes
-    downloads that were auto opened.
-  </summary>
-  <token key="ShowAction">
-    <variant name="OpenDirectly" summary="opens the download directly"/>
-    <variant name="ShowInFolder" summary="clicks show in folder"/>
-  </token>
-</histogram>
-
 <histogram name="SBClientDownload.SavePackageFileCount" units="files"
     expires_after="2023-03-19">
   <owner>domfc@chromium.org</owner>
@@ -352,7 +335,7 @@
 
 <histogram name="SBClientDownload.UserGestureFileType.Attributes"
     enum="SBClientDownloadUserGestureFileTypeAttributes"
-    expires_after="2025-01-22">
+    expires_after="2026-01-10">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@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 7b81669..a2b3165 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": "be278d644c8dc049d2c540e3a550d55e5152e702",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/1799f295a91ed394890e40071f3f0badfa47622e/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/e324242074e2e64a65e90a2933afd3ca4413554f/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "a15d8362d80cfd7cd8d785cf6afc22586de688cd",
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "d99ae0124dbfa94f91dd685422c7cb0ffd4052e0",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/1799f295a91ed394890e40071f3f0badfa47622e/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/e324242074e2e64a65e90a2933afd3ca4413554f/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc
index 3758ca6..b8b3f6b6 100644
--- a/ui/accessibility/accessibility_features.cc
+++ b/ui/accessibility/accessibility_features.cc
@@ -250,6 +250,14 @@
   return base::FeatureList::IsEnabled(::features::kAccessibilityShakeToLocate);
 }
 
+BASE_FEATURE(kAccessibilityManifestV3BrailleIme,
+             "AccessibilityManifestV3BrailleIme",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsAccessibilityManifestV3EnabledForBrailleIme() {
+  return base::FeatureList::IsEnabled(
+      ::features::kAccessibilityManifestV3BrailleIme);
+}
+
 BASE_FEATURE(kAccessibilityManifestV3EnhancedNetworkTts,
              "AccessibilityManifestV3EnhancedNetworkTts",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/ui/accessibility/accessibility_features.h b/ui/accessibility/accessibility_features.h
index 319b5c3..76419da2 100644
--- a/ui/accessibility/accessibility_features.h
+++ b/ui/accessibility/accessibility_features.h
@@ -192,6 +192,9 @@
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilitySlowKeys);
 AX_BASE_EXPORT bool IsAccessibilitySlowKeysEnabled();
 
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityManifestV3BrailleIme);
+AX_BASE_EXPORT bool IsAccessibilityManifestV3EnabledForBrailleIme();
+
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityManifestV3EnhancedNetworkTts);
 AX_BASE_EXPORT bool IsAccessibilityManifestV3EnabledForEnhancedNetworkTts();
 
diff --git a/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.cc b/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.cc
index c4bbbfb..cdf35a5 100644
--- a/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.cc
+++ b/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.cc
@@ -21,6 +21,10 @@
 bool GlobalAcceleratorListener::RegisterAccelerator(
     const ui::Accelerator& accelerator,
     Observer* observer) {
+  if (IsShortcutHandlingSuspended()) {
+    return false;
+  }
+
   AcceleratorMap::const_iterator it = accelerator_map_.find(accelerator);
   if (it != accelerator_map_.end()) {
     // The accelerator has been registered.
@@ -44,6 +48,10 @@
 void GlobalAcceleratorListener::UnregisterAccelerator(
     const ui::Accelerator& accelerator,
     Observer* observer) {
+  if (IsShortcutHandlingSuspended()) {
+    return;
+  }
+
   auto it = accelerator_map_.find(accelerator);
   // We should never get asked to unregister something that we didn't register.
   CHECK(it != accelerator_map_.end(), base::NotFatalUntil::M130);
@@ -57,8 +65,11 @@
   }
 }
 
-std::vector<ui::Accelerator> GlobalAcceleratorListener::UnregisterAccelerators(
-    Observer* observer) {
+void GlobalAcceleratorListener::UnregisterAccelerators(Observer* observer) {
+  if (IsShortcutHandlingSuspended()) {
+    return;
+  }
+
   std::vector<ui::Accelerator> removed_accelerators;
 
   auto it = accelerator_map_.begin();
@@ -71,8 +82,30 @@
       ++it;
     }
   }
+}
 
-  return removed_accelerators;
+void GlobalAcceleratorListener::SetShortcutHandlingSuspended(bool suspended) {
+  if (shortcut_handling_suspended_ == suspended) {
+    return;
+  }
+
+  shortcut_handling_suspended_ = suspended;
+  for (auto& it : accelerator_map_) {
+    // On Linux, when shortcut handling is suspended we cannot simply early
+    // return in NotifyKeyPressed (similar to what we do for non-global
+    // shortcuts) because we'd eat the keyboard event thereby preventing the
+    // user from setting the shortcut. Therefore we must unregister while
+    // handling is suspended and register when handling resumes.
+    if (shortcut_handling_suspended_) {
+      StopListeningForAccelerator(it.first);
+    } else {
+      StartListeningForAccelerator(it.first);
+    }
+  }
+}
+
+bool GlobalAcceleratorListener::IsShortcutHandlingSuspended() const {
+  return shortcut_handling_suspended_;
 }
 
 bool GlobalAcceleratorListener::IsRegistrationHandledExternally() const {
diff --git a/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.h b/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.h
index 70f352b..70180869 100644
--- a/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.h
+++ b/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.h
@@ -54,16 +54,15 @@
 
   // Unregister and stop listening for all accelerators of the given `observer`.
   // Returns a vector of the accelerators that were unregistered.
-  std::vector<ui::Accelerator> UnregisterAccelerators(Observer* observer);
+  void UnregisterAccelerators(Observer* observer);
 
-  // Begin listening to an accelerator that has already been registered by
-  // calling `RegisterAccelerator`.
-  virtual bool StartListeningForAccelerator(
-      const ui::Accelerator& accelerator) = 0;
-  // Stop listening to an accelerator that has already been registered by
-  // calling `RegisterAccelerator`.
-  virtual void StopListeningForAccelerator(
-      const ui::Accelerator& accelerator) = 0;
+  // Suspend/Resume global shortcut handling. Note that when suspending,
+  // RegisterAccelerator/UnregisterAccelerator/UnregisterAccelerators are not
+  // allowed to be called until shortcut handling has been resumed.
+  void SetShortcutHandlingSuspended(bool suspended);
+
+  // Returns whether shortcut handling is currently suspended.
+  bool IsShortcutHandlingSuspended() const;
 
   // Called when a group of commands are registered.
   virtual void OnCommandsChanged(const std::string& accelerator_group_id,
@@ -94,11 +93,23 @@
   virtual void StartListening() = 0;
   virtual void StopListening() = 0;
 
+  // Begin listening to an accelerator that has already been registered by
+  // calling `RegisterAccelerator`.
+  virtual bool StartListeningForAccelerator(
+      const ui::Accelerator& accelerator) = 0;
+  // Stop listening to an accelerator that has already been registered by
+  // calling `RegisterAccelerator`.
+  virtual void StopListeningForAccelerator(
+      const ui::Accelerator& accelerator) = 0;
+
   // The map of accelerators that have been successfully registered as global
   // accelerators and their observer.
   typedef std::map<ui::Accelerator, raw_ptr<Observer, CtnExperimental>>
       AcceleratorMap;
   AcceleratorMap accelerator_map_;
+
+  // Keeps track of whether shortcut handling is currently suspended.
+  bool shortcut_handling_suspended_ = false;
 };
 
 }  // namespace ui
diff --git a/ui/events/ash/keyboard_capability.cc b/ui/events/ash/keyboard_capability.cc
index 4c6836ea..e603e12 100644
--- a/ui/events/ash/keyboard_capability.cc
+++ b/ui/events/ash/keyboard_capability.cc
@@ -533,6 +533,16 @@
 }
 
 KeyboardCapability::KeyboardInfo::KeyboardInfo() = default;
+KeyboardCapability::KeyboardInfo::KeyboardInfo(
+    DeviceType device_type,
+    KeyboardTopRowLayout top_row_layout,
+    std::vector<uint32_t> top_row_scan_codes,
+    std::vector<TopRowActionKey> top_row_action_keys)
+    : device_type(device_type),
+      top_row_layout(top_row_layout),
+      top_row_scan_codes(std::move(top_row_scan_codes)),
+      top_row_action_keys(std::move(top_row_action_keys)) {}
+
 KeyboardCapability::KeyboardInfo::KeyboardInfo(KeyboardInfo&&) = default;
 KeyboardCapability::KeyboardInfo& KeyboardCapability::KeyboardInfo::operator=(
     KeyboardInfo&&) = default;
@@ -1308,6 +1318,16 @@
   return base::Contains(keyboard_info->top_row_action_keys, action_key);
 }
 
+bool KeyboardCapability::HasTopRowActionKey(int device_id,
+                                            TopRowActionKey action_key) const {
+  auto keyboard = FindKeyboardWithId(device_id);
+  if (!keyboard) {
+    return false;
+  }
+
+  return HasTopRowActionKey(*keyboard, action_key);
+}
+
 bool KeyboardCapability::HasTopRowActionKeyOnAnyKeyboard(
     TopRowActionKey action_key) const {
   for (const ui::KeyboardDevice& keyboard :
diff --git a/ui/events/ash/keyboard_capability.h b/ui/events/ash/keyboard_capability.h
index 06d355c..f41a881 100644
--- a/ui/events/ash/keyboard_capability.h
+++ b/ui/events/ash/keyboard_capability.h
@@ -184,6 +184,10 @@
 
   struct KeyboardInfo {
     KeyboardInfo();
+    KeyboardInfo(DeviceType device_type,
+                 KeyboardTopRowLayout top_row_layout,
+                 std::vector<uint32_t> top_row_scan_codes,
+                 std::vector<TopRowActionKey> top_row_action_keys);
     KeyboardInfo(KeyboardInfo&&);
     KeyboardInfo& operator=(KeyboardInfo&&);
     KeyboardInfo(const KeyboardInfo&) = delete;
@@ -289,6 +293,7 @@
   // Check if a given `action_key` exists on the given keyboard.
   bool HasTopRowActionKey(const KeyboardDevice& keyboard,
                           TopRowActionKey action_key) const;
+  bool HasTopRowActionKey(int device_id, TopRowActionKey action_key) const;
   bool HasTopRowActionKeyOnAnyKeyboard(TopRowActionKey action_key) const;
 
   // Check if the globe key exists on the given keyboard.
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_feedback.cc b/ui/events/ozone/evdev/libgestures_glue/gesture_feedback.cc
index 2261846..a76ba73 100644
--- a/ui/events/ozone/evdev/libgestures_glue/gesture_feedback.cc
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_feedback.cc
@@ -7,7 +7,10 @@
 #include <stddef.h>
 #include <time.h>
 
+#include <algorithm>
+#include <string>
 #include <utility>
+#include <vector>
 
 #include "base/command_line.h"
 #include "base/functional/bind.h"
@@ -17,6 +20,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/strings/to_string.h"
 #include "base/task/thread_pool.h"
 #include "ui/events/ozone/evdev/libgestures_glue/gesture_property_provider.h"
 
@@ -31,14 +35,11 @@
 
 // Return the values in an array in one string. Used for touch logging.
 template <typename T>
-std::string DumpArrayProperty(const std::vector<T>& value, const char* format) {
-  std::string ret;
-  for (size_t i = 0; i < value.size(); ++i) {
-    if (i > 0)
-      ret.append(", ");
-    ret.append(base::StringPrintfNonConstexpr(format, value[i]));
-  }
-  return ret;
+std::string DumpArrayProperty(const std::vector<T>& value) {
+  std::vector<std::string> strs;
+  strs.reserve(value.size());
+  std::ranges::transform(value, std::back_inserter(strs), &base::ToString<T>);
+  return base::JoinString(strs, ", ");
 }
 
 // Return the values in a gesture property in one string. Used for touch
@@ -46,15 +47,15 @@
 std::string DumpGesturePropertyValue(GesturesProp* property) {
   switch (property->type()) {
     case GesturePropertyProvider::PT_INT:
-      return DumpArrayProperty(property->GetIntValue(), "%d");
+      return DumpArrayProperty(property->GetIntValue());
     case GesturePropertyProvider::PT_SHORT:
-      return DumpArrayProperty(property->GetShortValue(), "%d");
+      return DumpArrayProperty(property->GetShortValue());
     case GesturePropertyProvider::PT_BOOL:
-      return DumpArrayProperty(property->GetBoolValue(), "%d");
+      return DumpArrayProperty(property->GetBoolValue());
     case GesturePropertyProvider::PT_STRING:
       return "\"" + property->GetStringValue() + "\"";
     case GesturePropertyProvider::PT_REAL:
-      return DumpArrayProperty(property->GetDoubleValue(), "%lf");
+      return DumpArrayProperty(property->GetDoubleValue());
     default:
       NOTREACHED();
   }
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
index 2a0b5af..93bb1e4 100644
--- a/ui/gl/BUILD.gn
+++ b/ui/gl/BUILD.gn
@@ -256,8 +256,14 @@
       "swap_chain_presenter.h",
       "vsync_provider_win.cc",
       "vsync_provider_win.h",
+      "vsync_provider_win_dcomp.cc",
+      "vsync_provider_win_dcomp.h",
       "vsync_thread_win.cc",
       "vsync_thread_win.h",
+      "vsync_thread_win_dcomp.cc",
+      "vsync_thread_win_dcomp.h",
+      "vsync_thread_win_dxgi.cc",
+      "vsync_thread_win_dxgi.h",
     ]
 
     libs = [
diff --git a/ui/gl/direct_composition_support.cc b/ui/gl/direct_composition_support.cc
index 424f9fe..d26cfa4b 100644
--- a/ui/gl/direct_composition_support.cc
+++ b/ui/gl/direct_composition_support.cc
@@ -618,6 +618,52 @@
 
 }  // namespace
 
+// Pointers to DirectComposition functions, dcomp.dll loaded at runtime in
+// InitializeDirectComposition when compositor clock vsync interval is enabled.
+// DcompositionWaitForCompositorClock function pointer
+using PFN_DCOMPOSITION_WAIT = HRESULT(WINAPI*)(UINT count,
+                                               const HANDLE* handles,
+                                               DWORD timeoutInMs);
+PFN_DCOMPOSITION_WAIT g_wait_for_compositor_clock_function = nullptr;
+
+// DCompositionGetFrameId function pointer
+using PFN_DCOMPOSITION_GET_FRAME_ID =
+    HRESULT(WINAPI*)(COMPOSITION_FRAME_ID_TYPE frameIdType,
+                     COMPOSITION_FRAME_ID* frameId);
+PFN_DCOMPOSITION_GET_FRAME_ID g_get_frame_id_function = nullptr;
+
+// DCompositionGetStatistics function pointer
+using PFN_DCOMPOSITION_GET_STATISTICS =
+    HRESULT(WINAPI*)(COMPOSITION_FRAME_ID frameId,
+                     COMPOSITION_FRAME_STATS* frameStats,
+                     UINT targetIdCount,
+                     COMPOSITION_TARGET_ID* targetIds,
+                     UINT* actualTargetIdCount);
+PFN_DCOMPOSITION_GET_STATISTICS g_get_statistics_function = nullptr;
+
+HRESULT DCompositionWaitForCompositorClock(UINT count,
+                                           const HANDLE* handles,
+                                           DWORD timeoutInMs) {
+  DCHECK(g_wait_for_compositor_clock_function);
+  return g_wait_for_compositor_clock_function(count, handles, timeoutInMs);
+}
+
+HRESULT DCompositionGetFrameId(COMPOSITION_FRAME_ID_TYPE frameIdType,
+                               COMPOSITION_FRAME_ID* frameId) {
+  DCHECK(g_get_frame_id_function);
+  return g_get_frame_id_function(frameIdType, frameId);
+}
+
+HRESULT DCompositionGetStatistics(COMPOSITION_FRAME_ID frameId,
+                                  COMPOSITION_FRAME_STATS* frameStats,
+                                  UINT targetIdCount,
+                                  COMPOSITION_TARGET_ID* targetIds,
+                                  UINT* actualTargetIdCount) {
+  DCHECK(g_get_statistics_function);
+  return g_get_statistics_function(frameId, frameStats, targetIdCount,
+                                   targetIds, actualTargetIdCount);
+}
+
 void InitializeDirectComposition(
     Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device) {
   DCHECK(!g_dcomp_device);
@@ -675,6 +721,22 @@
 
   g_d3d11_device = d3d11_device.Detach();
 
+  if (features::UseCompositorClockVSyncInterval()) {
+    g_get_frame_id_function = reinterpret_cast<PFN_DCOMPOSITION_GET_FRAME_ID>(
+        ::GetProcAddress(dcomp_module, "DCompositionGetFrameId"));
+    CHECK(g_get_frame_id_function);
+
+    g_get_statistics_function =
+        reinterpret_cast<PFN_DCOMPOSITION_GET_STATISTICS>(
+            ::GetProcAddress(dcomp_module, "DCompositionGetStatistics"));
+    CHECK(g_get_statistics_function);
+
+    g_wait_for_compositor_clock_function =
+        reinterpret_cast<PFN_DCOMPOSITION_WAIT>(::GetProcAddress(
+            dcomp_module, "DCompositionWaitForCompositorClock"));
+    CHECK(g_wait_for_compositor_clock_function);
+  }
+
   UpdateVideoProcessorAutoHDRSupport();
 }
 
diff --git a/ui/gl/direct_composition_support.h b/ui/gl/direct_composition_support.h
index f07c68f6..60adc67 100644
--- a/ui/gl/direct_composition_support.h
+++ b/ui/gl/direct_composition_support.h
@@ -20,6 +20,22 @@
 
 namespace gl {
 
+// Wrapper for DCompositionWaitForCompositorClock Win32 dcomp.h function
+HRESULT DCompositionWaitForCompositorClock(UINT count,
+                                           const HANDLE* handles,
+                                           DWORD timeoutInMs);
+
+// Wrapper for DcompositionGetFrameId Win32 dcomp.h function
+HRESULT DCompositionGetFrameId(COMPOSITION_FRAME_ID_TYPE frameIdType,
+                               COMPOSITION_FRAME_ID* frameId);
+
+// Wrapper for DCompositionGetStatistics Win32 dcomp.h function
+HRESULT DCompositionGetStatistics(COMPOSITION_FRAME_ID frameId,
+                                  COMPOSITION_FRAME_STATS* frameStats,
+                                  UINT targetIdCount,
+                                  COMPOSITION_TARGET_ID* targetIds,
+                                  UINT* actualTargetIdCount);
+
 // Initialize direct composition with the given d3d11 device.
 GL_EXPORT void InitializeDirectComposition(
     Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device);
diff --git a/ui/gl/gl_features.cc b/ui/gl/gl_features.cc
index bb1d50e..70cbbcb 100644
--- a/ui/gl/gl_features.cc
+++ b/ui/gl/gl_features.cc
@@ -7,6 +7,7 @@
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/strings/string_split.h"
+#include "base/win/windows_version.h"
 #include "build/build_config.h"
 #include "ui/gl/gl_switches.h"
 
@@ -161,6 +162,18 @@
 BASE_FEATURE(kUsePrimaryMonitorVSyncIntervalOnSV3,
              "UsePrimaryMonitorVSyncIntervalOnSV3",
              base::FEATURE_ENABLED_BY_DEFAULT);
+
+// If true, VsyncThreadWin will use the compositor clock
+// to determine the vsync interval.
+BASE_FEATURE(kUseCompositorClockVSyncInterval,
+             "UseCompositorClockVSyncInterval",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+bool UseCompositorClockVSyncInterval() {
+  return base::win::GetVersion() >= base::win::Version::WIN11_24H2 &&
+         base::FeatureList::IsEnabled(
+             features::kUseCompositorClockVSyncInterval);
+}
 #endif  // BUILDFLAG(IS_WIN)
 
 bool UseGpuVsync() {
diff --git a/ui/gl/gl_features.h b/ui/gl/gl_features.h
index 40efeaa..83e63d33 100644
--- a/ui/gl/gl_features.h
+++ b/ui/gl/gl_features.h
@@ -18,6 +18,9 @@
 // Controls if GPU should synchronize presentation with vsync.
 GL_EXPORT bool UseGpuVsync();
 
+// Controls if vsync interval should be based on compositor clock.
+GL_EXPORT bool UseCompositorClockVSyncInterval();
+
 #if BUILDFLAG(ENABLE_VALIDATING_COMMAND_DECODER)
 // All features in alphabetical order. The features should be documented
 // alongside the definition of their values in the .cc file.
@@ -32,6 +35,7 @@
 
 #if BUILDFLAG(IS_WIN)
 GL_EXPORT BASE_DECLARE_FEATURE(kUsePrimaryMonitorVSyncIntervalOnSV3);
+GL_EXPORT BASE_DECLARE_FEATURE(kUseCompositorClockVSyncInterval);
 #endif  // BUILDFLAG(IS_WIN)
 
 GL_EXPORT bool IsAndroidFrameDeadlineEnabled();
diff --git a/ui/gl/vsync_provider_win_dcomp.cc b/ui/gl/vsync_provider_win_dcomp.cc
new file mode 100644
index 0000000..50cf777
--- /dev/null
+++ b/ui/gl/vsync_provider_win_dcomp.cc
@@ -0,0 +1,54 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gl/vsync_provider_win_dcomp.h"
+
+#include "base/trace_event/typed_macros.h"
+#include "ui/gl/direct_composition_support.h"
+
+namespace gl {
+
+VSyncProviderWinDComp::VSyncProviderWinDComp() = default;
+
+VSyncProviderWinDComp::~VSyncProviderWinDComp() = default;
+
+void VSyncProviderWinDComp::GetVSyncParameters(UpdateVSyncCallback callback) {
+  base::TimeTicks timebase;
+  base::TimeDelta interval;
+  if (GetVSyncParametersIfAvailable(&timebase, &interval)) {
+    std::move(callback).Run(timebase, interval);
+  }
+}
+
+// Use the compositor clock to determine vsync interval (as opposed
+// to algining with primary monitor) to more optimally align vsync
+// interval to video FPS in multi-display mixed refresh rate or VRR configs.
+bool VSyncProviderWinDComp::GetVSyncParametersIfAvailable(
+    base::TimeTicks* out_timebase,
+    base::TimeDelta* out_interval) {
+  TRACE_EVENT0("gpu", "VSyncProviderWinDComp::GetVSyncIntervalIfAvailable");
+  HRESULT hr = S_OK;
+
+  COMPOSITION_FRAME_ID frame_id;
+  hr = gl::DCompositionGetFrameId(COMPOSITION_FRAME_ID_COMPLETED, &frame_id);
+  CHECK_EQ(S_OK, hr);
+
+  COMPOSITION_FRAME_STATS stats;
+  hr = gl::DCompositionGetStatistics(frame_id, &stats, 0, nullptr, nullptr);
+  CHECK_EQ(S_OK, hr);
+
+  *out_timebase = base::TimeTicks::FromQPCValue(stats.startTime);
+  *out_interval = base::TimeDelta::FromQPCValue(stats.framePeriod);
+  return true;
+}
+
+bool VSyncProviderWinDComp::SupportGetVSyncParametersIfAvailable() const {
+  return true;
+}
+
+bool VSyncProviderWinDComp::IsHWClock() const {
+  return true;
+}
+
+}  // namespace gl
diff --git a/ui/gl/vsync_provider_win_dcomp.h b/ui/gl/vsync_provider_win_dcomp.h
new file mode 100644
index 0000000..4072385e
--- /dev/null
+++ b/ui/gl/vsync_provider_win_dcomp.h
@@ -0,0 +1,33 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GL_VSYNC_PROVIDER_WIN_DCOMP_H_
+#define UI_GL_VSYNC_PROVIDER_WIN_DCOMP_H_
+
+#include "ui/gl/vsync_provider_win.h"
+
+namespace gl {
+// gfx::VSyncProvider implementation that utilizes the compositor clock to
+// determine vsync parameters (as opposed to VSyncProviderWin, where parameters
+// are calculated via DWM or QueryDisplayConfig)
+class GL_EXPORT VSyncProviderWinDComp : public gfx::VSyncProvider {
+ public:
+  VSyncProviderWinDComp();
+
+  VSyncProviderWinDComp(const VSyncProviderWinDComp&) = delete;
+  VSyncProviderWinDComp& operator=(const VSyncProviderWinDComp&) = delete;
+
+  ~VSyncProviderWinDComp() override;
+
+  // gfx::VSyncProvider overrides;
+  void GetVSyncParameters(UpdateVSyncCallback callback) override;
+  bool GetVSyncParametersIfAvailable(base::TimeTicks* timebase,
+                                     base::TimeDelta* interval) override;
+  bool SupportGetVSyncParametersIfAvailable() const override;
+  bool IsHWClock() const override;
+};
+
+}  // namespace gl
+
+#endif  // UI_GL_VSYNC_PROVIDER_WIN_DCOMP_H_
diff --git a/ui/gl/vsync_thread_win.cc b/ui/gl/vsync_thread_win.cc
index f3e35d77..dd405166 100644
--- a/ui/gl/vsync_thread_win.cc
+++ b/ui/gl/vsync_thread_win.cc
@@ -16,129 +16,30 @@
 #include "base/win/windows_version.h"
 #include "ui/gl/direct_composition_support.h"
 #include "ui/gl/gl_features.h"
+#include "ui/gl/vsync_thread_win_dcomp.h"
+#include "ui/gl/vsync_thread_win_dxgi.h"
 
 namespace gl {
 namespace {
-
 // Whether the current thread holds the `VSyncThreadWin` lock.
 thread_local bool g_current_thread_holds_lock = false;
-
-// Check if a DXGI adapter is stale and needs to be replaced. This can happen
-// e.g. when detaching/reattaching remote desktop sessions and causes subsequent
-// WaitForVSyncs on the stale adapter/output to return instantly.
-bool DXGIFactoryIsCurrent(IDXGIAdapter* dxgi_adapter) {
-  CHECK(dxgi_adapter);
-
-  HRESULT hr = S_OK;
-  Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory;
-  hr = dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory));
-  CHECK_EQ(S_OK, hr);
-  return dxgi_factory->IsCurrent();
-}
-
-// Create a new factory and find a DXGI adapter matching a LUID. This is useful
-// if we have a previous adapter whose factory has become stale.
-Microsoft::WRL::ComPtr<IDXGIAdapter> FindDXGIAdapterOnNewFactory(
-    const LUID luid) {
-  HRESULT hr = S_OK;
-
-  Microsoft::WRL::ComPtr<IDXGIFactory1> factory;
-  hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
-  CHECK_EQ(S_OK, hr);
-
-  Microsoft::WRL::ComPtr<IDXGIAdapter1> new_adapter;
-  for (uint32_t i = 0;; i++) {
-    hr = factory->EnumAdapters1(i, &new_adapter);
-    if (hr == DXGI_ERROR_NOT_FOUND) {
-      break;
-    }
-    if (FAILED(hr)) {
-      DLOG(ERROR) << "EnumAdapters1 failed: "
-                  << logging::SystemErrorCodeToString(hr);
-      return nullptr;
-    }
-
-    DXGI_ADAPTER_DESC1 new_adapter_desc;
-    hr = new_adapter->GetDesc1(&new_adapter_desc);
-    CHECK_EQ(S_OK, hr);
-
-    if (new_adapter_desc.AdapterLuid.HighPart == luid.HighPart &&
-        new_adapter_desc.AdapterLuid.LowPart == luid.LowPart) {
-      return new_adapter;
-    }
-  }
-
-  DLOG(ERROR) << "Failed to find DXGI adapter with matching LUID";
-  return nullptr;
-}
-
-// Return true if |output| is on |monitor|.
-bool DXGIOutputIsOnMonitor(IDXGIOutput* output, const HMONITOR monitor) {
-  CHECK(output);
-
-  DXGI_OUTPUT_DESC desc = {};
-  HRESULT hr = output->GetDesc(&desc);
-  CHECK_EQ(S_OK, hr);
-  return desc.Monitor == monitor;
-}
-
-Microsoft::WRL::ComPtr<IDXGIOutput> DXGIOutputFromMonitor(
-    HMONITOR monitor,
-    IDXGIAdapter* dxgi_adapter) {
-  CHECK(dxgi_adapter);
-
-  HRESULT hr = S_OK;
-
-  Microsoft::WRL::ComPtr<IDXGIOutput> output;
-  for (uint32_t i = 0;; i++) {
-    hr = dxgi_adapter->EnumOutputs(i, &output);
-    if (hr == DXGI_ERROR_NOT_FOUND) {
-      break;
-    }
-    if (FAILED(hr)) {
-      DLOG(ERROR) << "EnumOutputs failed: "
-                  << logging::SystemErrorCodeToString(hr);
-      return nullptr;
-    }
-
-    if (DXGIOutputIsOnMonitor(output.Get(), monitor)) {
-      return output;
-    }
-  }
-
-  DLOG(ERROR) << "Failed to find DXGI output with matching monitor";
-  return nullptr;
-}
-
-Microsoft::WRL::ComPtr<IDXGIAdapter> GetAdapter(IDXGIDevice* device) {
-  CHECK(device);
-
-  Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
-  CHECK_EQ(S_OK, device->GetAdapter(&adapter));
-  return adapter;
-}
-
-LUID GetLuid(IDXGIAdapter* adapter) {
-  CHECK(adapter);
-
-  DXGI_ADAPTER_DESC desc;
-  HRESULT hr = adapter->GetDesc(&desc);
-  CHECK_EQ(S_OK, hr);
-  return desc.AdapterLuid;
-}
 }  // namespace
 
 // static
 VSyncThreadWin* VSyncThreadWin::GetInstance() {
   static VSyncThreadWin* vsync_thread = []() -> VSyncThreadWin* {
-    Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device(
-        GetDirectCompositionD3D11Device());
-    if (!d3d11_device) {
-      return nullptr;
+    if (features::UseCompositorClockVSyncInterval()) {
+      return new VSyncThreadWinDComp();
+    } else {
+      Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device(
+          GetDirectCompositionD3D11Device());
+      if (!d3d11_device) {
+        return nullptr;
+      }
+      Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
+      CHECK_EQ(d3d11_device.As(&dxgi_device), S_OK);
+      return new VSyncThreadWinDXGI(std::move(dxgi_device));
     }
-    Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
-    CHECK_EQ(d3d11_device.As(&dxgi_device), S_OK);
-    return new VSyncThreadWin(std::move(dxgi_device));
   }();
   return vsync_thread;
 }
@@ -172,11 +73,7 @@
   std::optional<base::AutoLock> auto_lock_;
 };
 
-VSyncThreadWin::VSyncThreadWin(Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device)
-    : vsync_thread_("GpuVSyncThread"),
-      vsync_provider_(gfx::kNullAcceleratedWidget),
-      dxgi_adapter_(GetAdapter(dxgi_device.Get())),
-      original_adapter_luid_(GetLuid(dxgi_adapter_.Get())) {
+VSyncThreadWin::VSyncThreadWin() : vsync_thread_("GpuVSyncThread") {
   is_suspended_ = base::PowerMonitor::GetInstance()
                       ->AddPowerSuspendObserverAndReturnSuspendedState(this);
   vsync_thread_.StartWithOptions(
@@ -223,71 +120,25 @@
   PostTaskIfNeeded();
 }
 
-base::TimeDelta VSyncThreadWin::GetVsyncInterval() {
-  base::TimeTicks vsync_timebase;
+void VSyncThreadWin::WaitForVSync() {
   base::TimeDelta vsync_interval;
 
-  // This is a simplified initial approach to fix crbug.com/1456399
-  // In Windows SV3 builds DWM will operate with per monitor refresh
-  // rates. As a result of this, DwmGetCompositionTimingInfo is no longer
-  // guaranteed to align with the primary monitor but will instead align
-  // with the current highest refresh rate monitor. This can cause issues
-  // in clients which may be waiting on the primary monitor's vblank as
-  // the reported interval may no longer match with the vblank wait.
-  // To work around this discrepancy get the VSync interval directly from
-  // monitor associated with window_ or the primary monitor.
-  static bool use_sv3_workaround =
-      base::win::GetVersion() > base::win::Version::WIN11_22H2 &&
-      base::FeatureList::IsEnabled(
-          features::kUsePrimaryMonitorVSyncIntervalOnSV3);
+  const base::TimeTicks wait_for_vsync_start_time = base::TimeTicks::Now();
+  bool wait_succeeded = WaitForVSyncImpl(&vsync_interval);
+  const base::TimeDelta wait_for_vsync_elapsed_time =
+      base::TimeTicks::Now() - wait_for_vsync_start_time;
 
-  const bool get_vsync_params_succeeded =
-      use_sv3_workaround
-          ? vsync_provider_.GetVSyncIntervalIfAvailable(&vsync_interval)
-          : vsync_provider_.GetVSyncParametersIfAvailable(&vsync_timebase,
-                                                          &vsync_interval);
-  DCHECK(get_vsync_params_succeeded);
-  return vsync_interval;
-}
-
-void VSyncThreadWin::WaitForVSync() {
-  base::TimeDelta vsync_interval = GetVsyncInterval();
-
-  if (!dxgi_adapter_ || !DXGIFactoryIsCurrent(dxgi_adapter_.Get())) {
-    TRACE_EVENT("gpu", "DXGIFactoryIsCurrent non-current factory");
-    dxgi_adapter_ = FindDXGIAdapterOnNewFactory(original_adapter_luid_);
-    primary_output_.Reset();
-  }
-
-  // From Raymond Chen's blog "How do I get a handle to the primary monitor?"
-  // https://devblogs.microsoft.com/oldnewthing/20141106-00/?p=43683
-  const HMONITOR monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
-  if (primary_output_ &&
-      !DXGIOutputIsOnMonitor(primary_output_.Get(), monitor)) {
-    TRACE_EVENT("gpu", "DXGIOutputIsOnMonitor primary monitor changed");
-    primary_output_.Reset();
-  }
-
-  if (!primary_output_ && dxgi_adapter_) {
-    primary_output_ = DXGIOutputFromMonitor(monitor, dxgi_adapter_.Get());
-  }
-
-  const base::TimeTicks wait_for_vblank_start_time = base::TimeTicks::Now();
-  const bool wait_for_vblank_succeeded =
-      primary_output_ && SUCCEEDED(primary_output_->WaitForVBlank());
-
-  // WaitForVBlank returns very early instead of waiting until vblank when the
-  // monitor goes to sleep.  We use 1ms as a threshold for the duration of
-  // WaitForVBlank and fallback to Sleep() if it returns before that.  This
-  // could happen during normal operation for the first call after the vsync
-  // thread becomes non-idle, but it shouldn't happen often.
+  // WaitForVBlank and DCompositionWaitForCompositorClock returns very early
+  // instead of waiting until vblank when the monitor goes to sleep or is
+  // unplugged (nothing to present due to desktop occlusion). We use 1ms as
+  // a threshhold for the duration of the wait functions and fallback to
+  // Sleep() if it returns before that. This could happen during normal
+  // operation for the first call after the vsync thread becomes non-idle,
+  // but it shouldn't happen often.
   constexpr auto kVBlankIntervalThreshold = base::Milliseconds(1);
-  const base::TimeDelta wait_for_vblank_elapsed_time =
-      base::TimeTicks::Now() - wait_for_vblank_start_time;
-  if (!wait_for_vblank_succeeded ||
-      wait_for_vblank_elapsed_time < kVBlankIntervalThreshold) {
-    TRACE_EVENT2("gpu", "WaitForVSync Sleep", "has adapter", !!dxgi_adapter_,
-                 "has output", !!primary_output_);
+  if (!wait_succeeded ||
+      wait_for_vsync_elapsed_time < kVBlankIntervalThreshold) {
+    TRACE_EVENT0("gpu", "WaitForVSync Sleep");
     base::Time::ActivateHighResolutionTimer(true);
     Sleep(static_cast<DWORD>(vsync_interval.InMillisecondsRoundedUp()));
     base::Time::ActivateHighResolutionTimer(false);
diff --git a/ui/gl/vsync_thread_win.h b/ui/gl/vsync_thread_win.h
index b6ff1b0..4e864285 100644
--- a/ui/gl/vsync_thread_win.h
+++ b/ui/gl/vsync_thread_win.h
@@ -24,7 +24,7 @@
 // for the primary monitor and notifies observers. Observers can be added or
 // removed from any thread. The vsync thread sleeps when there are no observers.
 // This is used by ExternalBeginFrameSourceWin.
-class GL_EXPORT VSyncThreadWin final : public base::PowerSuspendObserver {
+class GL_EXPORT VSyncThreadWin : public base::PowerSuspendObserver {
  public:
   static VSyncThreadWin* GetInstance();
 
@@ -49,31 +49,31 @@
   void AddObserver(VSyncObserver* obs);
   void RemoveObserver(VSyncObserver* obs);
 
-  gfx::VSyncProvider* vsync_provider() { return &vsync_provider_; }
+  virtual gfx::VSyncProvider* vsync_provider() = 0;
 
   // Returns the vsync interval via the Vsync provider.
-  base::TimeDelta GetVsyncInterval();
+  virtual base::TimeDelta GetVsyncInterval() = 0;
+
+ protected:
+  VSyncThreadWin();
+  ~VSyncThreadWin() override;
+
+  // Gets vsync interval from vsync_provider and halts thread until the next
+  // signal from the compositor clock or vblank. Returns true if the wait was
+  // completed successfully, early if the desktop was occluded and false on any
+  // other failures.
+  virtual bool WaitForVSyncImpl(base::TimeDelta* vsync_interval) = 0;
 
  private:
   // Acquires `lock_` in a scope if not already held by the thread.
   class SCOPED_LOCKABLE AutoVSyncThreadLock;
 
-  explicit VSyncThreadWin(Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device);
-  ~VSyncThreadWin() final;
-
-  void PostTaskIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(lock_);
   void WaitForVSync();
 
+  void PostTaskIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(lock_);
+
   base::Thread vsync_thread_;
 
-  // Used on vsync thread only after initialization.
-  VSyncProviderWin vsync_provider_;
-  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter_;
-  Microsoft::WRL::ComPtr<IDXGIOutput> primary_output_;
-
-  // The LUID of the adapter of the IDXGIDevice this instance was created with.
-  const LUID original_adapter_luid_;
-
   base::Lock lock_;
   bool GUARDED_BY(lock_) is_vsync_task_posted_ = false;
   bool GUARDED_BY(lock_) is_suspended_ = false;
diff --git a/ui/gl/vsync_thread_win_dcomp.cc b/ui/gl/vsync_thread_win_dcomp.cc
new file mode 100644
index 0000000..2ff29ff
--- /dev/null
+++ b/ui/gl/vsync_thread_win_dcomp.cc
@@ -0,0 +1,55 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gl/vsync_thread_win_dcomp.h"
+
+#include "base/logging.h"
+#include "base/trace_event/typed_macros.h"
+#include "ui/gl/direct_composition_support.h"
+#include "ui/gl/gl_features.h"
+
+namespace gl {
+
+VSyncThreadWinDComp::VSyncThreadWinDComp()
+    : VSyncThreadWin(), vsync_provider_() {}
+
+VSyncThreadWinDComp::~VSyncThreadWinDComp() = default;
+
+base::TimeDelta VSyncThreadWinDComp::GetVsyncInterval() {
+  base::TimeTicks vsync_timebase;
+  base::TimeDelta vsync_interval;
+
+  // Use the compositor clock to determine vsync interval, disabled by default
+  const bool get_vsync_succeeded =
+      vsync_provider_.GetVSyncParametersIfAvailable(&vsync_timebase,
+                                                    &vsync_interval);
+  DCHECK(get_vsync_succeeded);
+
+  return vsync_interval;
+}
+
+gfx::VSyncProvider* VSyncThreadWinDComp::vsync_provider() {
+  return &vsync_provider_;
+}
+
+bool VSyncThreadWinDComp::WaitForVSyncImpl(base::TimeDelta* vsync_interval) {
+  *vsync_interval = GetVsyncInterval();
+
+  // Using INFINITE timeout as it is expected for the wait to return in a
+  // timely manner - either at the next vblank or immediately due to desktop
+  // occlusion. This behavior matches the DXGI case.
+  // DCompositionWaitForCompositorClock returns an error on desktop occlusion
+  // and returns early.
+  DWORD wait_result =
+      gl::DCompositionWaitForCompositorClock(0, nullptr, INFINITE);
+
+  if (wait_result != WAIT_OBJECT_0) {
+    TRACE_EVENT1("gpu", "WaitForVSyncImpl", "wait result", wait_result);
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace gl
diff --git a/ui/gl/vsync_thread_win_dcomp.h b/ui/gl/vsync_thread_win_dcomp.h
new file mode 100644
index 0000000..b081248
--- /dev/null
+++ b/ui/gl/vsync_thread_win_dcomp.h
@@ -0,0 +1,35 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GL_VSYNC_THREAD_WIN_DCOMP_H_
+#define UI_GL_VSYNC_THREAD_WIN_DCOMP_H_
+
+#include "ui/gl/vsync_provider_win_dcomp.h"
+#include "ui/gl/vsync_thread_win.h"
+
+namespace gl {
+class GL_EXPORT VSyncThreadWinDComp final : public VSyncThreadWin {
+ public:
+  VSyncThreadWinDComp();
+
+  VSyncThreadWinDComp(const VSyncThreadWinDComp&) = delete;
+  VSyncThreadWinDComp& operator=(const VSyncThreadWinDComp&) = delete;
+
+  // Returns the vsync interval via the Vsync provider.
+  base::TimeDelta GetVsyncInterval() final;
+
+  gfx::VSyncProvider* vsync_provider() final;
+
+ protected:
+  bool WaitForVSyncImpl(base::TimeDelta* vsync_interval) final;
+
+ private:
+  ~VSyncThreadWinDComp() final;
+
+  // Used on vsync thread only after initialization.
+  VSyncProviderWinDComp vsync_provider_;
+};
+}  // namespace gl
+
+#endif  // UI_GL_VSYNC_THREAD_WIN_DCOMP_H_
diff --git a/ui/gl/vsync_thread_win_dxgi.cc b/ui/gl/vsync_thread_win_dxgi.cc
new file mode 100644
index 0000000..ca88116
--- /dev/null
+++ b/ui/gl/vsync_thread_win_dxgi.cc
@@ -0,0 +1,197 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gl/vsync_thread_win_dxgi.h"
+
+#include "base/logging.h"
+#include "base/trace_event/typed_macros.h"
+#include "base/win/windows_version.h"
+#include "ui/gl/gl_features.h"
+
+namespace gl {
+namespace {
+
+// Check if a DXGI adapter is stale and needs to be replaced. This can happen
+// e.g. when detaching/reattaching remote desktop sessions and causes subsequent
+// WaitForVSyncs on the stale adapter/output to return instantly.
+bool DXGIFactoryIsCurrent(IDXGIAdapter* dxgi_adapter) {
+  CHECK(dxgi_adapter);
+
+  HRESULT hr = S_OK;
+  Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory;
+  hr = dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory));
+  CHECK_EQ(S_OK, hr);
+  return dxgi_factory->IsCurrent();
+}
+
+// Create a new factory and find a DXGI adapter matching a LUID. This is useful
+// if we have a previous adapter whose factory has become stale.
+Microsoft::WRL::ComPtr<IDXGIAdapter> FindDXGIAdapterOnNewFactory(
+    const LUID luid) {
+  HRESULT hr = S_OK;
+
+  Microsoft::WRL::ComPtr<IDXGIFactory1> factory;
+  hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
+  CHECK_EQ(S_OK, hr);
+
+  Microsoft::WRL::ComPtr<IDXGIAdapter1> new_adapter;
+  for (uint32_t i = 0;; i++) {
+    hr = factory->EnumAdapters1(i, &new_adapter);
+    if (hr == DXGI_ERROR_NOT_FOUND) {
+      break;
+    }
+    if (FAILED(hr)) {
+      DLOG(ERROR) << "EnumAdapters1 failed: "
+                  << logging::SystemErrorCodeToString(hr);
+      return nullptr;
+    }
+
+    DXGI_ADAPTER_DESC1 new_adapter_desc;
+    hr = new_adapter->GetDesc1(&new_adapter_desc);
+    CHECK_EQ(S_OK, hr);
+
+    if (new_adapter_desc.AdapterLuid.HighPart == luid.HighPart &&
+        new_adapter_desc.AdapterLuid.LowPart == luid.LowPart) {
+      return new_adapter;
+    }
+  }
+
+  DLOG(ERROR) << "Failed to find DXGI adapter with matching LUID";
+  return nullptr;
+}
+
+// Return true if |output| is on |monitor|.
+bool DXGIOutputIsOnMonitor(IDXGIOutput* output, const HMONITOR monitor) {
+  CHECK(output);
+
+  DXGI_OUTPUT_DESC desc = {};
+  HRESULT hr = output->GetDesc(&desc);
+  CHECK_EQ(S_OK, hr);
+  return desc.Monitor == monitor;
+}
+
+Microsoft::WRL::ComPtr<IDXGIOutput> DXGIOutputFromMonitor(
+    HMONITOR monitor,
+    IDXGIAdapter* dxgi_adapter) {
+  CHECK(dxgi_adapter);
+
+  HRESULT hr = S_OK;
+
+  Microsoft::WRL::ComPtr<IDXGIOutput> output;
+  for (uint32_t i = 0;; i++) {
+    hr = dxgi_adapter->EnumOutputs(i, &output);
+    if (hr == DXGI_ERROR_NOT_FOUND) {
+      break;
+    }
+    if (FAILED(hr)) {
+      DLOG(ERROR) << "EnumOutputs failed: "
+                  << logging::SystemErrorCodeToString(hr);
+      return nullptr;
+    }
+
+    if (DXGIOutputIsOnMonitor(output.Get(), monitor)) {
+      return output;
+    }
+  }
+
+  DLOG(ERROR) << "Failed to find DXGI output with matching monitor";
+  return nullptr;
+}
+
+Microsoft::WRL::ComPtr<IDXGIAdapter> GetAdapter(IDXGIDevice* device) {
+  CHECK(device);
+
+  Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
+  CHECK_EQ(S_OK, device->GetAdapter(&adapter));
+  return adapter;
+}
+
+LUID GetLuid(IDXGIAdapter* adapter) {
+  CHECK(adapter);
+
+  DXGI_ADAPTER_DESC desc;
+  HRESULT hr = adapter->GetDesc(&desc);
+  CHECK_EQ(S_OK, hr);
+  return desc.AdapterLuid;
+}
+}  // namespace
+
+VSyncThreadWinDXGI::VSyncThreadWinDXGI(
+    Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device)
+    : VSyncThreadWin(),
+      vsync_provider_(gfx::kNullAcceleratedWidget),
+      dxgi_adapter_(GetAdapter(dxgi_device.Get())),
+      original_adapter_luid_(GetLuid(dxgi_adapter_.Get())) {}
+
+VSyncThreadWinDXGI::~VSyncThreadWinDXGI() {
+  NOTREACHED();
+}
+
+gfx::VSyncProvider* VSyncThreadWinDXGI::vsync_provider() {
+  return &vsync_provider_;
+}
+
+base::TimeDelta VSyncThreadWinDXGI::GetVsyncInterval() {
+  base::TimeTicks vsync_timebase;
+  base::TimeDelta vsync_interval;
+
+  // This is a simplified initial approach to fix crbug.com/1456399
+  // In Windows SV3 builds DWM will operate with per monitor refresh
+  // rates. As a result of this, DwmGetCompositionTimingInfo is no longer
+  // guaranteed to align with the primary monitor but will instead align
+  // with the current highest refresh rate monitor. This can cause issues
+  // in clients which may be waiting on the primary monitor's vblank as
+  // the reported interval may no longer match with the vblank wait.
+  // To work around this discrepancy get the VSync interval directly from
+  // monitor associated with window_ or the primary monitor.
+  static bool use_sv3_workaround =
+      base::win::GetVersion() > base::win::Version::WIN11_22H2 &&
+      base::FeatureList::IsEnabled(
+          features::kUsePrimaryMonitorVSyncIntervalOnSV3);
+
+  const bool get_vsync_params_succeeded =
+      use_sv3_workaround
+          ? vsync_provider_.GetVSyncIntervalIfAvailable(&vsync_interval)
+          : vsync_provider_.GetVSyncParametersIfAvailable(&vsync_timebase,
+                                                          &vsync_interval);
+  DCHECK(get_vsync_params_succeeded);
+
+  return vsync_interval;
+}
+
+bool VSyncThreadWinDXGI::WaitForVSyncImpl(base::TimeDelta* vsync_interval) {
+  *vsync_interval = GetVsyncInterval();
+
+  if (!dxgi_adapter_ || !DXGIFactoryIsCurrent(dxgi_adapter_.Get())) {
+    TRACE_EVENT("gpu", "DXGIFactoryIsCurrent non-current factory");
+    dxgi_adapter_ = FindDXGIAdapterOnNewFactory(original_adapter_luid_);
+    primary_output_.Reset();
+  }
+
+  // From Raymond Chen's blog "How do I get a handle to the primary monitor?"
+  // https://devblogs.microsoft.com/oldnewthing/20141106-00/?p=43683
+  const HMONITOR monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
+  if (primary_output_ &&
+      !DXGIOutputIsOnMonitor(primary_output_.Get(), monitor)) {
+    TRACE_EVENT("gpu", "DXGIOutputIsOnMonitor primary monitor changed");
+    primary_output_.Reset();
+  }
+
+  if (!primary_output_ && dxgi_adapter_) {
+    primary_output_ = DXGIOutputFromMonitor(monitor, dxgi_adapter_.Get());
+  }
+
+  // WaitForVBlank returns success on desktop occlusion and returns early
+  const bool wait_succeeded =
+      primary_output_ && SUCCEEDED(primary_output_->WaitForVBlank());
+  if (!wait_succeeded) {
+    TRACE_EVENT2("gpu", "WaitForVSyncImpl", "has adapter", !!dxgi_adapter_,
+                 "has output", !!primary_output_);
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace gl
diff --git a/ui/gl/vsync_thread_win_dxgi.h b/ui/gl/vsync_thread_win_dxgi.h
new file mode 100644
index 0000000..0b0213d
--- /dev/null
+++ b/ui/gl/vsync_thread_win_dxgi.h
@@ -0,0 +1,41 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GL_VSYNC_THREAD_WIN_DXGI_H_
+#define UI_GL_VSYNC_THREAD_WIN_DXGI_H_
+
+#include "ui/gl/vsync_thread_win.h"
+
+namespace gl {
+class GL_EXPORT VSyncThreadWinDXGI final : public VSyncThreadWin {
+ public:
+  explicit VSyncThreadWinDXGI(Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device);
+
+  VSyncThreadWinDXGI(const VSyncThreadWinDXGI&) = delete;
+  VSyncThreadWinDXGI& operator=(const VSyncThreadWinDXGI&) = delete;
+
+  // Returns the vsync interval via the Vsync provider.
+  base::TimeDelta GetVsyncInterval() final;
+
+  gfx::VSyncProvider* vsync_provider() final;
+
+ protected:
+  bool WaitForVSyncImpl(base::TimeDelta* vsync_interval) final;
+
+ private:
+  ~VSyncThreadWinDXGI() final;
+
+  // Used on vsync thread only after initialization.
+  VSyncProviderWin vsync_provider_;
+
+  // Used on vsync thread only after initialization
+  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter_;
+  Microsoft::WRL::ComPtr<IDXGIOutput> primary_output_;
+
+  // The LUID of the adapter of the IDXGIDevice this instance was created with.
+  const LUID original_adapter_luid_;
+};
+}  // namespace gl
+
+#endif  // UI_GL_VSYNC_THREAD_WIN_DXGI_H_
diff --git a/ui/webui/resources/mojo/BUILD.gn b/ui/webui/resources/mojo/BUILD.gn
index e5388e7..3a7cf6f 100644
--- a/ui/webui/resources/mojo/BUILD.gn
+++ b/ui/webui/resources/mojo/BUILD.gn
@@ -22,6 +22,7 @@
   "mojo/public/mojom/base/safe_base_name.mojom-webui.ts",
   "mojo/public/mojom/base/string16.mojom-webui.ts",
   "mojo/public/mojom/base/text_direction.mojom-webui.ts",
+  "mojo/public/mojom/base/time.mojom-converters.ts",
   "mojo/public/mojom/base/time.mojom-webui.ts",
   "mojo/public/mojom/base/token.mojom-webui.ts",
   "mojo/public/mojom/base/unguessable_token.mojom-webui.ts",
@@ -39,6 +40,8 @@
   "url/mojom/url.mojom-webui.ts",
 ]
 
+mojo_converters = [ "mojo/public/mojom/base/time_converters.ts" ]
+
 if (is_ios) {
   mojo_ts_files += [ "ui/base/mojom/ui_base_types.mojom-webui.ts" ]
 }
@@ -116,17 +119,26 @@
   }
 }
 
+preprocess_if_expr("copy_converters") {
+  visibility = [ ":build_ts" ]
+
+  in_folder = "//"
+  out_folder = "$target_gen_dir/preprocessed"
+  in_files = mojo_converters
+}
+
 ts_library("build_ts") {
   root_dir = "$target_gen_dir/preprocessed"
   out_dir = tsc_folder
   composite = true
 
-  in_files = mojo_ts_files
+  in_files = mojo_ts_files + mojo_converters
 
   definitions = [ "$tsc_folder/mojo/public/js/bindings.d.ts" ]
 
   extra_deps = [
     ":copy_bindings_dts",
+    ":copy_converters",
     ":copy_mojo_ts",
   ]