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 #include "third_party/WebKit/public/web/WebInputEvent.h"
16 #import "third_party/mozilla/NSPasteboard+Utils.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;
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;
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
63 - (id)initWithWebContentsImpl:(WebContentsImpl*)contents {
64 if ((self = [super init])) {
65 webContents_ = contents;
71 - (DropData*)currentDropData {
72 return dropData_.get();
75 - (void)setDragDelegate:(content::WebDragDestDelegate*)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
90 NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
91 NSRect viewFrame = [view frame];
92 viewPoint.y = viewFrame.size.height - viewPoint.y;
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
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;
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
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(
138 static_cast<WebDragOperationsMask>(mask));
140 return NSDragOperationNone;
142 if ([self onlyAllowsNavigation]) {
143 if ([[info draggingPasteboard] containsURLData])
144 return NSDragOperationCopy;
145 return NSDragOperationNone;
149 delegate_->DragInitialize(webContents_);
150 delegate_->OnDragEnter();
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(
162 gfx::Point(viewPoint.x, viewPoint.y),
163 gfx::Point(screenPoint.x, screenPoint.y),
164 static_cast<WebDragOperationsMask>(mask),
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 {
175 if (currentRVH_ != webContents_->GetRenderViewHost())
181 if ([self onlyAllowsNavigation])
185 delegate_->OnDragLeave();
187 webContents_->GetRenderViewHost()->DragTargetDragLeave();
191 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info
194 if (currentRVH_ != webContents_->GetRenderViewHost())
195 [self draggingEntered:info view:view];
198 return NSDragOperationNone;
200 if ([self onlyAllowsNavigation]) {
201 if ([[info draggingPasteboard] containsURLData])
202 return NSDragOperationCopy;
203 return NSDragOperationNone;
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),
219 delegate_->OnDragOver();
221 return currentOperation_;
224 - (BOOL)performDragOperation:(id<NSDraggingInfo>)info
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]) {
234 ui::PopulateURLAndTitleFromPasteboard(&url, NULL, pboard, YES);
235 webContents_->OpenURL(OpenURLParams(
236 url, Referrer(), CURRENT_TAB, ui::PAGE_TRANSITION_AUTO_BOOKMARK,
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),
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 {
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,
284 if ([types containsObject:NSStringPboardType]) {
285 data->text = base::NullableString16(
286 base::SysNSStringToUTF16([pboard stringForType:NSStringPboardType]),
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);
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];
311 data->filenames.push_back(ui::FileInfo(
312 base::FilePath::FromUTF8Unsafe(base::SysNSStringToUTF8(filename)),
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],