diff --git a/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc b/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc index 7e2cd4a741c85d..e228e7fafc3e07 100644 --- a/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc +++ b/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc @@ -721,6 +721,51 @@ TEST_F(WaylandPointerTest, FlingVelocityWithoutLeadingAxis) { EXPECT_EQ(0.0f, scroll_event->y_offset_ordinal()); } +// Regression test: OnPointerFrameEvent() caches a raw `target` pointer outside +// its drain loop. If the focused window is synchronously destroyed while +// dispatching the first queued event (e.g. a popup closing on click/scroll), +// the second iteration uses the dangling pointer. This test reaches the +// 2-events-per-frame state via the fling-cancel path (no feature flags +// required) and destroys the focused window from inside the first dispatch. +TEST_F(WaylandPointerTest, FrameTargetUseAfterFreeOnSyncWindowClose) { + SendEnter(50, 50); + + // Frame 1: axis_stop -> FlingStart is queued and drained (1 event). This + // sets is_fling_active_=true so the next finger-scroll frame will queue + // *two* events (FlingCancel + Scroll). + SendAxisStopEvents(); + + // Now arm the delegate so that the *next* dispatched event tears down the + // pointer-focused window synchronously, simulating a popup widget closing + // from inside its own event handler. + bool destroyed = false; + EXPECT_CALL(delegate_, DispatchEvent(_)) + .WillRepeatedly([this, &destroyed](Event* event) { + if (!destroyed) { + destroyed = true; + window_.reset(); + } + }); + + // Frame 2: finger axis -> ProcessPointerScrollData() pushes + // [FlingCancel, Scroll] into pointer_frames_, then OnPointerFrameEvent() + // drains both against the cached `target`. The first dispatch frees + // `target`; the second dispatch is a use-after-free in + // SetTargetAndDispatchEvent -> SetRootLocation (virtual call on freed + // WaylandWindow). + PostToServerAndWait([](wl::TestWaylandServerThread* server) { + auto* const pointer = server->seat()->pointer()->resource(); + SendAxisEvents(pointer, server->GetNextTime(), + WL_POINTER_AXIS_SOURCE_FINGER, + WL_POINTER_AXIS_VERTICAL_SCROLL, 10); + }); + + EXPECT_TRUE(destroyed); + // The test fixture's window has been torn down mid-test; skip the + // teardown sync to avoid spurious errors after the UAF site. + DisableSyncOnTearDown(); +} + TEST_F(WaylandPointerTest, FlingVelocityWithSingleLeadingAxis) { SendEnter(50, 75);