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