ozone/wayland: dnd: Support dragging to/from non-toplevel windows

So far, only WaylandToplevelWindow supports drag'n'drop, by implementing
ui::WmDragHandler, which prevents, for example, Chrome's Bookmarks DND
features from working as expected. Dragging a bookmark from the bookmark
bar into a folder makes the folder content to be shown in a popup/menu
window, which must get notified about the drag/drop events so the menu
controller can determine when to open/close menu items accordingly.

This CL address it by moving WmDragHandler impl to WaylandWindow base
class. Also, unit tests are added to make sure the DND events are
properly propagated to drag/drop handlers attached to non-toplevel
windows.

Bug: 1143707
Test: ozone_unittest --gtest_filter='*WaylandDataDragControllerTest.*'
Change-Id: Icef2cc1e66851edfa21fa5d890ca0398aceeed51
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2595087
Commit-Queue: Nick Yamane <nickdiego@igalia.com>
Reviewed-by: Antonio Gomes <tonikitoo@igalia.com>
Cr-Commit-Position: refs/heads/master@{#840753}
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 93e1ca9..f44eed4 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
@@ -26,7 +26,6 @@
 #include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h"
 #include "ui/ozone/platform/wayland/host/wayland_data_drag_controller.h"
 #include "ui/ozone/platform/wayland/host/wayland_data_source.h"
-#include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h"
 #include "ui/ozone/platform/wayland/host/wayland_window.h"
 #include "ui/ozone/platform/wayland/test/mock_surface.h"
 #include "ui/ozone/platform/wayland/test/test_data_device.h"
@@ -154,6 +153,12 @@
     return connection_->data_device_manager()->GetDevice();
   }
 
+  MockDropHandler* drop_handler() { return drop_handler_.get(); }
+
+  MockDragHandlerDelegate* drag_handler() {
+    return drag_handler_delegate_.get();
+  }
+
   WaylandConnection* connection() { return connection_.get(); }
 
   WaylandWindow* window() { return window_.get(); }
@@ -168,6 +173,22 @@
     return ++serial;
   }
 
+  std::unique_ptr<WaylandWindow> CreateTestWindow(
+      PlatformWindowType type,
+      const gfx::Size& size,
+      MockPlatformWindowDelegate* delegate) {
+    DCHECK(delegate);
+    PlatformWindowInitProperties properties{gfx::Rect(size)};
+    properties.type = type;
+    EXPECT_CALL(*delegate, OnAcceleratedWidgetAvailable(_)).Times(1);
+    auto window = WaylandWindow::Create(delegate, connection_.get(),
+                                        std::move(properties));
+    SetWmDropHandler(window.get(), drop_handler_.get());
+    EXPECT_NE(gfx::kNullAcceleratedWidget, window->GetWidget());
+    Sync();
+    return window;
+  }
+
   // TODO(crbug.com/1163544): Deduplicate DnD test helper code.
   void SendDndEnter(WaylandWindow* window, const gfx::Point& location) {
     EXPECT_TRUE(data_device_manager_->data_source());
@@ -232,6 +253,13 @@
     ScheduleTestTask(base::BindOnce(
         [](WaylandDataDragControllerTest* self) {
           self->SendDndCancelled();
+
+          // DnD handlers expect DragLeave to be sent before DragFinished when
+          // drag sessions end up with no data transfer (cancelled). Otherwise,
+          // it might lead to issues like https://crbug.com/1109324.
+          EXPECT_CALL(*self->drop_handler(), OnDragLeave).Times(1);
+          EXPECT_CALL(*self->drag_handler(), OnDragFinished).Times(1);
+
           self->Sync();
         },
         base::Unretained(this)));
@@ -278,10 +306,9 @@
       base::BindOnce(&WaylandDataDragControllerTest::ReadDataWhenSourceIsReady,
                      base::Unretained(this)));
 
