drag_drop_client_mac_unittest.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // Copyright 2016 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 "base/memory/raw_ptr.h"
  5. #import "ui/views/cocoa/drag_drop_client_mac.h"
  6. #import <Cocoa/Cocoa.h>
  7. #include "base/bind.h"
  8. #import "base/mac/scoped_objc_class_swizzler.h"
  9. #include "base/strings/utf_string_conversions.h"
  10. #include "base/threading/thread_task_runner_handle.h"
  11. #import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
  12. #import "ui/base/clipboard/clipboard_util_mac.h"
  13. #import "ui/base/dragdrop/drag_drop_types.h"
  14. #import "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
  15. #include "ui/gfx/image/image_unittest_util.h"
  16. #import "ui/views/cocoa/native_widget_mac_ns_window_host.h"
  17. #include "ui/views/test/widget_test.h"
  18. #include "ui/views/view.h"
  19. #include "ui/views/widget/native_widget_mac.h"
  20. #include "ui/views/widget/widget.h"
  21. @interface NSView (DragSessionTestingDonor)
  22. @end
  23. @implementation NSView (DragSessionTestingDonor)
  24. - (NSDraggingSession*)cr_beginDraggingSessionWithItems:(NSArray*)items
  25. event:(NSEvent*)event
  26. source:(id<NSDraggingSource>)
  27. source {
  28. return nil;
  29. }
  30. @end
  31. // Mocks the NSDraggingInfo sent to the DragDropClientMac's DragUpdate() and
  32. // Drop() methods. Out of the required methods of the protocol, only
  33. // draggingLocation and draggingPasteboard are used.
  34. @interface MockDraggingInfo : NSObject<NSDraggingInfo> {
  35. NSPasteboard* _pasteboard;
  36. }
  37. @property BOOL animatesToDestination;
  38. @property NSInteger numberOfValidItemsForDrop;
  39. @property NSDraggingFormation draggingFormation;
  40. @property(readonly) NSSpringLoadingHighlight springLoadingHighlight;
  41. @end
  42. @implementation MockDraggingInfo
  43. @synthesize animatesToDestination;
  44. @synthesize numberOfValidItemsForDrop;
  45. @synthesize draggingFormation;
  46. @synthesize springLoadingHighlight;
  47. - (instancetype)initWithPasteboard:(NSPasteboard*)pasteboard {
  48. if ((self = [super init])) {
  49. _pasteboard = pasteboard;
  50. }
  51. return self;
  52. }
  53. - (NSPoint)draggingLocation {
  54. return NSMakePoint(50, 50);
  55. }
  56. - (NSPasteboard*)draggingPasteboard {
  57. return _pasteboard;
  58. }
  59. - (NSInteger)draggingSequenceNumber {
  60. return 0;
  61. }
  62. - (id)draggingSource {
  63. return nil;
  64. }
  65. - (NSDragOperation)draggingSourceOperationMask {
  66. return NSDragOperationEvery;
  67. }
  68. - (NSWindow*)draggingDestinationWindow {
  69. return nil;
  70. }
  71. - (NSArray*)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination {
  72. return nil;
  73. }
  74. - (NSImage*)draggedImage {
  75. return nil;
  76. }
  77. - (NSPoint)draggedImageLocation {
  78. return NSZeroPoint;
  79. }
  80. - (void)slideDraggedImageTo:(NSPoint)aPoint {
  81. }
  82. - (void)
  83. enumerateDraggingItemsWithOptions:(NSDraggingItemEnumerationOptions)enumOpts
  84. forView:(NSView*)view
  85. classes:(NSArray*)classArray
  86. searchOptions:(NSDictionary*)searchOptions
  87. usingBlock:(void (^)(NSDraggingItem* draggingItem,
  88. NSInteger idx,
  89. BOOL* stop))block {
  90. }
  91. - (void)resetSpringLoading {
  92. }
  93. @end
  94. namespace views {
  95. namespace test {
  96. using ::base::ASCIIToUTF16;
  97. using ::ui::mojom::DragOperation;
  98. // View object that will receive and process dropped data from the test.
  99. class DragDropView : public View {
  100. public:
  101. DragDropView() = default;
  102. DragDropView(const DragDropView&) = delete;
  103. DragDropView& operator=(const DragDropView&) = delete;
  104. void set_formats(int formats) { formats_ = formats; }
  105. // View:
  106. bool GetDropFormats(
  107. int* formats,
  108. std::set<ui::ClipboardFormatType>* format_types) override {
  109. *formats |= formats_;
  110. return true;
  111. }
  112. bool CanDrop(const OSExchangeData& data) override { return true; }
  113. int OnDragUpdated(const ui::DropTargetEvent& event) override {
  114. return ui::DragDropTypes::DRAG_COPY;
  115. }
  116. views::View::DropCallback GetDropCallback(
  117. const ui::DropTargetEvent& event) override {
  118. return base::BindOnce([](const ui::DropTargetEvent& event,
  119. ui::mojom::DragOperation& output_drag_op) {
  120. output_drag_op = DragOperation::kMove;
  121. });
  122. }
  123. private:
  124. // Drop formats accepted by this View object.
  125. int formats_ = 0;
  126. };
  127. class DragDropClientMacTest : public WidgetTest {
  128. public:
  129. DragDropClientMacTest() : widget_(new Widget) {}
  130. DragDropClientMacTest(const DragDropClientMacTest&) = delete;
  131. DragDropClientMacTest& operator=(const DragDropClientMacTest&) = delete;
  132. DragDropClientMac* drag_drop_client() {
  133. return ns_window_host_->drag_drop_client();
  134. }
  135. NSDragOperation DragUpdate(NSPasteboard* pasteboard) {
  136. DragDropClientMac* client = drag_drop_client();
  137. dragging_info_.reset(
  138. [[MockDraggingInfo alloc] initWithPasteboard:pasteboard]);
  139. return client->DragUpdate(dragging_info_.get());
  140. }
  141. NSDragOperation Drop() {
  142. DragDropClientMac* client = drag_drop_client();
  143. DCHECK(dragging_info_.get());
  144. NSDragOperation operation = client->Drop(dragging_info_.get());
  145. dragging_info_.reset();
  146. return operation;
  147. }
  148. void SetData(OSExchangeData& data) {
  149. drag_drop_client()->exchange_data_ =
  150. std::make_unique<ui::OSExchangeData>(data.provider().Clone());
  151. }
  152. // testing::Test:
  153. void SetUp() override {
  154. WidgetTest::SetUp();
  155. widget_ = CreateTopLevelPlatformWidget();
  156. gfx::Rect bounds(0, 0, 100, 100);
  157. widget_->SetBounds(bounds);
  158. ns_window_host_ = NativeWidgetMacNSWindowHost::GetFromNativeWindow(
  159. widget_->GetNativeWindow());
  160. bridge_ = ns_window_host_->GetInProcessNSWindowBridge();
  161. widget_->Show();
  162. target_ = new DragDropView();
  163. widget_->non_client_view()->frame_view()->AddChildView(target_.get());
  164. target_->SetBoundsRect(bounds);
  165. drag_drop_client()->source_operation_ = ui::DragDropTypes::DRAG_COPY;
  166. }
  167. void TearDown() override {
  168. if (widget_)
  169. widget_->CloseNow();
  170. WidgetTest::TearDown();
  171. }
  172. protected:
  173. raw_ptr<Widget> widget_ = nullptr;
  174. raw_ptr<remote_cocoa::NativeWidgetNSWindowBridge> bridge_ = nullptr;
  175. raw_ptr<NativeWidgetMacNSWindowHost> ns_window_host_ = nullptr;
  176. raw_ptr<DragDropView> target_ = nullptr;
  177. base::scoped_nsobject<MockDraggingInfo> dragging_info_;
  178. };
  179. // Tests if the drag and drop target receives the dropped data.
  180. TEST_F(DragDropClientMacTest, BasicDragDrop) {
  181. // Create the drop data
  182. OSExchangeData data;
  183. const std::u16string& text = u"text";
  184. data.SetString(text);
  185. SetData(data);
  186. target_->set_formats(ui::OSExchangeData::STRING | ui::OSExchangeData::URL);
  187. // Check if the target receives the data from a drop and returns the expected
  188. // operation.
  189. EXPECT_EQ(DragUpdate(nil), NSDragOperationCopy);
  190. EXPECT_EQ(Drop(), NSDragOperationMove);
  191. }
  192. // Ensure that capture is released before the end of a drag and drop operation.
  193. TEST_F(DragDropClientMacTest, ReleaseCapture) {
  194. // DragDropView doesn't actually capture the mouse, so explicitly acquire it
  195. // to test that StartDragAndDrop() actually releases it.
  196. // Although this is not an interactive UI test, acquiring capture should be OK
  197. // since the runloop will exit before the system has any opportunity to
  198. // capture anything.
  199. bridge_->AcquireCapture();
  200. EXPECT_TRUE(ns_window_host_->IsMouseCaptureActive());
  201. // Create the drop data
  202. std::unique_ptr<OSExchangeData> data(std::make_unique<OSExchangeData>());
  203. const std::u16string& text = u"text";
  204. data->SetString(text);
  205. data->provider().SetDragImage(gfx::test::CreateImageSkia(100, 100),
  206. gfx::Vector2d());
  207. SetData(*data.get());
  208. // There's no way to cleanly stop NSDraggingSession inside unit tests, so just
  209. // don't start it at all.
  210. base::mac::ScopedObjCClassSwizzler swizzle(
  211. [NSView class], @selector(beginDraggingSessionWithItems:event:source:),
  212. @selector(cr_beginDraggingSessionWithItems:event:source:));
  213. // Immediately quit drag'n'drop, or we'll hang.
  214. base::ThreadTaskRunnerHandle::Get()->PostTask(
  215. FROM_HERE, base::BindOnce(&DragDropClientMac::EndDrag,
  216. base::Unretained(drag_drop_client())));
  217. // It will call ReleaseCapture().
  218. drag_drop_client()->StartDragAndDrop(target_, std::move(data), 0,
  219. ui::mojom::DragEventSource::kMouse);
  220. // The capture should be released.
  221. EXPECT_FALSE(ns_window_host_->IsMouseCaptureActive());
  222. }
  223. // Tests if the drag and drop target rejects the dropped data with the
  224. // incorrect format.
  225. TEST_F(DragDropClientMacTest, InvalidFormatDragDrop) {
  226. OSExchangeData data;
  227. const std::u16string& text = u"text";
  228. data.SetString(text);
  229. SetData(data);
  230. target_->set_formats(ui::OSExchangeData::URL);
  231. // Check if the target receives the data from a drop and returns the expected
  232. // operation.
  233. EXPECT_EQ(DragUpdate(nil), NSDragOperationNone);
  234. EXPECT_EQ(Drop(), NSDragOperationNone);
  235. }
  236. // Tests if the drag and drop target can accept data without an OSExchangeData
  237. // object.
  238. TEST_F(DragDropClientMacTest, PasteboardToOSExchangeTest) {
  239. target_->set_formats(ui::OSExchangeData::STRING);
  240. scoped_refptr<ui::UniquePasteboard> pasteboard = new ui::UniquePasteboard;
  241. // The test should reject the data if the pasteboard is empty.
  242. EXPECT_EQ(DragUpdate(pasteboard->get()), NSDragOperationNone);
  243. EXPECT_EQ(Drop(), NSDragOperationNone);
  244. drag_drop_client()->EndDrag();
  245. // Add valid data to the pasteboard and check to see if the target accepts
  246. // it.
  247. [pasteboard->get() setString:@"text" forType:NSPasteboardTypeString];
  248. EXPECT_EQ(DragUpdate(pasteboard->get()), NSDragOperationCopy);
  249. EXPECT_EQ(Drop(), NSDragOperationMove);
  250. }
  251. // View object that will close Widget on drop.
  252. class DragDropCloseView : public DragDropView {
  253. public:
  254. DragDropCloseView() {}
  255. DragDropCloseView(const DragDropCloseView&) = delete;
  256. DragDropCloseView& operator=(const DragDropCloseView&) = delete;
  257. views::View::DropCallback GetDropCallback(
  258. const ui::DropTargetEvent& event) override {
  259. // base::Unretained is safe here because in the tests the view isn't deleted
  260. // before the drop callback is run.
  261. return base::BindOnce(&DragDropCloseView::PerformDrop,
  262. base::Unretained(this));
  263. }
  264. private:
  265. void PerformDrop(const ui::DropTargetEvent& event,
  266. ui::mojom::DragOperation& output_drag_op) {
  267. GetWidget()->CloseNow();
  268. output_drag_op = DragOperation::kMove;
  269. }
  270. };
  271. // Tests that closing Widget on drop does not crash.
  272. TEST_F(DragDropClientMacTest, CloseWidgetOnDrop) {
  273. OSExchangeData data;
  274. const std::u16string& text = u"text";
  275. data.SetString(text);
  276. SetData(data);
  277. target_ = new DragDropCloseView();
  278. widget_->non_client_view()->frame_view()->AddChildView(target_.get());
  279. target_->SetBoundsRect(gfx::Rect(0, 0, 100, 100));
  280. target_->set_formats(ui::OSExchangeData::STRING | ui::OSExchangeData::URL);
  281. EXPECT_EQ(DragUpdate(nil), NSDragOperationCopy);
  282. EXPECT_EQ(Drop(), NSDragOperationMove);
  283. // Drop callback will have deleted the widget.
  284. widget_ = nullptr;
  285. }
  286. } // namespace test
  287. } // namespace views