extended_drag_source_unittest.cc 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/exo/extended_drag_source.h"
  5. #include <memory>
  6. #include <string>
  7. #include "ash/constants/app_types.h"
  8. #include "ash/drag_drop/drag_drop_controller.h"
  9. #include "ash/drag_drop/toplevel_window_drag_delegate.h"
  10. #include "ash/public/cpp/tablet_mode.h"
  11. #include "ash/public/cpp/window_properties.h"
  12. #include "ash/shell.h"
  13. #include "ash/wm/toplevel_window_event_handler.h"
  14. #include "ash/wm/window_state.h"
  15. #include "base/strings/utf_string_conversions.h"
  16. #include "base/test/bind.h"
  17. #include "base/test/gmock_callback_support.h"
  18. #include "chromeos/ui/base/window_properties.h"
  19. #include "components/exo/buffer.h"
  20. #include "components/exo/data_source.h"
  21. #include "components/exo/data_source_delegate.h"
  22. #include "components/exo/drag_drop_operation.h"
  23. #include "components/exo/seat.h"
  24. #include "components/exo/shell_surface.h"
  25. #include "components/exo/surface.h"
  26. #include "components/exo/test/exo_test_base.h"
  27. #include "components/exo/test/exo_test_data_exchange_delegate.h"
  28. #include "components/exo/test/exo_test_helper.h"
  29. #include "components/exo/test/shell_surface_builder.h"
  30. #include "testing/gmock/include/gmock/gmock.h"
  31. #include "testing/gtest/include/gtest/gtest.h"
  32. #include "ui/aura/client/aura_constants.h"
  33. #include "ui/aura/client/drag_drop_client.h"
  34. #include "ui/aura/client/drag_drop_delegate.h"
  35. #include "ui/aura/window_tree_host.h"
  36. #include "ui/base/dragdrop/drag_drop_types.h"
  37. #include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
  38. #include "ui/base/dragdrop/os_exchange_data.h"
  39. #include "ui/events/event_utils.h"
  40. #include "ui/events/test/event_generator.h"
  41. #include "ui/events/test/events_test_utils.h"
  42. #include "ui/gfx/geometry/point.h"
  43. #include "ui/gfx/geometry/size.h"
  44. #include "ui/gfx/geometry/vector2d.h"
  45. using ::testing::_;
  46. using ::testing::AnyNumber;
  47. using ::testing::DoAll;
  48. using ::testing::InvokeWithoutArgs;
  49. using ::testing::SaveArg;
  50. namespace exo {
  51. namespace {
  52. void DispatchGesture(ui::EventType gesture_type, gfx::Point location) {
  53. ui::GestureEventDetails event_details(gesture_type);
  54. ui::GestureEvent gesture_event(location.x(), location.y(), 0,
  55. ui::EventTimeForNow(), event_details);
  56. ui::EventSource* event_source =
  57. ash::Shell::GetPrimaryRootWindow()->GetHost()->GetEventSource();
  58. ui::EventSourceTestApi event_source_test(event_source);
  59. ui::EventDispatchDetails details =
  60. event_source_test.SendEventToSink(&gesture_event);
  61. CHECK(!details.dispatcher_destroyed);
  62. }
  63. class TestExtendedDragSourceDelegate : public ExtendedDragSource::Delegate {
  64. public:
  65. TestExtendedDragSourceDelegate(bool allow_drop_no_target, bool lock_cursor)
  66. : allow_drap_no_target_(allow_drop_no_target),
  67. lock_cursor_(lock_cursor) {}
  68. TestExtendedDragSourceDelegate(const TestExtendedDragSourceDelegate&) =
  69. delete;
  70. TestExtendedDragSourceDelegate& operator=(
  71. const TestExtendedDragSourceDelegate&) = delete;
  72. ~TestExtendedDragSourceDelegate() override = default;
  73. // ExtendedDragSource::Delegate:
  74. bool ShouldAllowDropAnywhere() const override {
  75. return allow_drap_no_target_;
  76. }
  77. bool ShouldLockCursor() const override { return lock_cursor_; }
  78. void OnSwallowed(const std::string& mime_type) override {
  79. ASSERT_FALSE(swallowed_);
  80. swallowed_ = true;
  81. }
  82. void OnUnswallowed(const std::string& mime_type,
  83. const gfx::Vector2d& offset) override {
  84. ASSERT_TRUE(swallowed_);
  85. swallowed_ = false;
  86. }
  87. void OnDataSourceDestroying() override { delete this; }
  88. private:
  89. const bool allow_drap_no_target_;
  90. const bool lock_cursor_;
  91. bool swallowed_ = true;
  92. };
  93. class ExtendedDragSourceTest : public test::ExoTestBase {
  94. public:
  95. ExtendedDragSourceTest() {}
  96. ExtendedDragSourceTest(const ExtendedDragSourceTest&) = delete;
  97. ExtendedDragSourceTest& operator=(const ExtendedDragSourceTest&) = delete;
  98. ~ExtendedDragSourceTest() override = default;
  99. void SetUp() override {
  100. test::ExoTestBase::SetUp();
  101. drag_drop_controller_ = static_cast<ash::DragDropController*>(
  102. aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()));
  103. ASSERT_TRUE(drag_drop_controller_);
  104. drag_drop_controller_->set_should_block_during_drag_drop(false);
  105. drag_drop_controller_->set_enabled(true);
  106. seat_ =
  107. std::make_unique<Seat>(std::make_unique<TestDataExchangeDelegate>());
  108. data_source_delegate_ = std::make_unique<TestDataSourceDelegate>();
  109. data_source_ = std::make_unique<DataSource>(data_source_delegate_.get());
  110. extended_drag_source_ = std::make_unique<ExtendedDragSource>(
  111. data_source_.get(), new TestExtendedDragSourceDelegate(
  112. /*allow_drop_no_target=*/true,
  113. /*lock_cursor=*/true));
  114. }
  115. void TearDown() override {
  116. extended_drag_source_.reset();
  117. data_source_.reset();
  118. data_source_delegate_.reset();
  119. seat_.reset();
  120. test::ExoTestBase::TearDown();
  121. }
  122. protected:
  123. void StartExtendedDragSession(aura::Window* origin,
  124. gfx::Point start_location,
  125. int operation,
  126. ui::mojom::DragEventSource source) {
  127. auto data = std::make_unique<ui::OSExchangeData>();
  128. data->SetString(u"I am being dragged");
  129. drag_drop_controller_->set_toplevel_window_drag_delegate(
  130. extended_drag_source_.get());
  131. drag_drop_controller_->StartDragAndDrop(std::move(data),
  132. origin->GetRootWindow(), origin,
  133. start_location, operation, source);
  134. }
  135. std::unique_ptr<Buffer> CreateBuffer(gfx::Size size) {
  136. return std::make_unique<Buffer>(
  137. exo_test_helper()->CreateGpuMemoryBuffer(size));
  138. }
  139. ash::DragDropController* drag_drop_controller_ = nullptr;
  140. std::unique_ptr<Seat> seat_;
  141. std::unique_ptr<DataSource> data_source_;
  142. std::unique_ptr<ExtendedDragSource> extended_drag_source_;
  143. std::unique_ptr<TestDataSourceDelegate> data_source_delegate_;
  144. };
  145. // Enables or disables tablet mode and waits for the transition to finish.
  146. void SetTabletModeEnabled(bool enabled) {
  147. // This does not use ShellTestApi or TabletModeControllerTestApi because those
  148. // are implemented in test-only files.
  149. ash::TabletMode::Waiter waiter(enabled);
  150. ash::Shell::Get()->tablet_mode_controller()->SetEnabledForTest(enabled);
  151. waiter.Wait();
  152. }
  153. } // namespace
  154. TEST_F(ExtendedDragSourceTest, DestroySource) {
  155. Surface origin;
  156. // Give |origin| a root window and start DragDropOperation.
  157. GetContext()->AddChild(origin.window());
  158. seat_->StartDrag(data_source_.get(), &origin,
  159. /*icon=*/nullptr, ui::mojom::DragEventSource::kMouse);
  160. // Ensure that destroying the data source invalidates its extended_drag_source
  161. // counterpart for the rest of its lifetime.
  162. EXPECT_TRUE(seat_->get_drag_drop_operation_for_testing());
  163. EXPECT_TRUE(extended_drag_source_->IsActive());
  164. data_source_.reset();
  165. EXPECT_FALSE(seat_->get_drag_drop_operation_for_testing());
  166. EXPECT_FALSE(extended_drag_source_->IsActive());
  167. }
  168. TEST_F(ExtendedDragSourceTest, DragSurfaceAlreadyMapped) {
  169. // Create and map a toplevel shell surface.
  170. auto surface = std::make_unique<Surface>();
  171. auto shell_surface = std::make_unique<ShellSurface>(surface.get());
  172. auto buffer = CreateBuffer({32, 32});
  173. surface->Attach(buffer.get());
  174. surface->Commit();
  175. gfx::Point origin(0, 0);
  176. shell_surface->GetWidget()->SetBounds(gfx::Rect(origin, buffer->GetSize()));
  177. EXPECT_EQ(origin, surface->window()->GetBoundsInRootWindow().origin());
  178. // Set it as the dragged surface when it's already mapped. This allows clients
  179. // to set existing/visible windows as the dragged surface and possibly
  180. // snapping it to another surface, which is required for Chrome's tab drag use
  181. // case, for example.
  182. aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
  183. extended_drag_source_->Drag(surface.get(), gfx::Vector2d());
  184. EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
  185. EXPECT_TRUE(extended_drag_source_->GetDragOffsetForTesting().has_value());
  186. EXPECT_EQ(gfx::Vector2d(0, 0),
  187. *extended_drag_source_->GetDragOffsetForTesting());
  188. // Start the DND + extended-drag session.
  189. // Creates a mouse-pressed event before starting the drag session.
  190. ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
  191. generator.PressLeftButton();
  192. StartExtendedDragSession(window, gfx::Point(0, 0),
  193. ui::DragDropTypes::DRAG_MOVE,
  194. ui::mojom::DragEventSource::kMouse);
  195. // Verify that dragging it by 190,190, with the current pointer location being
  196. // 10,10 will set the dragged window bounds as expected.
  197. generator.MoveMouseBy(190, 190);
  198. generator.ReleaseLeftButton();
  199. EXPECT_EQ(gfx::Point(200, 200), window->GetBoundsInScreen().origin());
  200. }
  201. // This class installs an observer to the window being dragged.
  202. // The goal is to ensure the drag 'n drop only effectively starts
  203. // off of the aura::WindowObserver::OnWindowVisibilityChanged() hook,
  204. // when it is guarantee the its state is properly set.
  205. class WindowObserverHookChecker : public aura::WindowObserver {
  206. public:
  207. explicit WindowObserverHookChecker(aura::Window* surface_window)
  208. : surface_window_(surface_window) {
  209. DCHECK(!surface_window_->GetRootWindow());
  210. surface_window_->AddObserver(this);
  211. }
  212. ~WindowObserverHookChecker() {
  213. DCHECK(dragged_window_);
  214. dragged_window_->RemoveObserver(this);
  215. }
  216. void OnWindowAddedToRootWindow(aura::Window* window) override {
  217. dragged_window_ = surface_window_->GetToplevelWindow();
  218. dragged_window_->AddObserver(this);
  219. surface_window_->RemoveObserver(this);
  220. dragged_window_->SetProperty(aura::client::kAppType,
  221. static_cast<int>(ash::AppType::LACROS));
  222. }
  223. MOCK_METHOD(void,
  224. OnWindowVisibilityChanging,
  225. (aura::Window*, bool),
  226. (override));
  227. MOCK_METHOD(void,
  228. OnWindowVisibilityChanged,
  229. (aura::Window*, bool),
  230. (override));
  231. private:
  232. aura::Window* surface_window_ = nullptr;
  233. aura::Window* dragged_window_ = nullptr;
  234. };
  235. // Differently than the window observer class above, this one observers
  236. // the window instance being directly provided to its ctor.
  237. class WindowObserverHookChecker2 : public aura::WindowObserver {
  238. public:
  239. explicit WindowObserverHookChecker2(aura::Window* surface_window)
  240. : surface_window_(surface_window) {
  241. surface_window_->AddObserver(this);
  242. }
  243. ~WindowObserverHookChecker2() { surface_window_->RemoveObserver(this); }
  244. MOCK_METHOD(void,
  245. OnWindowPropertyChanged,
  246. (aura::Window*, const void*, intptr_t),
  247. (override));
  248. private:
  249. aura::Window* surface_window_ = nullptr;
  250. };
  251. TEST_F(ExtendedDragSourceTest, DragSurfaceNotMappedYet) {
  252. // Create and Map the drag origin surface
  253. auto surface = std::make_unique<Surface>();
  254. auto shell_surface = std::make_unique<ShellSurface>(surface.get());
  255. auto buffer = CreateBuffer({32, 32});
  256. surface->Attach(buffer.get());
  257. surface->Commit();
  258. // Creates a mouse-pressed event before starting the drag session.
  259. ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
  260. generator.PressLeftButton();
  261. // Start the DND + extended-drag session.
  262. StartExtendedDragSession(shell_surface->GetWidget()->GetNativeWindow(),
  263. gfx::Point(0, 0), ui::DragDropTypes::DRAG_MOVE,
  264. ui::mojom::DragEventSource::kMouse);
  265. // Create a new surface to emulate a "detachment" process.
  266. auto detached_surface = std::make_unique<Surface>();
  267. auto detached_shell_surface =
  268. std::make_unique<ShellSurface>(detached_surface.get());
  269. // Set |surface| as the dragged surface while it's still unmapped/invisible.
  270. // This can be used to implement tab detaching in Chrome's tab drag use case,
  271. // for example. Extended drag source will monitor surface mapping and it's
  272. // expected to position it correctly using the provided drag offset here
  273. // relative to the current pointer location.
  274. extended_drag_source_->Drag(detached_surface.get(), gfx::Vector2d(10, 10));
  275. EXPECT_FALSE(extended_drag_source_->GetDraggedWindowForTesting());
  276. EXPECT_TRUE(extended_drag_source_->GetDragOffsetForTesting().has_value());
  277. EXPECT_EQ(gfx::Vector2d(10, 10),
  278. *extended_drag_source_->GetDragOffsetForTesting());
  279. // Ensure drag 'n drop starts after
  280. // ExtendedDragSource::OnDraggedWindowVisibilityChanged()
  281. aura::Window* toplevel_window;
  282. WindowObserverHookChecker checker(detached_surface->window());
  283. EXPECT_CALL(checker, OnWindowVisibilityChanging(_, _))
  284. .Times(1)
  285. .WillOnce(DoAll(
  286. SaveArg<0>(&toplevel_window), InvokeWithoutArgs([&]() {
  287. auto* toplevel_handler =
  288. ash::Shell::Get()->toplevel_window_event_handler();
  289. EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
  290. EXPECT_TRUE(toplevel_window->GetProperty(ash::kIsDraggingTabsKey));
  291. })));
  292. EXPECT_CALL(checker, OnWindowVisibilityChanged(_, _))
  293. .Times(1)
  294. .WillOnce(InvokeWithoutArgs([]() {
  295. auto* toplevel_handler =
  296. ash::Shell::Get()->toplevel_window_event_handler();
  297. EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
  298. }));
  299. // Map the |detached_surface|.
  300. auto detached_buffer = CreateBuffer({50, 50});
  301. detached_surface->Attach(detached_buffer.get());
  302. detached_surface->Commit();
  303. // Ensure the toplevel window for the dragged surface set above, is correctly
  304. // detected, after it's mapped.
  305. aura::Window* window = detached_shell_surface->GetWidget()->GetNativeWindow();
  306. EXPECT_TRUE(extended_drag_source_->GetDraggedWindowForTesting());
  307. EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
  308. // Verify that dragging it by 100,100, with drag offset 10,10 and current
  309. // pointer location 50,50 will set the dragged window bounds as expected.
  310. generator.set_current_screen_location(gfx::Point(100, 100));
  311. generator.DragMouseBy(50, 50);
  312. generator.ReleaseLeftButton();
  313. EXPECT_EQ(gfx::Point(140, 140), window->GetBoundsInScreen().origin());
  314. }
  315. TEST_F(ExtendedDragSourceTest, DragSurfaceNotMappedYet_TabletMode) {
  316. SetTabletModeEnabled(true);
  317. // Create and Map the drag origin surface
  318. auto surface = std::make_unique<Surface>();
  319. auto shell_surface = std::make_unique<ShellSurface>(surface.get());
  320. auto buffer = CreateBuffer({32, 32});
  321. surface->Attach(buffer.get());
  322. surface->Commit();
  323. // Creates a mouse-pressed event before starting the drag session.
  324. ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
  325. generator.PressLeftButton();
  326. // Start the DND + extended-drag session.
  327. StartExtendedDragSession(shell_surface->GetWidget()->GetNativeWindow(),
  328. gfx::Point(0, 0), ui::DragDropTypes::DRAG_MOVE,
  329. ui::mojom::DragEventSource::kMouse);
  330. // Create a new surface to emulate a "detachment" process.
  331. auto detached_surface = std::make_unique<Surface>();
  332. auto detached_shell_surface =
  333. std::make_unique<ShellSurface>(detached_surface.get());
  334. // Set |surface| as the dragged surface while it's still unmapped/invisible.
  335. // This can be used to implement tab detaching in Chrome's tab drag use case,
  336. // for example. Extended drag source will monitor surface mapping and it's
  337. // expected to position it correctly using the provided drag offset here
  338. // relative to the current pointer location.
  339. extended_drag_source_->Drag(detached_surface.get(), gfx::Vector2d(10, 10));
  340. EXPECT_FALSE(extended_drag_source_->GetDraggedWindowForTesting());
  341. EXPECT_TRUE(extended_drag_source_->GetDragOffsetForTesting().has_value());
  342. EXPECT_EQ(gfx::Vector2d(10, 10),
  343. *extended_drag_source_->GetDragOffsetForTesting());
  344. // Ensure drag 'n drop starts after
  345. // ExtendedDragSource::OnDraggedWindowVisibilityChanged()
  346. aura::Window* toplevel_window = nullptr;
  347. WindowObserverHookChecker checker(detached_surface->window());
  348. EXPECT_CALL(checker, OnWindowVisibilityChanging(_, _))
  349. .Times(1)
  350. .WillOnce(DoAll(
  351. SaveArg<0>(&toplevel_window), InvokeWithoutArgs([&]() {
  352. auto* toplevel_handler =
  353. ash::Shell::Get()->toplevel_window_event_handler();
  354. EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
  355. EXPECT_TRUE(toplevel_window->GetProperty(ash::kIsDraggingTabsKey));
  356. })));
  357. EXPECT_CALL(checker, OnWindowVisibilityChanged(_, _))
  358. .Times(1)
  359. .WillOnce(DoAll(
  360. SaveArg<0>(&toplevel_window), InvokeWithoutArgs([&]() {
  361. auto* toplevel_handler =
  362. ash::Shell::Get()->toplevel_window_event_handler();
  363. EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
  364. gfx::Rect* override_bounds =
  365. toplevel_window->GetProperty(ash::kRestoreBoundsOverrideKey);
  366. EXPECT_TRUE(override_bounds && !override_bounds->IsEmpty());
  367. EXPECT_EQ(*override_bounds, toplevel_window->bounds());
  368. })));
  369. // Map the |detached_surface|.
  370. auto detached_buffer = CreateBuffer({50, 50});
  371. detached_surface->Attach(detached_buffer.get());
  372. detached_surface->Commit();
  373. // Ensure the toplevel window for the dragged surface set above, is correctly
  374. // detected, after it's mapped.
  375. aura::Window* window = detached_shell_surface->GetWidget()->GetNativeWindow();
  376. EXPECT_TRUE(extended_drag_source_->GetDraggedWindowForTesting());
  377. EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
  378. WindowObserverHookChecker2 checker2(
  379. shell_surface->GetWidget()->GetNativeWindow());
  380. aura::Window* source_window = nullptr;
  381. const void* property_key;
  382. EXPECT_CALL(checker2, OnWindowPropertyChanged(_, _, _))
  383. .Times(AnyNumber())
  384. .WillRepeatedly(
  385. DoAll(SaveArg<0>(&source_window), SaveArg<1>(&property_key),
  386. InvokeWithoutArgs([&]() {
  387. if (property_key ==
  388. chromeos::kIsDeferredTabDraggingTargetWindowKey) {
  389. bool new_value = source_window->GetProperty(
  390. chromeos::kIsDeferredTabDraggingTargetWindowKey);
  391. EXPECT_TRUE(new_value);
  392. }
  393. })));
  394. generator.ReleaseLeftButton();
  395. SetTabletModeEnabled(false);
  396. }
  397. TEST_F(ExtendedDragSourceTest, DragSurfaceNotMappedYet_Touch) {
  398. // Create and Map the drag origin surface
  399. auto surface = std::make_unique<Surface>();
  400. auto shell_surface = std::make_unique<ShellSurface>(surface.get());
  401. auto buffer = CreateBuffer({32, 32});
  402. surface->Attach(buffer.get());
  403. surface->Commit();
  404. // Start the DND + extended-drag session.
  405. StartExtendedDragSession(shell_surface->GetWidget()->GetNativeWindow(),
  406. gfx::Point(0, 0), ui::DragDropTypes::DRAG_MOVE,
  407. ui::mojom::DragEventSource::kTouch);
  408. // Create a new surface to emulate a "detachment" process.
  409. auto detached_surface = std::make_unique<Surface>();
  410. auto detached_shell_surface =
  411. std::make_unique<ShellSurface>(detached_surface.get());
  412. // Set |surface| as the dragged surface while it's still unmapped/invisible.
  413. // This can be used to implement tab detaching in Chrome's tab drag use case,
  414. // for example. Extended drag source will monitor surface mapping and it's
  415. // expected to position it correctly using the provided drag offset here
  416. // relative to the current pointer location.
  417. extended_drag_source_->Drag(detached_surface.get(), gfx::Vector2d(10, 10));
  418. EXPECT_FALSE(extended_drag_source_->GetDraggedWindowForTesting());
  419. EXPECT_TRUE(extended_drag_source_->GetDragOffsetForTesting().has_value());
  420. EXPECT_EQ(gfx::Vector2d(10, 10),
  421. *extended_drag_source_->GetDragOffsetForTesting());
  422. // Initiate the gesture sequence.
  423. DispatchGesture(ui::ET_GESTURE_BEGIN, gfx::Point(10, 10));
  424. // Map the |detached_surface|.
  425. auto detached_buffer = CreateBuffer({50, 50});
  426. detached_surface->Attach(detached_buffer.get());
  427. detached_surface->Commit();
  428. // Ensure the toplevel window for the dragged surface set above, is correctly
  429. // detected, after it's mapped.
  430. aura::Window* window = detached_shell_surface->GetWidget()->GetNativeWindow();
  431. EXPECT_TRUE(extended_drag_source_->GetDraggedWindowForTesting());
  432. EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
  433. // Verify that dragging it by 100,100, with drag offset 10,10 and current
  434. // pointer location 50,50 will set the dragged window bounds as expected.
  435. ui::test::EventGenerator generator(GetContext());
  436. generator.set_current_screen_location(gfx::Point(100, 100));
  437. generator.PressMoveAndReleaseTouchBy(50, 50);
  438. EXPECT_EQ(gfx::Point(140, 140), window->GetBoundsInScreen().origin());
  439. }
  440. TEST_F(ExtendedDragSourceTest, DestroyDraggedSurfaceWhileDragging) {
  441. // Create and map a toplevel shell surface.
  442. gfx::Size buffer_size(32, 32);
  443. std::unique_ptr<Buffer> buffer(
  444. new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
  445. std::unique_ptr<Surface> surface(new Surface);
  446. std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
  447. surface->Attach(buffer.get());
  448. surface->Commit();
  449. gfx::Point origin(0, 0);
  450. shell_surface->GetWidget()->SetBounds(gfx::Rect(origin, buffer_size));
  451. EXPECT_EQ(origin, surface->window()->GetBoundsInRootWindow().origin());
  452. // Start dragging |surface|'s window.
  453. extended_drag_source_->Drag(surface.get(), gfx::Vector2d());
  454. aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
  455. EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
  456. // Make sure extended drag source gracefully handles window destruction during
  457. // while the drag session is still alive.
  458. shell_surface.reset();
  459. EXPECT_TRUE(surface->window()->GetBoundsInScreen().origin().IsOrigin());
  460. ui::test::EventGenerator generator(GetContext());
  461. generator.DragMouseBy(190, 190);
  462. EXPECT_TRUE(surface->window()->GetBoundsInScreen().origin().IsOrigin());
  463. }
  464. // Regression test for crbug.com/1330125.
  465. // In exo, the drag source window could be destroyed while the dragged window
  466. // is updating its visibility. The example in this bug was while merging a
  467. // single-tab browser window in and out of another browser window.
  468. TEST_F(ExtendedDragSourceTest, DestroyDragSourceWindowWhileDragging) {
  469. auto shell_surface =
  470. exo::test::ShellSurfaceBuilder({32, 32}).BuildShellSurface();
  471. auto* surface = shell_surface->root_surface();
  472. // Start hidden so when it's shown later it triggers the memory violation.
  473. shell_surface->GetWidget()->Hide();
  474. extended_drag_source_->Drag(surface, gfx::Vector2d());
  475. aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
  476. EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
  477. auto drag_source_window = CreateToplevelTestWindow({20, 30, 200, 100});
  478. extended_drag_source_->OnToplevelWindowDragStarted(
  479. {70.0, 70.0}, ui::mojom::DragEventSource::kMouse,
  480. drag_source_window.get());
  481. EXPECT_EQ(extended_drag_source_->GetDragSourceWindowForTesting(),
  482. drag_source_window.get());
  483. drag_source_window.reset();
  484. EXPECT_EQ(extended_drag_source_->GetDragSourceWindowForTesting(), nullptr);
  485. // Without the fix, in asan build, this should cause the use-after-free.
  486. shell_surface->GetWidget()->Show();
  487. }
  488. TEST_F(ExtendedDragSourceTest, DragRequestsInRow_NoCrash) {
  489. // Create and map a toplevel shell surface, but hidden.
  490. auto shell_surface =
  491. exo::test::ShellSurfaceBuilder({32, 32}).BuildShellSurface();
  492. auto* surface = shell_surface->root_surface();
  493. shell_surface->GetWidget()->Hide();
  494. // Request two dragging |surface|'s actions in a roll, which a real
  495. // world use case scenario when system is under heavy load.
  496. extended_drag_source_->Drag(surface, gfx::Vector2d());
  497. extended_drag_source_->Drag(surface, gfx::Vector2d());
  498. // Make sure extended drag source gracefully handles window destruction during
  499. // while the drag session is still alive.
  500. shell_surface.reset();
  501. }
  502. TEST_F(ExtendedDragSourceTest, CancelDraggingOperation) {
  503. // Create and map a toplevel shell surface.
  504. auto shell_surface =
  505. exo::test::ShellSurfaceBuilder({32, 32}).BuildShellSurface();
  506. auto* surface = shell_surface->root_surface();
  507. extended_drag_source_->Drag(surface, gfx::Vector2d());
  508. // Start the DND + extended-drag session.
  509. // Creates a mouse-pressed event before starting the drag session.
  510. ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
  511. generator.PressLeftButton();
  512. // Start a DragDropOperation.
  513. drag_drop_controller_->set_should_block_during_drag_drop(true);
  514. seat_->StartDrag(data_source_.get(), surface, /*icon=*/nullptr,
  515. ui::mojom::DragEventSource::kMouse);
  516. auto task_1 = base::BindLambdaForTesting([&]() {
  517. generator.MoveMouseBy(190, 190);
  518. auto* toplevel_handler = ash::Shell::Get()->toplevel_window_event_handler();
  519. EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
  520. drag_drop_controller_->DragCancel();
  521. EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
  522. });
  523. base::ThreadTaskRunnerHandle::Get()->PostTask(
  524. FROM_HERE, base::BindOnce(std::move(task_1)));
  525. base::RunLoop().RunUntilIdle();
  526. EXPECT_TRUE(data_source_delegate_->cancelled());
  527. }
  528. // Make sure that the extended drag is recognized as shell surface drag.
  529. TEST_F(ExtendedDragSourceTest, DragWithScreenCoordinates) {
  530. auto shell_surface = test::ShellSurfaceBuilder({20, 20}).BuildShellSurface();
  531. TestDataExchangeDelegate data_exchange_delegate;
  532. auto delegate = std::make_unique<TestDataSourceDelegate>();
  533. auto data_source = std::make_unique<DataSource>(delegate.get());
  534. constexpr char kTextMimeType[] = "text/plain";
  535. data_source->Offer(kTextMimeType);
  536. gfx::PointF location(10, 10);
  537. auto operation = DragDropOperation::Create(
  538. &data_exchange_delegate, data_source.get(), shell_surface->root_surface(),
  539. nullptr, location, ui::mojom::DragEventSource::kMouse);
  540. auto* drag_drop_controller = static_cast<ash::DragDropController*>(
  541. aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()));
  542. EXPECT_FALSE(shell_surface->IsDragged());
  543. base::RunLoop loop;
  544. drag_drop_controller->SetLoopClosureForTesting(
  545. base::BindLambdaForTesting([&]() {
  546. // The drag session must have been started by the time
  547. // drag loop starts.
  548. EXPECT_TRUE(shell_surface->IsDragged());
  549. drag_drop_controller->DragCancel();
  550. loop.Quit();
  551. }),
  552. base::DoNothing());
  553. loop.Run();
  554. operation.reset();
  555. EXPECT_FALSE(shell_surface->IsDragged());
  556. }
  557. #if BUILDFLAG(IS_CHROMEOS_ASH)
  558. TEST_F(ExtendedDragSourceTest, DragWithScreenCoordinates_Touch) {
  559. // Create and map a toplevel shell surface.
  560. auto shell_surface =
  561. exo::test::ShellSurfaceBuilder({32, 32}).BuildShellSurface();
  562. auto* surface = shell_surface->root_surface();
  563. extended_drag_source_->Drag(surface, gfx::Vector2d());
  564. // Start the DND + extended-drag session.
  565. // Creates a mouse-pressed event before starting the drag session.
  566. ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
  567. generator.MoveTouch(surface->window()->GetBoundsInScreen().origin());
  568. generator.PressTouch();
  569. // Start a DragDropOperation.
  570. drag_drop_controller_->set_should_block_during_drag_drop(true);
  571. seat_->StartDrag(data_source_.get(), surface, /*icon=*/nullptr,
  572. ui::mojom::DragEventSource::kTouch);
  573. base::RunLoop loop;
  574. drag_drop_controller_->SetLoopClosureForTesting(
  575. base::BindLambdaForTesting([&]() {
  576. auto* toplevel_handler =
  577. ash::Shell::Get()->toplevel_window_event_handler();
  578. EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
  579. auto* window_state =
  580. ash::WindowState::Get(surface->window()->GetToplevelWindow());
  581. auto* drag_details = window_state->drag_details();
  582. DCHECK(drag_details);
  583. EXPECT_EQ(gfx::ToRoundedPoint(drag_details->initial_location_in_parent),
  584. surface->window()->GetBoundsInScreen().origin());
  585. drag_drop_controller_->DragCancel();
  586. EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
  587. loop.Quit();
  588. }),
  589. base::DoNothing());
  590. loop.Run();
  591. EXPECT_TRUE(data_source_delegate_->cancelled());
  592. }
  593. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  594. TEST_F(ExtendedDragSourceTest, DragToAnotherDisplay) {
  595. UpdateDisplay("400x300,800x600");
  596. const gfx::Rect kOriginalWindowBounds(410, 10, 500, 200);
  597. // Create and map a toplevel shell surface, with the size larger than 2nd
  598. // display to test if configure uses the adjusted size.
  599. auto shell_surface =
  600. exo::test::ShellSurfaceBuilder(kOriginalWindowBounds.size())
  601. .SetOrigin(kOriginalWindowBounds.origin())
  602. .BuildShellSurface();
  603. auto* surface = shell_surface->root_surface();
  604. EXPECT_EQ(kOriginalWindowBounds,
  605. shell_surface->GetWidget()->GetWindowBoundsInScreen());
  606. auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(
  607. shell_surface->GetWidget()->GetNativeWindow());
  608. EXPECT_EQ(gfx::Rect(400, 0, 800, 600), display.bounds());
  609. uint32_t serial = 0;
  610. gfx::Rect drop_bounds;
  611. auto configure_callback = base::BindLambdaForTesting(
  612. [&](const gfx::Rect& bounds, chromeos::WindowStateType state_type,
  613. bool resizing, bool activated, const gfx::Vector2d& origin_offset) {
  614. drop_bounds = bounds;
  615. return ++serial;
  616. });
  617. std::vector<gfx::Point> origins;
  618. auto origin_change_callback = base::BindLambdaForTesting(
  619. [&](const gfx::Point& point) { origins.push_back(point); });
  620. // Map shell surface.
  621. shell_surface->set_configure_callback(configure_callback);
  622. shell_surface->set_origin_change_callback(origin_change_callback);
  623. constexpr int kDragOffset = 100;
  624. extended_drag_source_->Drag(surface, gfx::Vector2d(kDragOffset, 0));
  625. // Start the DND + extended-drag session.
  626. // Creates a mouse-pressed event before starting the drag session.
  627. ui::test::EventGenerator* generator = GetEventGenerator();
  628. generator->MoveMouseTo({410 + kDragOffset, 10});
  629. generator->PressLeftButton();
  630. // Start a DragDropOperation.
  631. drag_drop_controller_->set_should_block_during_drag_drop(true);
  632. seat_->StartDrag(data_source_.get(), surface, /*icon=*/nullptr,
  633. ui::mojom::DragEventSource::kMouse);
  634. // Just move to the middle to avoid snapping.
  635. int x_movement = 300;
  636. constexpr int kXDragDelta = 20;
  637. auto* toplevel_handler = ash::Shell::Get()->toplevel_window_event_handler();
  638. base::RunLoop loop;
  639. size_t move_count = 0;
  640. drag_drop_controller_->SetLoopClosureForTesting(
  641. base::BindLambdaForTesting([&]() {
  642. if (x_movement == 300) {
  643. auto* window_state = ash::WindowState::Get(
  644. shell_surface->GetWidget()->GetNativeWindow());
  645. EXPECT_EQ(gfx::PointF(110, 10),
  646. window_state->drag_details()->initial_location_in_parent);
  647. }
  648. if (x_movement > 0) {
  649. x_movement -= kXDragDelta;
  650. move_count++;
  651. generator->MoveMouseBy(-kXDragDelta, 0);
  652. EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
  653. } else {
  654. generator->ReleaseLeftButton();
  655. }
  656. }),
  657. loop.QuitClosure());
  658. loop.Run();
  659. EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
  660. EXPECT_FALSE(shell_surface->GetWidget()->IsMaximized());
  661. display = display::Screen::GetScreen()->GetDisplayNearestWindow(
  662. shell_surface->GetWidget()->GetNativeWindow());
  663. gfx::Size secondary_display_size(400, 300);
  664. EXPECT_EQ(gfx::Rect(secondary_display_size), display.bounds());
  665. EXPECT_EQ(move_count, origins.size());
  666. // Configure when dropped.
  667. EXPECT_EQ(1u, serial);
  668. // Upon drop, the window is shrunk horizontally.
  669. gfx::Rect expected_drop_bounds =
  670. gfx::Rect(origins.back(), kOriginalWindowBounds.size());
  671. expected_drop_bounds.set_width(secondary_display_size.width());
  672. int offset =
  673. (kOriginalWindowBounds.width() - expected_drop_bounds.width()) / 2;
  674. expected_drop_bounds.set_x(expected_drop_bounds.x() + offset);
  675. EXPECT_EQ(expected_drop_bounds, drop_bounds);
  676. EXPECT_EQ(expected_drop_bounds,
  677. shell_surface->GetWidget()->GetWindowBoundsInScreen());
  678. // Make sure all origins are kXDragDelta apart.
  679. for (size_t i = 1; origins.size() < i; i++) {
  680. EXPECT_EQ(origins[i - 1].x() + kXDragDelta, origins[i].x());
  681. }
  682. // The size will be adjusted when dropped.
  683. EXPECT_EQ(gfx::Size(400, 200), drop_bounds.size());
  684. }
  685. } // namespace exo