-  static_cast<WaylandToplevelWindow*>(window_.get())
-      ->StartDrag(os_exchange_data,
-                  DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE, {}, true,
-                  drag_handler_delegate_.get());
+  window_->StartDrag(os_exchange_data,
+                     DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE, {},
+                     /*can_grab_pointer=*/true, drag_handler_delegate_.get());
   Sync();
 
   EXPECT_FALSE(data_device()->drag_delegate_);
@@ -537,24 +564,12 @@
   const bool restored_focus = window_->has_pointer_focus();
   window_->SetPointerFocus(true);
 
-  ASSERT_EQ(PlatformWindowType::kWindow, window_->type());
+  ScheduleDragCancel();
+
   OSExchangeData os_exchange_data;
   os_exchange_data.SetString(sample_text_for_dnd());
-
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&WaylandDataDragControllerTest::ScheduleDragCancel,
-                     base::Unretained(this)));
-
-  // DnD handlers expect DragLeave to be sent before DragFinished when drag
-  // sessions end up with no data transfer (cancelled). Otherwise, it might lead
-  // to issues like https://crbug.com/1109324.
-  EXPECT_CALL(*drop_handler_, OnDragLeave()).Times(1);
-  EXPECT_CALL(*drag_handler_delegate_, OnDragFinished(_)).Times(1);
-
-  static_cast<WaylandToplevelWindow*>(window_.get())
-      ->StartDrag(os_exchange_data, DragDropTypes::DRAG_COPY, {}, true,
-                  drag_handler_delegate_.get());
+  window_->StartDrag(os_exchange_data, DragDropTypes::DRAG_COPY, {},
+                     /*can_grab_pointer=*/true, drag_handler_delegate_.get());
   Sync();
 
   window_->SetPointerFocus(restored_focus);
@@ -595,7 +610,7 @@
 
 // Regression test for https://crbug.com/1143707.
 TEST_P(WaylandDataDragControllerTest, DestroyEnteredSurface) {
-  auto* window_1 = static_cast<WaylandToplevelWindow*>(window_.get());
+  auto* window_1 = window_.get();
   const bool restored_focus = window_1->has_pointer_focus();
   window_1->SetPointerFocus(true);
   ASSERT_EQ(PlatformWindowType::kWindow, window_1->type());
@@ -605,14 +620,9 @@
     self->SendDndEnter(self->window(), gfx::Point(10, 10));
 
     // Init and open |target_window|.
-    PlatformWindowInitProperties properties{gfx::Rect{80, 80}};
-    properties.type = PlatformWindowType::kWindow;
     MockPlatformWindowDelegate delegate_2;
-    EXPECT_CALL(delegate_2, OnAcceleratedWidgetAvailable(_)).Times(1);
-    auto window_2 = WaylandWindow::Create(&delegate_2, self->connection(),
-                                          std::move(properties));
-    ASSERT_NE(gfx::kNullAcceleratedWidget, window_2->GetWidget());
-    self->Sync();
+    auto window_2 = self->CreateTestWindow(PlatformWindowType::kWindow,
+                                           gfx::Size(80, 80), &delegate_2);
 
     // Leave |window_1| and enter |window_2|.
     self->SendDndLeave();
@@ -644,6 +654,60 @@
   window_1->SetPointerFocus(restored_focus);
 }
 
