web_drag_dest_mac.mm revision 5821806d5e7f356e8fa4b058a389a808ea183019
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.
4
5#import "content/browser/web_contents/web_drag_dest_mac.h"
6
7#import <Carbon/Carbon.h>
8
9#include "base/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_drag_dest_delegate.h"
13#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
14#import "third_party/mozilla/NSPasteboard+Utils.h"
15#include "ui/base/clipboard/custom_data_helper.h"
16#import "ui/base/dragdrop/cocoa_dnd_util.h"
17#include "webkit/glue/webdropdata.h"
18#include "webkit/glue/window_open_disposition.h"
19
20using WebKit::WebDragOperationsMask;
21using content::OpenURLParams;
22using content::Referrer;
23using content::WebContentsImpl;
24
25int GetModifierFlags() {
26  int modifier_state = 0;
27  UInt32 currentModifiers = GetCurrentKeyModifiers();
28  if (currentModifiers & ::shiftKey)
29    modifier_state |= WebKit::WebInputEvent::ShiftKey;
30  if (currentModifiers & ::controlKey)
31    modifier_state |= WebKit::WebInputEvent::ControlKey;
32  if (currentModifiers & ::optionKey)
33    modifier_state |= WebKit::WebInputEvent::AltKey;
34  if (currentModifiers & ::cmdKey)
35      modifier_state |= WebKit::WebInputEvent::MetaKey;
36  return modifier_state;
37}
38
39@implementation WebDragDest
40
41// |contents| is the WebContentsImpl representing this tab, used to communicate
42// drag&drop messages to WebCore and handle navigation on a successful drop
43// (if necessary).
44- (id)initWithWebContentsImpl:(WebContentsImpl*)contents {
45  if ((self = [super init])) {
46    webContents_ = contents;
47  }
48  return self;
49}
50
51- (WebDropData*)currentDropData {
52  return dropData_.get();
53}
54
55- (void)setDragDelegate:(content::WebDragDestDelegate*)delegate {
56  delegate_ = delegate;
57}
58
59// Call to set whether or not we should allow the drop. Takes effect the
60// next time |-draggingUpdated:| is called.
61- (void)setCurrentOperation:(NSDragOperation)operation {
62  currentOperation_ = operation;
63}
64
65// Given a point in window coordinates and a view in that window, return a
66// flipped point in the coordinate system of |view|.
67- (NSPoint)flipWindowPointToView:(const NSPoint&)windowPoint
68                            view:(NSView*)view {
69  DCHECK(view);
70  NSPoint viewPoint =  [view convertPoint:windowPoint fromView:nil];
71  NSRect viewFrame = [view frame];
72  viewPoint.y = viewFrame.size.height - viewPoint.y;
73  return viewPoint;
74}
75
76// Given a point in window coordinates and a view in that window, return a
77// flipped point in screen coordinates.
78- (NSPoint)flipWindowPointToScreen:(const NSPoint&)windowPoint
79                              view:(NSView*)view {
80  DCHECK(view);
81  NSPoint screenPoint = [[view window] convertBaseToScreen:windowPoint];
82  NSScreen* screen = [[view window] screen];
83  NSRect screenFrame = [screen frame];
84  screenPoint.y = screenFrame.size.height - screenPoint.y;
85  return screenPoint;
86}
87
88// Return YES if the drop site only allows drops that would navigate.  If this
89// is the case, we don't want to pass messages to the renderer because there's
90// really no point (i.e., there's nothing that cares about the mouse position or
91// entering and exiting).  One example is an interstitial page (e.g., safe
92// browsing warning).
93- (BOOL)onlyAllowsNavigation {
94  return webContents_->ShowingInterstitialPage();
95}
96
97// Messages to send during the tracking of a drag, usually upon receiving
98// calls from the view system. Communicates the drag messages to WebCore.
99
100- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info
101                              view:(NSView*)view {
102  // Save off the RVH so we can tell if it changes during a drag. If it does,
103  // we need to send a new enter message in draggingUpdated:.
104  currentRVH_ = webContents_->GetRenderViewHost();
105
106  if ([self onlyAllowsNavigation]) {
107    if ([[info draggingPasteboard] containsURLData])
108      return NSDragOperationCopy;
109    return NSDragOperationNone;
110  }
111
112  if (delegate_) {
113    delegate_->DragInitialize(webContents_);
114    delegate_->OnDragEnter();
115  }
116
117  // Fill out a WebDropData from pasteboard.
118  dropData_.reset(new WebDropData());
119  [self populateWebDropData:dropData_.get()
120             fromPasteboard:[info draggingPasteboard]];
121
122  // Create the appropriate mouse locations for WebCore. The draggingLocation
123  // is in window coordinates. Both need to be flipped.
124  NSPoint windowPoint = [info draggingLocation];
125  NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
126  NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
127  NSDragOperation mask = [info draggingSourceOperationMask];
128  webContents_->GetRenderViewHost()->DragTargetDragEnter(
129      *dropData_,
130      gfx::Point(viewPoint.x, viewPoint.y),
131      gfx::Point(screenPoint.x, screenPoint.y),
132      static_cast<WebDragOperationsMask>(mask),
133      GetModifierFlags());
134
135  // We won't know the true operation (whether the drag is allowed) until we
136  // hear back from the renderer. For now, be optimistic:
137  currentOperation_ = NSDragOperationCopy;
138  return currentOperation_;
139}
140
141- (void)draggingExited:(id<NSDraggingInfo>)info {
142  DCHECK(currentRVH_);
143  if (currentRVH_ != webContents_->GetRenderViewHost())
144    return;
145
146  if ([self onlyAllowsNavigation])
147    return;
148
149  if (delegate_)
150    delegate_->OnDragLeave();
151
152  webContents_->GetRenderViewHost()->DragTargetDragLeave();
153  dropData_.reset();
154}
155
156- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info
157                              view:(NSView*)view {
158  DCHECK(currentRVH_);
159  if (currentRVH_ != webContents_->GetRenderViewHost())
160    [self draggingEntered:info view:view];
161
162  if ([self onlyAllowsNavigation]) {
163    if ([[info draggingPasteboard] containsURLData])
164      return NSDragOperationCopy;
165    return NSDragOperationNone;
166  }
167
168  // Create the appropriate mouse locations for WebCore. The draggingLocation
169  // is in window coordinates.
170  NSPoint windowPoint = [info draggingLocation];
171  NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
172  NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
173  NSDragOperation mask = [info draggingSourceOperationMask];
174  webContents_->GetRenderViewHost()->DragTargetDragOver(
175      gfx::Point(viewPoint.x, viewPoint.y),
176      gfx::Point(screenPoint.x, screenPoint.y),
177      static_cast<WebDragOperationsMask>(mask),
178      GetModifierFlags());
179
180  if (delegate_)
181    delegate_->OnDragOver();
182
183  return currentOperation_;
184}
185
186- (BOOL)performDragOperation:(id<NSDraggingInfo>)info
187                              view:(NSView*)view {
188  if (currentRVH_ != webContents_->GetRenderViewHost())
189    [self draggingEntered:info view:view];
190
191  // Check if we only allow navigation and navigate to a url on the pasteboard.
192  if ([self onlyAllowsNavigation]) {
193    NSPasteboard* pboard = [info draggingPasteboard];
194    if ([pboard containsURLData]) {
195      GURL url;
196      ui::PopulateURLAndTitleFromPasteboard(&url, NULL, pboard, YES);
197      webContents_->OpenURL(OpenURLParams(
198          url, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_AUTO_BOOKMARK,
199          false));
200      return YES;
201    } else {
202      return NO;
203    }
204  }
205
206  if (delegate_)
207    delegate_->OnDrop();
208
209  currentRVH_ = NULL;
210
211  // Create the appropriate mouse locations for WebCore. The draggingLocation
212  // is in window coordinates. Both need to be flipped.
213  NSPoint windowPoint = [info draggingLocation];
214  NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
215  NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
216  webContents_->GetRenderViewHost()->DragTargetDrop(
217      gfx::Point(viewPoint.x, viewPoint.y),
218      gfx::Point(screenPoint.x, screenPoint.y),
219      GetModifierFlags());
220
221  dropData_.reset();
222
223  return YES;
224}
225
226// Given |data|, which should not be nil, fill it in using the contents of the
227// given pasteboard. The types handled by this method should be kept in sync
228// with [WebContentsViewCocoa registerDragTypes].
229- (void)populateWebDropData:(WebDropData*)data
230             fromPasteboard:(NSPasteboard*)pboard {
231  DCHECK(data);
232  DCHECK(pboard);
233  NSArray* types = [pboard types];
234
235  // Get URL if possible. To avoid exposing file system paths to web content,
236  // filenames in the drag are not converted to file URLs.
237  ui::PopulateURLAndTitleFromPasteboard(&data->url,
238                                        &data->url_title,
239                                        pboard,
240                                        NO);
241
242  // Get plain text.
243  if ([types containsObject:NSStringPboardType]) {
244    data->text = NullableString16(
245        base::SysNSStringToUTF16([pboard stringForType:NSStringPboardType]),
246        false);
247  }
248
249  // Get HTML. If there's no HTML, try RTF.
250  if ([types containsObject:NSHTMLPboardType]) {
251    data->html = NullableString16(
252        base::SysNSStringToUTF16([pboard stringForType:NSHTMLPboardType]),
253        false);
254  } else if ([types containsObject:NSRTFPboardType]) {
255    NSString* html = [pboard htmlFromRtf];
256    data->html = NullableString16(base::SysNSStringToUTF16(html), false);
257  }
258
259  // Get files.
260  if ([types containsObject:NSFilenamesPboardType]) {
261    NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
262    if ([files isKindOfClass:[NSArray class]] && [files count]) {
263      for (NSUInteger i = 0; i < [files count]; i++) {
264        NSString* filename = [files objectAtIndex:i];
265        BOOL exists = [[NSFileManager defaultManager]
266                           fileExistsAtPath:filename];
267        if (exists) {
268          data->filenames.push_back(
269              WebDropData::FileInfo(
270                  base::SysNSStringToUTF16(filename), string16()));
271        }
272      }
273    }
274  }
275
276  // TODO(pinkerton): Get file contents. http://crbug.com/34661
277
278  // Get custom MIME data.
279  if ([types containsObject:ui::kWebCustomDataPboardType]) {
280    NSData* customData = [pboard dataForType:ui::kWebCustomDataPboardType];
281    ui::ReadCustomDataIntoMap([customData bytes],
282                              [customData length],
283                              &data->custom_data);
284  }
285}
286
287@end
288