diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc index fe2877710c3b4..d9a89a6741891 100644 --- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc +++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc @@ -31,6 +31,8 @@ #include "ui/base/dragdrop/os_exchange_data.h" #include "ui/base/dragdrop/os_exchange_data_provider_factory.h" #include "ui/events/base_event_utils.h" +#include "ui/events/event.h" +#include "ui/events/types/event_type.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/native_ui_types.h" @@ -912,6 +914,36 @@ TEST_P(WaylandDataDragControllerTest, DestroyOriginSurface) { SendDndCancelled(); } +// Verifies the drag loop exits gracefully when the origin window is destroyed +// while dispatching the synthetic pointer release at drag-session close. +TEST_P(WaylandDataDragControllerTest, + DestroyOriginWindowDuringDragSessionClose) { + FocusAndPressLeftPointerButton(window_.get(), &delegate_); + + // Once the drag session has started, emulate a successful drop. The + // controller will synthesize a release for the still-pressed pointer button; + // have the delegate destroy the origin window from within that event's + // dispatch, mimicking a queued close task running in a nested loop. + ScheduleTestTask(base::BindLambdaForTesting([&]() { + EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) { + if (event->type() == EventType::kMouseReleased) { + window_.reset(); + } + }); + SendDndFinished(); + })); + + OSExchangeData os_exchange_data; + os_exchange_data.SetString(sample_text_for_dnd()); + EXPECT_CALL(drag_started_callback_, Run()).Times(1); + EXPECT_FALSE(window_->StartDrag( + os_exchange_data, DragDropTypes::DRAG_COPY, DragEventSource::kMouse, + /*cursor=*/{}, /*can_grab_pointer=*/true, drag_started_callback_.Get(), + drag_finished_callback_.Get(), /*location_delegate=*/nullptr)); + + EXPECT_FALSE(window_); +} + // Ensures drag/drop events are properly propagated to non-toplevel windows. TEST_P(WaylandDataDragControllerTest, DragToNonToplevelWindows) { auto* origin_window = window_.get(); diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc index 7701deda447bd..52e0f12167a8f 100644 --- a/ui/ozone/platform/wayland/host/wayland_window.cc +++ b/ui/ozone/platform/wayland/host/wayland_window.cc @@ -918,16 +918,23 @@ void WaylandWindow::OnDragSessionClose(DragOperation operation) { // is about to shut down. Do nothing and return. return; } + // Running `drag_finished_callback_` and dispatching the synthetic pointer + // release below may spin a nested run loop in which `this` gets destroyed, + // so move the quit closure onto the stack to ensure the drag loop is still + // quit in that case. + base::OnceClosure quit_closure = std::move(drag_loop_quit_closure_); + auto alive = AsWeakPtr(); std::move(drag_finished_callback_).Run(operation); // Skip releasing any pointer buttons for the case of a window drag driven by // the data drag controller. // TODO: crbug.com/40238145 - Refactor this per discussion at // crrev.com/c/5570335/comment/0b8811fc_818028c9/. - if (!connection()->data_drag_controller()->IsWindowDragSessionRunning()) { + if (alive && + !connection()->data_drag_controller()->IsWindowDragSessionRunning()) { connection()->event_source()->ReleasePressedPointerButtons( this, EventTimeForNow()); } - std::move(drag_loop_quit_closure_).Run(); + std::move(quit_closure).Run(); } bool WaylandWindow::Initialize(PlatformWindowInitProperties properties) {