+// Ensures drag/drop events are properly propagated to non-toplevel windows.
+TEST_P(WaylandDataDragControllerTest, DragToNonToplevelWindows) {
+  auto* origin_window = window_.get();
+  const bool restored_focus = origin_window->has_pointer_focus();
+  origin_window->SetPointerFocus(true);
+
+  auto test = [](WaylandDataDragControllerTest* self,
+                 PlatformWindowType window_type) {
+    // Emulate server sending an dnd offer + enter events for |origin_window|.
+    self->SendDndEnter(self->window(), gfx::Point(10, 10));
+    EXPECT_CALL(*self->drop_handler(), MockOnDragEnter()).Times(1);
+    EXPECT_CALL(*self->drop_handler(), MockDragMotion(_, _, _)).Times(1);
+    self->Sync();
+
+    // Init and open |target_window|.
+    MockPlatformWindowDelegate delegate_2;
+    auto window_2 =
+        self->CreateTestWindow(window_type, gfx::Size(100, 40), &delegate_2);
+
+    // Leave |origin_window| and enter non-toplevel |window_2|.
+    self->SendDndLeave();
+    EXPECT_CALL(*self->drop_handler(), OnDragLeave).Times(1);
+
+    self->SendDndEnter(window_2.get(), {});
+    EXPECT_CALL(*self->drop_handler(), MockOnDragEnter()).Times(1);
+    EXPECT_CALL(*self->drop_handler(), MockDragMotion(_, _, _)).Times(1);
+    self->Sync();
+
+    self->SendDndLeave();
+    EXPECT_CALL(*self->drop_handler(), OnDragLeave).Times(1);
+    self->Sync();
+  };
+
+  // Post test tasks, for each non-toplevel window type, to be performed
+  // asynchronously once the dnd-related protocol objects are ready.
+  constexpr PlatformWindowType kNonToplevelWindowTypes[]{
+      PlatformWindowType::kPopup, PlatformWindowType::kMenu,
+      PlatformWindowType::kTooltip, PlatformWindowType::kBubble};
+  for (auto window_type : kNonToplevelWindowTypes)
+    ScheduleTestTask(base::BindOnce(test, base::Unretained(this), window_type));
+
+  // Post a wl_data_source::cancelled notifying the client to tear down the drag
+  // session.
+  ScheduleDragCancel();
+
+  // Request to start the drag session, which spins a nested run loop.
+  OSExchangeData os_exchange_data;
+  os_exchange_data.SetString(sample_text_for_dnd());
+  origin_window->StartDrag(os_exchange_data, DragDropTypes::DRAG_COPY, {}, true,
+                           drag_handler_delegate_.get());
+  Sync();
+  origin_window->SetPointerFocus(restored_focus);
+}
+
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandDataDragControllerTest,
                          ::testing::Values(kXdgShellStable));
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
index 1ea4c1a3..1de78dc 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
@@ -9,21 +9,17 @@
 #include "base/run_loop.h"
 #include "base/unguessable_token.h"
 #include "build/chromeos_buildflags.h"
-#include "ui/base/dragdrop/drag_drop_types.h"
-#include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/base/hit_test.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/ozone/platform/wayland/host/shell_object_factory.h"
 #include "ui/ozone/platform/wayland/host/shell_surface_wrapper.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
-#include "ui/ozone/platform/wayland/host/wayland_data_drag_controller.h"
 #include "ui/ozone/platform/wayland/host/wayland_event_source.h"
 #include "ui/ozone/platform/wayland/host/wayland_window.h"
 #include "ui/ozone/platform/wayland/host/wayland_window_drag_controller.h"
 #include "ui/ozone/platform/wayland/host/wayland_zaura_shell.h"
 #include "ui/platform_window/extensions/wayland_extension.h"
-#include "ui/platform_window/wm/wm_drop_handler.h"
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 // TODO(jamescook): The nogncheck is to work around false-positive failures on
@@ -40,18 +36,9 @@
   // Set a class property key, which allows |this| to be used for interactive
   // events, e.g. move or resize.
   SetWmMoveResizeHandler(this, AsWmMoveResizeHandler());
-
-  // Set a class property key, which allows |this| to be used for drag action.
-  SetWmDragHandler(this, this);
 }
 
-WaylandToplevelWindow::~WaylandToplevelWindow() {
-  if (drag_handler_delegate_) {
-    drag_handler_delegate_->OnDragFinished(
-        DragDropTypes::DragOperation::DRAG_NONE);
-  }
-  CancelDrag();
-}
+WaylandToplevelWindow::~WaylandToplevelWindow() = default;
 
 bool WaylandToplevelWindow::CreateShellSurface() {
   ShellObjectFactory factory;
@@ -99,31 +86,6 @@
   connection()->ScheduleFlush();
 }
 
