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