drag_drop_operation_unittest.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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/drag_drop_operation.h"
  5. #include <memory>
  6. #include "ash/shell.h"
  7. #include "base/bind.h"
  8. #include "base/containers/flat_map.h"
  9. #include "base/files/file_util.h"
  10. #include "base/run_loop.h"
  11. #include "base/test/bind.h"
  12. #include "base/threading/sequenced_task_runner_handle.h"
  13. #include "components/exo/buffer.h"
  14. #include "components/exo/data_exchange_delegate.h"
  15. #include "components/exo/data_source.h"
  16. #include "components/exo/data_source_delegate.h"
  17. #include "components/exo/shell_surface.h"
  18. #include "components/exo/surface.h"
  19. #include "components/exo/test/exo_test_base.h"
  20. #include "components/exo/test/exo_test_data_exchange_delegate.h"
  21. #include "components/exo/test/shell_surface_builder.h"
  22. #include "testing/gmock/include/gmock/gmock.h"
  23. #include "ui/aura/client/drag_drop_client.h"
  24. #include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
  25. #include "ui/base/data_transfer_policy/data_transfer_policy_controller.h"
  26. #include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
  27. #include "ui/events/test/event_generator.h"
  28. #include "ui/gfx/geometry/point_f.h"
  29. #if BUILDFLAG(IS_CHROMEOS_ASH)
  30. #include "ash/drag_drop/drag_drop_controller.h"
  31. #include "url/gurl.h"
  32. #endif
  33. namespace exo {
  34. namespace {
  35. using ::testing::_;
  36. using ::testing::Property;
  37. constexpr char kTextMimeType[] = "text/plain";
  38. } // namespace
  39. class DragDropOperationTest : public test::ExoTestBase,
  40. public aura::client::DragDropClientObserver {
  41. public:
  42. DragDropOperationTest() = default;
  43. ~DragDropOperationTest() override = default;
  44. DragDropOperationTest(const DragDropOperationTest&) = delete;
  45. DragDropOperationTest& operator=(const DragDropOperationTest&) = delete;
  46. void SetUp() override {
  47. test::ExoTestBase::SetUp();
  48. aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow())
  49. ->AddObserver(this);
  50. }
  51. void TearDown() override {
  52. aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow())
  53. ->RemoveObserver(this);
  54. test::ExoTestBase::TearDown();
  55. }
  56. // aura::client::DragDropClientObserver:
  57. void OnDragStarted() override {
  58. drag_start_count_++;
  59. if (!drag_blocked_callback_.is_null()) {
  60. base::SequencedTaskRunnerHandle::Get()->PostTask(
  61. FROM_HERE, std::move(drag_blocked_callback_));
  62. }
  63. }
  64. void OnDragCompleted(const ui::DropTargetEvent& event) override {
  65. drag_end_count_++;
  66. }
  67. void OnDragCancelled() override { drag_end_count_++; }
  68. protected:
  69. void set_drag_blocked_callback(base::OnceClosure callback) {
  70. drag_blocked_callback_ = std::move(callback);
  71. }
  72. int GetDragStartCountAndReset() {
  73. int result = drag_start_count_;
  74. drag_start_count_ = 0;
  75. return result;
  76. }
  77. int GetDragEndCountAndReset() {
  78. int result = drag_end_count_;
  79. drag_end_count_ = 0;
  80. return result;
  81. }
  82. private:
  83. // Callback running inside the nested RunLoop in StartDragAndDrop().
  84. base::OnceClosure drag_blocked_callback_;
  85. int drag_start_count_ = 0;
  86. int drag_end_count_ = 0;
  87. };
  88. TEST_F(DragDropOperationTest, DeleteDuringDragging) {
  89. TestDataExchangeDelegate data_exchange_delegate;
  90. auto delegate = std::make_unique<TestDataSourceDelegate>();
  91. auto data_source = std::make_unique<DataSource>(delegate.get());
  92. data_source->Offer(kTextMimeType);
  93. auto origin_surface = std::make_unique<Surface>();
  94. ash::Shell::GetPrimaryRootWindow()->AddChild(origin_surface->window());
  95. gfx::Size buffer_size(100, 100);
  96. std::unique_ptr<Buffer> buffer(
  97. new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
  98. auto icon_surface = std::make_unique<Surface>();
  99. icon_surface->Attach(buffer.get());
  100. auto operation = DragDropOperation::Create(
  101. &data_exchange_delegate, data_source.get(), origin_surface.get(),
  102. icon_surface.get(), gfx::PointF(), ui::mojom::DragEventSource::kMouse);
  103. icon_surface->Commit();
  104. base::RunLoop run_loop;
  105. set_drag_blocked_callback(base::BindOnce(
  106. [](std::unique_ptr<DataSource> data_source,
  107. base::WeakPtr<DragDropOperation> operation,
  108. base::OnceClosure quit_closure) {
  109. // This function runs inside the nested RunLoop in
  110. // ash::DragDropController::StartDragAndDrop().
  111. EXPECT_TRUE(operation);
  112. // Deleting DataSource causes DragDropOperation to be deleted as well.
  113. data_source.reset();
  114. EXPECT_FALSE(operation);
  115. std::move(quit_closure).Run();
  116. },
  117. std::move(data_source), operation, run_loop.QuitClosure()));
  118. run_loop.Run();
  119. EXPECT_FALSE(operation);
  120. }
  121. #if BUILDFLAG(IS_CHROMEOS_ASH)
  122. TEST_F(DragDropOperationTest, DragDropFromPopup) {
  123. static_cast<ash::DragDropController*>(
  124. aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()))
  125. ->set_should_block_during_drag_drop(false);
  126. TestDataExchangeDelegate data_exchange_delegate;
  127. auto delegate = std::make_unique<TestDataSourceDelegate>();
  128. auto data_source = std::make_unique<DataSource>(delegate.get());
  129. data_source->Offer(kTextMimeType);
  130. auto origin_shell_surface = test::ShellSurfaceBuilder(gfx::Size(100, 100))
  131. .SetNoCommit()
  132. .BuildShellSurface();
  133. origin_shell_surface->SetPopup();
  134. origin_shell_surface->Grab();
  135. int closed_count = 0;
  136. origin_shell_surface->set_close_callback(
  137. base::BindLambdaForTesting([&]() { closed_count++; }));
  138. auto* origin_surface = origin_shell_surface->root_surface();
  139. origin_surface->Commit();
  140. gfx::Size buffer_size(32, 32);
  141. std::unique_ptr<Buffer> buffer(
  142. new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
  143. auto icon_surface = std::make_unique<Surface>();
  144. icon_surface->Attach(buffer.get());
  145. base::RunLoop run_loop;
  146. set_drag_blocked_callback(run_loop.QuitClosure());
  147. ui::test::EventGenerator generator(origin_surface->window()->GetRootWindow(),
  148. origin_surface->window());
  149. generator.PressLeftButton();
  150. gfx::Point location =
  151. generator.current_screen_location() -
  152. origin_surface->window()->GetBoundsInScreen().OffsetFromOrigin();
  153. auto operation = DragDropOperation::Create(
  154. &data_exchange_delegate, data_source.get(),
  155. origin_shell_surface->root_surface(), icon_surface.get(),
  156. gfx::PointF(location), ui::mojom::DragEventSource::kMouse);
  157. icon_surface->Commit();
  158. run_loop.Run();
  159. EXPECT_EQ(1, GetDragStartCountAndReset());
  160. EXPECT_EQ(0, GetDragEndCountAndReset());
  161. EXPECT_EQ(0, closed_count);
  162. generator.MoveMouseBy(150, 150);
  163. EXPECT_EQ(0, GetDragStartCountAndReset());
  164. EXPECT_EQ(0, GetDragEndCountAndReset());
  165. generator.ReleaseLeftButton();
  166. EXPECT_EQ(0, GetDragStartCountAndReset());
  167. EXPECT_EQ(1, GetDragEndCountAndReset());
  168. }
  169. TEST_F(DragDropOperationTest, DragDropFromNestedPopup) {
  170. static_cast<ash::DragDropController*>(
  171. aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()))
  172. ->set_should_block_during_drag_drop(false);
  173. TestDataExchangeDelegate data_exchange_delegate;
  174. auto parent_shell_surface = test::ShellSurfaceBuilder(gfx::Size(100, 100))
  175. .SetNoCommit()
  176. .BuildShellSurface();
  177. parent_shell_surface->SetPopup();
  178. parent_shell_surface->Grab();
  179. parent_shell_surface->root_surface()->Commit();
  180. auto delegate = std::make_unique<TestDataSourceDelegate>();
  181. auto data_source = std::make_unique<DataSource>(delegate.get());
  182. data_source->Offer(kTextMimeType);
  183. auto origin_shell_surface = test::ShellSurfaceBuilder(gfx::Size(100, 100))
  184. .SetNoCommit()
  185. .SetParent(parent_shell_surface.get())
  186. .BuildShellSurface();
  187. origin_shell_surface->SetPopup();
  188. origin_shell_surface->Grab();
  189. auto* origin_surface = origin_shell_surface->root_surface();
  190. origin_surface->Commit();
  191. gfx::Size buffer_size(32, 32);
  192. std::unique_ptr<Buffer> buffer(
  193. new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
  194. auto icon_surface = std::make_unique<Surface>();
  195. icon_surface->Attach(buffer.get());
  196. base::RunLoop run_loop;
  197. set_drag_blocked_callback(run_loop.QuitClosure());
  198. ui::test::EventGenerator generator(origin_surface->window()->GetRootWindow(),
  199. origin_surface->window());
  200. generator.PressLeftButton();
  201. gfx::Point location =
  202. generator.current_screen_location() -
  203. origin_surface->window()->GetBoundsInScreen().OffsetFromOrigin();
  204. auto operation = DragDropOperation::Create(
  205. &data_exchange_delegate, data_source.get(),
  206. origin_shell_surface->root_surface(), icon_surface.get(),
  207. gfx::PointF(location), ui::mojom::DragEventSource::kMouse);
  208. icon_surface->Commit();
  209. run_loop.Run();
  210. EXPECT_EQ(1, GetDragStartCountAndReset());
  211. EXPECT_EQ(0, GetDragEndCountAndReset());
  212. generator.MoveMouseBy(150, 150);
  213. EXPECT_EQ(0, GetDragStartCountAndReset());
  214. EXPECT_EQ(0, GetDragEndCountAndReset());
  215. // Hide the origin_shell_surface -- that will update the capture but shouldn't
  216. // stop the drag&drop session. Note: do not remove |origin_shell_surface|
  217. // itself, as the testing shell surface will also remove the root surface
  218. // which will stop the drag&drop while root surface remains normally.
  219. origin_shell_surface->GetWidget()->Hide();
  220. EXPECT_EQ(0, GetDragStartCountAndReset());
  221. EXPECT_EQ(0, GetDragEndCountAndReset());
  222. generator.ReleaseLeftButton();
  223. EXPECT_EQ(0, GetDragStartCountAndReset());
  224. EXPECT_EQ(1, GetDragEndCountAndReset());
  225. }
  226. namespace {
  227. class MockDataTransferPolicyController
  228. : public ui::DataTransferPolicyController {
  229. public:
  230. MOCK_METHOD3(IsClipboardReadAllowed,
  231. bool(const ui::DataTransferEndpoint* const data_src,
  232. const ui::DataTransferEndpoint* const data_dst,
  233. const absl::optional<size_t> size));
  234. MOCK_METHOD5(PasteIfAllowed,
  235. void(const ui::DataTransferEndpoint* const data_src,
  236. const ui::DataTransferEndpoint* const data_dst,
  237. const absl::optional<size_t> size,
  238. content::RenderFrameHost* rfh,
  239. base::OnceCallback<void(bool)> callback));
  240. MOCK_METHOD3(DropIfAllowed,
  241. void(const ui::DataTransferEndpoint* data_src,
  242. const ui::DataTransferEndpoint* data_dst,
  243. base::OnceClosure drop_cb));
  244. };
  245. } // namespace
  246. // Lacros sends additional metadata about the drag and drop source (e.g. origin
  247. // URL). This synchronizes the source metadata between Lacros to Ash. This is
  248. // used in Data Leak Prevention restrictions where admins can restrict data from
  249. // being copied from restricted locations.
  250. TEST_F(DragDropOperationTest, DragDropCheckSourceFromLacros) {
  251. static_cast<ash::DragDropController*>(
  252. aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()))
  253. ->set_should_block_during_drag_drop(false);
  254. TestDataExchangeDelegate data_exchange_delegate;
  255. data_exchange_delegate.set_endpoint_type(ui::EndpointType::kLacros);
  256. auto delegate = std::make_unique<TestDataSourceDelegate>();
  257. auto data_source = std::make_unique<DataSource>(delegate.get());
  258. auto dlp_controller = std::make_unique<MockDataTransferPolicyController>();
  259. // Encoded source DataTransferEndpoint.
  260. const std::string kEncodedTestDte =
  261. R"({"endpoint_type":"url","url":"https://www.google.com"})";
  262. const std::string kDteMimeType = "chromium/x-data-transfer-endpoint";
  263. data_source->Offer(kDteMimeType);
  264. delegate->SetData(kDteMimeType, std::vector<uint8_t>(kEncodedTestDte.begin(),
  265. kEncodedTestDte.end()));
  266. auto origin_surface = std::make_unique<Surface>();
  267. ash::Shell::GetPrimaryRootWindow()->AddChild(origin_surface->window());
  268. gfx::Size buffer_size(100, 100);
  269. std::unique_ptr<Buffer> buffer(
  270. new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
  271. auto icon_surface = std::make_unique<Surface>();
  272. icon_surface->Attach(buffer.get());
  273. // Expect the encoded endpoint from Lacros to be correctly parsed.
  274. EXPECT_CALL(*dlp_controller,
  275. DropIfAllowed(
  276. Pointee(AllOf(
  277. Property(&ui::DataTransferEndpoint::IsUrlType, true),
  278. Property(&ui::DataTransferEndpoint::GetURL,
  279. Pointee(Property(&GURL::spec,
  280. "https://www.google.com/"))))),
  281. _, _))
  282. .WillOnce([&](const ui::DataTransferEndpoint* data_src,
  283. const ui::DataTransferEndpoint* data_dst,
  284. base::OnceClosure drop_cb) { std::move(drop_cb).Run(); });
  285. base::RunLoop run_loop;
  286. set_drag_blocked_callback(run_loop.QuitClosure());
  287. ui::test::EventGenerator generator(origin_surface->window()->GetRootWindow(),
  288. origin_surface->window());
  289. generator.PressLeftButton();
  290. gfx::Point location =
  291. generator.current_screen_location() -
  292. origin_surface->window()->GetBoundsInScreen().OffsetFromOrigin();
  293. auto operation = DragDropOperation::Create(
  294. &data_exchange_delegate, data_source.get(), origin_surface.get(),
  295. icon_surface.get(), gfx::PointF(location),
  296. ui::mojom::DragEventSource::kMouse);
  297. icon_surface->Commit();
  298. run_loop.Run();
  299. generator.MoveMouseBy(150, 150);
  300. generator.ReleaseLeftButton();
  301. ::testing::Mock::VerifyAndClearExpectations(dlp_controller.get());
  302. }
  303. // Additional source metadata should be ignored from non-Lacros instances.
  304. TEST_F(DragDropOperationTest, DragDropCheckSourceFromNonLacros) {
  305. static_cast<ash::DragDropController*>(
  306. aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()))
  307. ->set_should_block_during_drag_drop(false);
  308. TestDataExchangeDelegate data_exchange_delegate;
  309. data_exchange_delegate.set_endpoint_type(ui::EndpointType::kCrostini);
  310. auto delegate = std::make_unique<TestDataSourceDelegate>();
  311. auto data_source = std::make_unique<DataSource>(delegate.get());
  312. auto dlp_controller = std::make_unique<MockDataTransferPolicyController>();
  313. // Encoded source DataTransferEndpoint.
  314. const std::string kEncodedTestDte =
  315. R"({"endpoint_type":"url","url":"https://www.google.com"})";
  316. const std::string kDteMimeType = "chromium/x-data-transfer-endpoint";
  317. data_source->Offer(kDteMimeType);
  318. delegate->SetData(kDteMimeType, std::vector<uint8_t>(kEncodedTestDte.begin(),
  319. kEncodedTestDte.end()));
  320. auto origin_surface = std::make_unique<Surface>();
  321. ash::Shell::GetPrimaryRootWindow()->AddChild(origin_surface->window());
  322. gfx::Size buffer_size(100, 100);
  323. std::unique_ptr<Buffer> buffer(
  324. new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
  325. auto icon_surface = std::make_unique<Surface>();
  326. icon_surface->Attach(buffer.get());
  327. // Expect the encoded endpoint from non-Lacros to be ignored.
  328. EXPECT_CALL(
  329. *dlp_controller,
  330. DropIfAllowed(
  331. Pointee(AllOf(Property(&ui::DataTransferEndpoint::IsUrlType, false),
  332. Property(&ui::DataTransferEndpoint::type,
  333. ui::EndpointType::kCrostini))),
  334. _, _))
  335. .WillOnce([&](const ui::DataTransferEndpoint* data_src,
  336. const ui::DataTransferEndpoint* data_dst,
  337. base::OnceClosure drop_cb) { std::move(drop_cb).Run(); });
  338. base::RunLoop run_loop;
  339. set_drag_blocked_callback(run_loop.QuitClosure());
  340. ui::test::EventGenerator generator(origin_surface->window()->GetRootWindow(),
  341. origin_surface->window());
  342. generator.PressLeftButton();
  343. gfx::Point location =
  344. generator.current_screen_location() -
  345. origin_surface->window()->GetBoundsInScreen().OffsetFromOrigin();
  346. auto operation = DragDropOperation::Create(
  347. &data_exchange_delegate, data_source.get(), origin_surface.get(),
  348. icon_surface.get(), gfx::PointF(location),
  349. ui::mojom::DragEventSource::kMouse);
  350. icon_surface->Commit();
  351. run_loop.Run();
  352. generator.MoveMouseBy(150, 150);
  353. generator.ReleaseLeftButton();
  354. ::testing::Mock::VerifyAndClearExpectations(dlp_controller.get());
  355. }
  356. #endif
  357. } // namespace exo