-bool WaylandToplevelWindow::StartDrag(const ui::OSExchangeData& data,
-                                      int operation,
-                                      gfx::NativeCursor cursor,
-                                      bool can_grab_pointer,
-                                      WmDragHandler::Delegate* delegate) {
-  DCHECK(!drag_handler_delegate_);
-  drag_handler_delegate_ = delegate;
-  connection()->data_drag_controller()->StartSession(data, operation);
-
-  base::RunLoop drag_loop(base::RunLoop::Type::kNestableTasksAllowed);
-  drag_loop_quit_closure_ = drag_loop.QuitClosure();
-
-  auto alive = weak_ptr_factory_.GetWeakPtr();
-  drag_loop.Run();
-  if (!alive)
-    return false;
-  return true;
-}
-
-void WaylandToplevelWindow::CancelDrag() {
-  if (drag_loop_quit_closure_.is_null())
-    return;
-  std::move(drag_loop_quit_closure_).Run();
-}
-
 void WaylandToplevelWindow::Show(bool inactive) {
   if (shell_surface_)
     return;
@@ -321,59 +283,6 @@
     delegate()->OnActivationChanged(is_active_);
 }
 
-void WaylandToplevelWindow::OnDragEnter(const gfx::PointF& point,
-                                        std::unique_ptr<OSExchangeData> data,
-                                        int operation) {
-  WmDropHandler* drop_handler = GetWmDropHandler(*this);
-  if (!drop_handler)
-    return;
-
-  // Wayland sends locations in DIP so they need to be translated to
-  // physical pixels.
-  // TODO(crbug.com/1102857): get the real event modifier here.
-  drop_handler->OnDragEnter(
-      gfx::ScalePoint(point, buffer_scale(), buffer_scale()), std::move(data),
-      operation,
-      /*modifiers=*/0);
-}
-
-int WaylandToplevelWindow::OnDragMotion(const gfx::PointF& point,
-                                        int operation) {
-  WmDropHandler* drop_handler = GetWmDropHandler(*this);
-  if (!drop_handler)
-    return 0;
-
-  // Wayland sends locations in DIP so they need to be translated to
-  // physical pixels.
-  // TODO(crbug.com/1102857): get the real event modifier here.
-  return drop_handler->OnDragMotion(
-      gfx::ScalePoint(point, buffer_scale(), buffer_scale()), operation,
-      /*modifiers=*/0);
-}
-
-void WaylandToplevelWindow::OnDragDrop() {
-  WmDropHandler* drop_handler = GetWmDropHandler(*this);
-  if (!drop_handler)
-    return;
-  // TODO(crbug.com/1102857): get the real event modifier here.
-  drop_handler->OnDragDrop({}, /*modifiers=*/0);
-}
-
-void WaylandToplevelWindow::OnDragLeave() {
-  WmDropHandler* drop_handler = GetWmDropHandler(*this);
-  if (!drop_handler)
-    return;
-  drop_handler->OnDragLeave();
-}
-
-void WaylandToplevelWindow::OnDragSessionClose(uint32_t dnd_action) {
-  DCHECK(drag_handler_delegate_);
-  drag_handler_delegate_->OnDragFinished(dnd_action);
-  drag_handler_delegate_ = nullptr;
-  connection()->event_source()->ResetPointerFlags();
-  std::move(drag_loop_quit_closure_).Run();
-}
-
 bool WaylandToplevelWindow::OnInitialize(
     PlatformWindowInitProperties properties) {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
index a6f2b82..591ca199 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
@@ -9,7 +9,6 @@
 #include "ui/gfx/geometry/vector2d.h"
 #include "ui/ozone/platform/wayland/host/wayland_window.h"
 #include "ui/platform_window/extensions/wayland_extension.h"
-#include "ui/platform_window/wm/wm_drag_handler.h"
 #include "ui/platform_window/wm/wm_move_loop_handler.h"
 #include "ui/platform_window/wm/wm_move_resize_handler.h"
 
@@ -19,7 +18,6 @@
 
 class WaylandToplevelWindow : public WaylandWindow,
                               public WmMoveResizeHandler,
-                              public WmDragHandler,
                               public WmMoveLoopHandler,
                               public WaylandExtension {
  public:
@@ -40,14 +38,6 @@
       int hittest,
       const gfx::Point& pointer_location_in_px) override;
 
-  // WmDragHandler
-  bool StartDrag(const ui::OSExchangeData& data,
-                 int operation,
-                 gfx::NativeCursor cursor,
-                 bool can_grab_pointer,
-                 WmDragHandler::Delegate* delegate) override;
-  void CancelDrag() override;
-
   // PlatformWindow
   void Show(bool inactive) override;
   void Hide() override;
@@ -72,13 +62,6 @@
                               bool is_maximized,
                               bool is_fullscreen,
                               bool is_activated) override;
