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 int GetModifierFlags() {
28 int modifier_state = 0;
29 UInt32 currentModifiers = GetCurrentKeyModifiers();
30 if (currentModifiers & ::shiftKey)
31 modifier_state |= blink::WebInputEvent::ShiftKey;
32 if (currentModifiers & ::controlKey)
33 modifier_state |= blink::WebInputEvent::ControlKey;
34 if (currentModifiers & ::optionKey)
35 modifier_state |= blink::WebInputEvent::AltKey;
36 if (currentModifiers & ::cmdKey)
37 modifier_state |= blink::WebInputEvent::MetaKey;
38 return modifier_state;
41 @implementation WebDragDest
43 // |contents| is the WebContentsImpl representing this tab, used to communicate
44 // drag&drop messages to WebCore and handle navigation on a successful drop
46 - (id)initWithWebContentsImpl:(WebContentsImpl*)contents {
47 if ((self = [super init])) {
48 webContents_ = contents;
54 - (DropData*)currentDropData {
55 return dropData_.get();
58 - (void)setDragDelegate:(content::WebDragDestDelegate*)delegate {
62 // Call to set whether or not we should allow the drop. Takes effect the
63 // next time |-draggingUpdated:| is called.
64 - (void)setCurrentOperation:(NSDragOperation)operation {
65 currentOperation_ = operation;
68 // Given a point in window coordinates and a view in that window, return a
69 // flipped point in the coordinate system of |view|.
70 - (NSPoint)flipWindowPointToView:(const NSPoint&)windowPoint
73 NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
74 NSRect viewFrame = [view frame];
75 viewPoint.y = viewFrame.size.height - viewPoint.y;
79 // Given a point in window coordinates and a view in that window, return a
80 // flipped point in screen coordinates.
81 - (NSPoint)flipWindowPointToScreen:(const NSPoint&)windowPoint
84 NSPoint screenPoint = [[view window] convertBaseToScreen:windowPoint];
85 NSScreen* screen = [[view window] screen];
86 NSRect screenFrame = [screen frame];
87 screenPoint.y = screenFrame.size.height - screenPoint.y;
91 // Return YES if the drop site only allows drops that would navigate. If this
92 // is the case, we don't want to pass messages to the renderer because there's
93 // really no point (i.e., there's nothing that cares about the mouse position or
94 // entering and exiting). One example is an interstitial page (e.g., safe
96 - (BOOL)onlyAllowsNavigation {
97 return webContents_->ShowingInterstitialPage();
100 // Messages to send during the tracking of a drag, usually upon receiving
101 // calls from the view system. Communicates the drag messages to WebCore.
103 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info
105 // Save off the RVH so we can tell if it changes during a drag. If it does,
106 // we need to send a new enter message in draggingUpdated:.
107 currentRVH_ = webContents_->GetRenderViewHost();
109 // Fill out a DropData from pasteboard.
110 scoped_ptr<DropData> dropData;
111 dropData.reset(new DropData());
112 [self populateDropData:dropData.get()
113 fromPasteboard:[info draggingPasteboard]];
115 NSDragOperation mask = [info draggingSourceOperationMask];
117 // Give the delegate an opportunity to cancel the drag.
118 canceled_ = !webContents_->GetDelegate()->CanDragEnter(
121 static_cast<WebDragOperationsMask>(mask));
123 return NSDragOperationNone;
125 if ([self onlyAllowsNavigation]) {
126 if ([[info draggingPasteboard] containsURLData])
127 return NSDragOperationCopy;
128 return NSDragOperationNone;
132 delegate_->DragInitialize(webContents_);
133 delegate_->OnDragEnter();
136 dropData_.swap(dropData);
138 // Create the appropriate mouse locations for WebCore. The draggingLocation
139 // is in window coordinates. Both need to be flipped.
140 NSPoint windowPoint = [info draggingLocation];
141 NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
142 NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
143 webContents_->GetRenderViewHost()->DragTargetDragEnter(
145 gfx::Point(viewPoint.x, viewPoint.y),
146 gfx::Point(screenPoint.x, screenPoint.y),
147 static_cast<WebDragOperationsMask>(mask),
150 // We won't know the true operation (whether the drag is allowed) until we
151 // hear back from the renderer. For now, be optimistic:
152 currentOperation_ = NSDragOperationCopy;
153 return currentOperation_;
156 - (void)draggingExited:(id<NSDraggingInfo>)info {
158 if (currentRVH_ != webContents_->GetRenderViewHost())
164 if ([self onlyAllowsNavigation])
168 delegate_->OnDragLeave();
170 webContents_->GetRenderViewHost()->DragTargetDragLeave();
174 - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info
177 if (currentRVH_ != webContents_->GetRenderViewHost())
178 [self draggingEntered:info view:view];
181 return NSDragOperationNone;
183 if ([self onlyAllowsNavigation]) {
184 if ([[info draggingPasteboard] containsURLData])
185 return NSDragOperationCopy;
186 return NSDragOperationNone;
189 // Create the appropriate mouse locations for WebCore. The draggingLocation
190 // is in window coordinates.
191 NSPoint windowPoint = [info draggingLocation];
192 NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
193 NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
194 NSDragOperation mask = [info draggingSourceOperationMask];
195 webContents_->GetRenderViewHost()->DragTargetDragOver(
196 gfx::Point(viewPoint.x, viewPoint.y),
197 gfx::Point(screenPoint.x, screenPoint.y),
198 static_cast<WebDragOperationsMask>(mask),
202 delegate_->OnDragOver();
204 return currentOperation_;
207 - (BOOL)performDragOperation:(id<NSDraggingInfo>)info
209 if (currentRVH_ != webContents_->GetRenderViewHost())
210 [self draggingEntered:info view:view];
212 // Check if we only allow navigation and navigate to a url on the pasteboard.
213 if ([self onlyAllowsNavigation]) {
214 NSPasteboard* pboard = [info draggingPasteboard];
215 if ([pboard containsURLData]) {
217 ui::PopulateURLAndTitleFromPasteboard(&url, NULL, pboard, YES);
218 webContents_->OpenURL(OpenURLParams(
219 url, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_AUTO_BOOKMARK,
232 // Create the appropriate mouse locations for WebCore. The draggingLocation
233 // is in window coordinates. Both need to be flipped.
234 NSPoint windowPoint = [info draggingLocation];
235 NSPoint viewPoint = [self flipWindowPointToView:windowPoint view:view];
236 NSPoint screenPoint = [self flipWindowPointToScreen:windowPoint view:view];
237 webContents_->GetRenderViewHost()->DragTargetDrop(
238 gfx::Point(viewPoint.x, viewPoint.y),
239 gfx::Point(screenPoint.x, screenPoint.y),
247 // Given |data|, which should not be nil, fill it in using the contents of the
248 // given pasteboard. The types handled by this method should be kept in sync
249 // with [WebContentsViewCocoa registerDragTypes].
250 - (void)populateDropData:(DropData*)data
251 fromPasteboard:(NSPasteboard*)pboard {
254 NSArray* types = [pboard types];
256 // Get URL if possible. To avoid exposing file system paths to web content,
257 // filenames in the drag are not converted to file URLs.
258 ui::PopulateURLAndTitleFromPasteboard(&data->url,
264 if ([types containsObject:NSStringPboardType]) {
265 data->text = base::NullableString16(
266 base::SysNSStringToUTF16([pboard stringForType:NSStringPboardType]),
270 // Get HTML. If there's no HTML, try RTF.
271 if ([types containsObject:NSHTMLPboardType]) {
272 NSString* html = [pboard stringForType:NSHTMLPboardType];
273 data->html = base::NullableString16(base::SysNSStringToUTF16(html), false);
274 } else if ([types containsObject:ui::kChromeDragImageHTMLPboardType]) {
275 NSString* html = [pboard stringForType:ui::kChromeDragImageHTMLPboardType];
276 data->html = base::NullableString16(base::SysNSStringToUTF16(html), false);
277 } else if ([types containsObject:NSRTFPboardType]) {
278 NSString* html = [pboard htmlFromRtf];
279 data->html = base::NullableString16(base::SysNSStringToUTF16(html), false);
283 if ([types containsObject:NSFilenamesPboardType]) {
284 NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
285 if ([files isKindOfClass:[NSArray class]] && [files count]) {
286 for (NSUInteger i = 0; i < [files count]; i++) {
287 NSString* filename = [files objectAtIndex:i];
288 BOOL exists = [[NSFileManager defaultManager]
289 fileExistsAtPath:filename];
291 data->filenames.push_back(
293 base::SysNSStringToUTF16(filename), base::string16()));
299 // TODO(pinkerton): Get file contents. http://crbug.com/34661
301 // Get custom MIME data.
302 if ([types containsObject:ui::kWebCustomDataPboardType]) {
303 NSData* customData = [pboard dataForType:ui::kWebCustomDataPboardType];
304 ui::ReadCustomDataIntoMap([customData bytes],