Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / web_contents / web_drag_dest_mac.mm
blobbc41d43a49a36578be349304795d999316f74ecd
1 // Copyright (c) 2012 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.
5 #import "content/browser/web_contents/web_drag_dest_mac.h"
7 #import <Carbon/Carbon.h>
9 #include "base/strings/sys_string_conversions.h"
10 #include "content/browser/renderer_host/render_view_host_impl.h"
11 #include "content/browser/web_contents/web_contents_impl.h"
12 #include "content/public/browser/web_contents_delegate.h"
13 #include "content/public/browser/web_drag_dest_delegate.h"
14 #include "content/public/common/drop_data.h"
15 #import "third_party/mozilla/NSPasteboard+Utils.h"
16 #include "third_party/WebKit/public/web/WebInputEvent.h"
17 #include "ui/base/clipboard/custom_data_helper.h"
18 #import "ui/base/dragdrop/cocoa_dnd_util.h"
19 #include "ui/base/window_open_disposition.h"
21 using blink::WebDragOperationsMask;
22 using content::DropData;
23 using content::OpenURLParams;
24 using content::Referrer;
25 using content::WebContentsImpl;
27 namespace {
29 int GetModifierFlags() {
30   int modifier_state = 0;
31   UInt32 currentModifiers = GetCurrentKeyModifiers();
32   if (currentModifiers & ::shiftKey)
33     modifier_state |= blink::WebInputEvent::ShiftKey;
34   if (currentModifiers & ::controlKey)
35     modifier_state |= blink::WebInputEvent::ControlKey;
36   if (currentModifiers & ::optionKey)
37     modifier_state |= blink::WebInputEvent::AltKey;
38   if (currentModifiers & ::cmdKey)
39       modifier_state |= blink::WebInputEvent::MetaKey;
41   // The return value of 1 << 0 corresponds to the left mouse button,
42   // 1 << 1 corresponds to the right mouse button,
43   // 1 << n, n >= 2 correspond to other mouse buttons.
44   NSUInteger pressedButtons = [NSEvent pressedMouseButtons];
46   if (pressedButtons & (1 << 0))
47       modifier_state |= blink::WebInputEvent::LeftButtonDown;
48   if (pressedButtons & (1 << 1))
49       modifier_state |= blink::WebInputEvent::RightButtonDown;
50   if (pressedButtons & (1 << 2))
51       modifier_state |= blink::WebInputEvent::MiddleButtonDown;
53   return modifier_state;
56 }  // namespace
58 @implementation WebDragDest
60 // |contents| is the WebContentsImpl representing this tab, used to communicate
61 // drag&drop messages to WebCore and handle navigation on a successful drop
62 // (if necessary).
63 - (id)initWithWebContentsImpl:(WebContentsImpl*)contents {
64   if ((self = [super init])) {
65     webContents_ = contents;
66     canceled_ = false;
67   }
68   return self;
71 - (DropData*)currentDropData {
72   return dropData_.get();
75 - (void)setDragDelegate:(content::WebDragDestDelegate*)delegate {
76   delegate_ = delegate;
79 // Call to set whether or not we should allow the drop. Takes effect the
80 // next time |-draggingUpdated:| is called.
81 - (void)setCurrentOperation:(NSDragOperation)operation {
82   currentOperation_ = operation;
85 // Given a point in window coordinates and a view in that window, return a
86 // flipped point in the coordinate system of |view|.
87 - (NSPoint)flipWindowPointToView:(const NSPoint&)windowPoint
88                             view:(NSView*)view {
89   DCHECK(view);
90   NSPoint viewPoint =  [view convertPoint:windowPoint fromView:nil];
91   NSRect viewFrame = [view frame];
92   viewPoint.y = viewFrame.size.height - viewPoint.y;
93   return viewPoint;
96 // Given a point in window coordinates and a view in that window, return a
97 // flipped point in screen coordinates.
98 - (NSPoint)flipWindowPointToScreen:(const NSPoint&)windowPoint
99                               view:(NSView*)view {
100   DCHECK(view);
101   NSPoint screenPoint = [[view window] convertBaseToScreen:windowPoint];
102   NSScreen* screen = [[view window] screen];
103   NSRect screenFrame = [screen frame];
104   screenPoint.y = screenFrame.size.height - screenPoint.y;
105   return screenPoint;
108 // Return YES if the drop site only allows drops that would navigate.  If this
109 // is the case, we don't want to pass messages to the renderer because there's
110 // really no point (i.e., there's nothing that cares about the mouse position or
111 // entering and exiting).  One example is an interstitial page (e.g., safe
112 // browsing warning).
113 - (BOOL)onlyAllowsNavigation {
114   return webContents_->ShowingInterstitialPage();
117 // Messages to send during the tracking of a drag, usually upon receiving
118 // calls from the view system. Communicates the drag messages to WebCore.
120 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info
121                               view:(NSView*)view {
122   // Save off the RVH so we can tell if it changes during a drag. If it does,
123   // we need to send a new enter message in draggingUpdated:.
124   currentRVH_ = webContents_->GetRenderViewHost();
126   // Fill out a DropData from pasteboard.
127   scoped_ptr<DropData> dropData;
128   dropData.reset(new DropData());
129   [self populateDropData:dropData.get()
130              fromPasteboard:[info draggingPasteboard]];
132   NSDragOperation mask = [info draggingSourceOperationMask];
134   // Give the delegate an opportunity to cancel the drag.
135   canceled_ = !webContents_->GetDelegate()->CanDragEnter(
136       webContents_,
137       *dropData,
138       static_cast<WebDragOperationsMask>(mask));
139   if (canceled_)
140     return NSDragOperationNone;
142   if ([self onlyAllowsNavigation]) {
143     if ([[info draggingPasteboard] containsURLData])
144       return NSDragOperationCopy;
145     return NSDragOperationNone;
146   }
148   if (delegate_) {
149     delegate_->DragInitialize(webContents_);
150     delegate_->OnDragEnter();
151   }
153   dropData_.swap(dropData);
155   // Create the appropriate mouse locations for WebCore. The draggingLocation
156   // is in window coordinates. Both need to be flipped.
157   NSPoint windowPoint = [info draggingLocation];
158   NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
159   NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
160   webContents_->GetRenderViewHost()->DragTargetDragEnter(
161       *dropData_,
162       gfx::Point(viewPoint.x, viewPoint.y),
163       gfx::Point(screenPoint.x, screenPoint.y),
164       static_cast<WebDragOperationsMask>(mask),
165       GetModifierFlags());
167   // We won't know the true operation (whether the drag is allowed) until we
168   // hear back from the renderer. For now, be optimistic:
169   currentOperation_ = NSDragOperationCopy;
170   return currentOperation_;
173 - (void)draggingExited:(id<NSDraggingInfo>)info {
174   DCHECK(currentRVH_);
175   if (currentRVH_ != webContents_->GetRenderViewHost())
176     return;
178   if (canceled_)
179     return;
181   if ([self onlyAllowsNavigation])
182     return;
184   if (delegate_)
185     delegate_->OnDragLeave();
187   webContents_->GetRenderViewHost()->DragTargetDragLeave();
188   dropData_.reset();
191 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info
192                               view:(NSView*)view {
193   DCHECK(currentRVH_);
194   if (currentRVH_ != webContents_->GetRenderViewHost())
195     [self draggingEntered:info view:view];
197   if (canceled_)
198     return NSDragOperationNone;
200   if ([self onlyAllowsNavigation]) {
201     if ([[info draggingPasteboard] containsURLData])
202       return NSDragOperationCopy;
203     return NSDragOperationNone;
204   }
206   // Create the appropriate mouse locations for WebCore. The draggingLocation
207   // is in window coordinates.
208   NSPoint windowPoint = [info draggingLocation];
209   NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
210   NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
211   NSDragOperation mask = [info draggingSourceOperationMask];
212   webContents_->GetRenderViewHost()->DragTargetDragOver(
213       gfx::Point(viewPoint.x, viewPoint.y),
214       gfx::Point(screenPoint.x, screenPoint.y),
215       static_cast<WebDragOperationsMask>(mask),
216       GetModifierFlags());
218   if (delegate_)
219     delegate_->OnDragOver();
221   return currentOperation_;
224 - (BOOL)performDragOperation:(id<NSDraggingInfo>)info
225                               view:(NSView*)view {
226   if (currentRVH_ != webContents_->GetRenderViewHost())
227     [self draggingEntered:info view:view];
229   // Check if we only allow navigation and navigate to a url on the pasteboard.
230   if ([self onlyAllowsNavigation]) {
231     NSPasteboard* pboard = [info draggingPasteboard];
232     if ([pboard containsURLData]) {
233       GURL url;
234       ui::PopulateURLAndTitleFromPasteboard(&url, NULL, pboard, YES);
235       webContents_->OpenURL(OpenURLParams(
236           url, Referrer(), CURRENT_TAB, ui::PAGE_TRANSITION_AUTO_BOOKMARK,
237           false));
238       return YES;
239     } else {
240       return NO;
241     }
242   }
244   if (delegate_)
245     delegate_->OnDrop();
247   currentRVH_ = NULL;
249   // Create the appropriate mouse locations for WebCore. The draggingLocation
250   // is in window coordinates. Both need to be flipped.
251   NSPoint windowPoint = [info draggingLocation];
252   NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
253   NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
254   webContents_->GetRenderViewHost()->DragTargetDrop(
255       gfx::Point(viewPoint.x, viewPoint.y),
256       gfx::Point(screenPoint.x, screenPoint.y),
257       GetModifierFlags());
259   dropData_.reset();
261   return YES;
264 // Given |data|, which should not be nil, fill it in using the contents of the
265 // given pasteboard. The types handled by this method should be kept in sync
266 // with [WebContentsViewCocoa registerDragTypes].
267 - (void)populateDropData:(DropData*)data
268           fromPasteboard:(NSPasteboard*)pboard {
269   DCHECK(data);
270   DCHECK(pboard);
271   NSArray* types = [pboard types];
273   data->did_originate_from_renderer =
274       [types containsObject:ui::kChromeDragDummyPboardType];
276   // Get URL if possible. To avoid exposing file system paths to web content,
277   // filenames in the drag are not converted to file URLs.
278   ui::PopulateURLAndTitleFromPasteboard(&data->url,
279                                         &data->url_title,
280                                         pboard,
281                                         NO);
283   // Get plain text.
284   if ([types containsObject:NSStringPboardType]) {
285     data->text = base::NullableString16(
286         base::SysNSStringToUTF16([pboard stringForType:NSStringPboardType]),
287         false);
288   }
290   // Get HTML. If there's no HTML, try RTF.
291   if ([types containsObject:NSHTMLPboardType]) {
292     NSString* html = [pboard stringForType:NSHTMLPboardType];
293     data->html = base::NullableString16(base::SysNSStringToUTF16(html), false);
294   } else if ([types containsObject:ui::kChromeDragImageHTMLPboardType]) {
295     NSString* html = [pboard stringForType:ui::kChromeDragImageHTMLPboardType];
296     data->html = base::NullableString16(base::SysNSStringToUTF16(html), false);
297   } else if ([types containsObject:NSRTFPboardType]) {
298     NSString* html = [pboard htmlFromRtf];
299     data->html = base::NullableString16(base::SysNSStringToUTF16(html), false);
300   }
302   // Get files.
303   if ([types containsObject:NSFilenamesPboardType]) {
304     NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
305     if ([files isKindOfClass:[NSArray class]] && [files count]) {
306       for (NSUInteger i = 0; i < [files count]; i++) {
307         NSString* filename = [files objectAtIndex:i];
308         BOOL exists = [[NSFileManager defaultManager]
309                            fileExistsAtPath:filename];
310         if (exists) {
311           data->filenames.push_back(ui::FileInfo(
312               base::FilePath::FromUTF8Unsafe(base::SysNSStringToUTF8(filename)),
313               base::FilePath()));
314         }
315       }
316     }
317   }
319   // TODO(pinkerton): Get file contents. http://crbug.com/34661
321   // Get custom MIME data.
322   if ([types containsObject:ui::kWebCustomDataPboardType]) {
323     NSData* customData = [pboard dataForType:ui::kWebCustomDataPboardType];
324     ui::ReadCustomDataIntoMap([customData bytes],
325                               [customData length],
326                               &data->custom_data);
327   }
330 @end