-  void OnDragEnter(const gfx::PointF& point,
-                   std::unique_ptr<OSExchangeData> data,
-                   int operation) override;
-  int OnDragMotion(const gfx::PointF& point, int operation) override;
-  void OnDragDrop() override;
-  void OnDragLeave() override;
-  void OnDragSessionClose(uint32_t dnd_action) override;
   bool OnInitialize(PlatformWindowInitProperties properties) override;
   bool IsActive() const override;
 
@@ -113,8 +96,6 @@
   // Wrappers around shell surface.
   std::unique_ptr<ShellSurfaceWrapper> shell_surface_;
 
-  WmDragHandler::Delegate* drag_handler_delegate_ = nullptr;
-
   // These bounds attributes below have suffices that indicate units used.
   // Wayland operates in DIP but the platform operates in physical pixels so
   // our WaylandToplevelWindow is the link that has to translate the units.  See
@@ -153,8 +134,6 @@
   base::Optional<gfx::Size> min_size_;
   base::Optional<gfx::Size> max_size_;
 
-  base::OnceClosure drag_loop_quit_closure_;
-
   wl::Object<zaura_surface> aura_surface_;
 
   // When use_native_frame is false, client-side decoration is set,
@@ -162,8 +141,6 @@
   // When use_native_frame is true, server-side decoration is set,
   // e.g. lacros-taskmanager.
   bool use_native_frame_ = false;
-
-  base::WeakPtrFactory<WaylandToplevelWindow> weak_ptr_factory_{this};
 };
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index 69d11bf..f14adef 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -8,24 +8,32 @@
 #include <memory>
 
 #include "base/bind.h"
+#include "base/run_loop.h"
 #include "build/chromeos_buildflags.h"
 #include "ui/base/cursor/ozone/bitmap_cursor_factory_ozone.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/events/event.h"
 #include "ui/events/event_utils.h"
 #include "ui/events/ozone/events_ozone.h"
 #include "ui/events/platform/platform_event_source.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_widget_types.h"
 #include "ui/ozone/common/features.h"
 #include "ui/ozone/platform/wayland/common/wayland_util.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 #include "ui/ozone/platform/wayland/host/wayland_cursor_position.h"
+#include "ui/ozone/platform/wayland/host/wayland_data_drag_controller.h"
+#include "ui/ozone/platform/wayland/host/wayland_event_source.h"
 #include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
 #include "ui/ozone/platform/wayland/host/wayland_pointer.h"
 #include "ui/ozone/platform/wayland/host/wayland_subsurface.h"
 #include "ui/ozone/platform/wayland/host/wayland_zcr_cursor_shapes.h"
 #include "ui/ozone/public/mojom/wayland/wayland_overlay_config.mojom.h"
+#include "ui/platform_window/wm/wm_drag_handler.h"
+#include "ui/platform_window/wm/wm_drop_handler.h"
 
 namespace {
 
@@ -45,7 +53,10 @@
       connection_(connection),
       wayland_overlay_delegation_enabled_(IsWaylandOverlayDelegationEnabled()),
       accelerated_widget_(
-          connection->wayland_window_manager()->AllocateAcceleratedWidget()) {}
+          connection->wayland_window_manager()->AllocateAcceleratedWidget()) {
+  // Set a class property key, which allows |this| to be used for drag action.
+  SetWmDragHandler(this, this);
+}
 
 WaylandWindow::~WaylandWindow() {
   shutting_down_ = true;
@@ -65,6 +76,12 @@
 
   if (parent_window_)
     parent_window_->set_child_window(nullptr);
+
+  if (drag_handler_delegate_) {
+    drag_handler_delegate_->OnDragFinished(
+        DragDropTypes::DragOperation::DRAG_NONE);
+  }
+  CancelDrag();
 }
 
 void WaylandWindow::OnWindowLostCapture() {
@@ -124,6 +141,31 @@
   }
 }
 
+bool WaylandWindow::StartDrag(const ui::OSExchangeData& data,
+                              int operation,
+                              gfx::NativeCursor cursor,
+                              bool can_grab_pointer,
+                              WmDragHandler::Delegate* delegate) {
+  DCHECK(!drag_handler_delegate_);
+  drag_handler_delegate_ = delegate;
+  connection()->data_drag_controller()->StartSession(data, operation);
+
+  base::RunLoop drag_loop(base::RunLoop::Type::kNestableTasksAllowed);
+  drag_loop_quit_closure_ = drag_loop.QuitClosure();
+
+  auto alive = weak_ptr_factory_.GetWeakPtr();
+  drag_loop.Run();
+  if (!alive)
+    return false;
+  return true;
+}
+
+void WaylandWindow::CancelDrag() {
+  if (drag_loop_quit_closure_.is_null())
+    return;
+  std::move(drag_loop_quit_closure_).Run();
+}
+
 void WaylandWindow::Show(bool inactive) {
   if (background_buffer_id_ != 0u)
     should_attach_background_buffer_ = true;
@@ -354,17 +396,58 @@
 
 void WaylandWindow::OnDragEnter(const gfx::PointF& point,
                                 std::unique_ptr<OSExchangeData> data,
-                                int operation) {}
+                                int operation) {
+  WmDropHandler* drop_handler = GetWmDropHandler(*this);
+  if (!drop_handler)
+    return;
 
-int WaylandWindow::OnDragMotion(const gfx::PointF& point, int operation) {
-  return -1;
+  auto location_px = gfx::ScalePoint(TranslateLocationToRootWindow(point),
+                                     buffer_scale(), buffer_scale());
+
+  // Wayland sends locations in DIP so they need to be translated to
+  // physical pixels.
+  // TODO(crbug.com/1102857): get the real event modifier here.
+  drop_handler->OnDragEnter(location_px, std::move(data), operation,
+                            /*modifiers=*/0);
 }
 
-void WaylandWindow::OnDragDrop() {}
+int WaylandWindow::OnDragMotion(const gfx::PointF& point, int operation) {
+  WmDropHandler* drop_handler = GetWmDropHandler(*this);
+  if (!drop_handler)
+    return 0;
 
-void WaylandWindow::OnDragLeave() {}
+  auto location_px = gfx::ScalePoint(TranslateLocationToRootWindow(point),
+                                     buffer_scale(), buffer_scale());
 
-void WaylandWindow::OnDragSessionClose(uint32_t dnd_action) {}
+  // Wayland sends locations in DIP so they need to be translated to
+  // physical pixels.
+  // TODO(crbug.com/1102857): get the real event modifier here.
+  return drop_handler->OnDragMotion(location_px, operation,
+                                    /*modifiers=*/0);
+}
+
+void WaylandWindow::OnDragDrop() {
+  WmDropHandler* drop_handler = GetWmDropHandler(*this);
+  if (!drop_handler)
+    return;
+  // TODO(crbug.com/1102857): get the real event modifier here.
+  drop_handler->OnDragDrop({}, /*modifiers=*/0);
+}
+
+void WaylandWindow::OnDragLeave() {
+  WmDropHandler* drop_handler = GetWmDropHandler(*this);
+  if (!drop_handler)
+    return;
+  drop_handler->OnDragLeave();
+}
+
+void WaylandWindow::OnDragSessionClose(uint32_t dnd_action) {
+  DCHECK(drag_handler_delegate_);
+  drag_handler_delegate_->OnDragFinished(dnd_action);
+  drag_handler_delegate_ = nullptr;
+  connection()->event_source()->ResetPointerFlags();
+  std::move(drag_loop_quit_closure_).Run();
+}
 
 void WaylandWindow::SetBoundsDip(const gfx::Rect& bounds_dip) {
   SetBounds(gfx::ScaleToRoundedRect(bounds_dip, buffer_scale()));
@@ -482,8 +565,16 @@
   }
 }
 
-WaylandWindow* WaylandWindow::GetTopLevelWindow() {
-  return parent_window_ ? parent_window_->GetTopLevelWindow() : this;
+gfx::PointF WaylandWindow::TranslateLocationToRootWindow(
+    const gfx::PointF& location) {
+  auto* root_window = GetRootParentWindow();
+  DCHECK(root_window);
+  if (root_window == this)
+    return location;
+
+  gfx::Vector2d offset =
+      GetBounds().origin() - root_window->GetBounds().origin();
+  return location + gfx::Vector2dF(offset);
 }
 
 WaylandWindow* WaylandWindow::GetTopMostChildWindow() {
diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h
index 860ca85d..4e0a01c 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_window.h
@@ -15,6 +15,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
 #include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/ozone/platform/wayland/common/wayland_object.h"
@@ -23,10 +24,7 @@
 #include "ui/platform_window/platform_window.h"
 #include "ui/platform_window/platform_window_delegate.h"
 #include "ui/platform_window/platform_window_init_properties.h"
-
-namespace gfx {
-class PointF;
-}
+#include "ui/platform_window/wm/wm_drag_handler.h"
 
 namespace ui {
 
@@ -38,7 +36,9 @@
 
 using WidgetSubsurfaceSet = base::flat_set<std::unique_ptr<WaylandSubsurface>>;
 
-class WaylandWindow : public PlatformWindow, public PlatformEventDispatcher {
+class WaylandWindow : public PlatformWindow,
+                      public PlatformEventDispatcher,
+                      public WmDragHandler {
  public:
   ~WaylandWindow() override;
 
@@ -111,6 +111,14 @@
   // Returns current type of the window.
   PlatformWindowType type() const { return type_; }
 
+  // WmDragHandler
+  bool StartDrag(const ui::OSExchangeData& data,
+                 int operation,
+                 gfx::NativeCursor cursor,
+                 bool can_grab_pointer,
+                 WmDragHandler::Delegate* delegate) override;
+  void CancelDrag() override;
+
   // PlatformWindow
   void Show(bool inactive) override;
   void Hide() override;
@@ -211,7 +219,7 @@
 
   void UpdateCursorPositionFromEvent(std::unique_ptr<Event> event);
 
-  WaylandWindow* GetTopLevelWindow();
+  gfx::PointF TranslateLocationToRootWindow(const gfx::PointF& location);
 
   uint32_t DispatchEventToDelegate(const PlatformEvent& native_event);
 
@@ -288,6 +296,12 @@
   // AcceleratedWidget for this window. This will be unique even over time.
   gfx::AcceleratedWidget accelerated_widget_;
 
+  WmDragHandler::Delegate* drag_handler_delegate_ = nullptr;
+
+  base::OnceClosure drag_loop_quit_closure_;
+
+  base::WeakPtrFactory<WaylandWindow> weak_ptr_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(WaylandWindow);
 };