web_drag_dest_mac.mm revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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#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" 20 21using blink::WebDragOperationsMask; 22using content::DropData; 23using content::OpenURLParams; 24using content::Referrer; 25using content::WebContentsImpl; 26 27int 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; 39} 40 41@implementation WebDragDest 42 43// |contents| is the WebContentsImpl representing this tab, used to communicate 44// drag&drop messages to WebCore and handle navigation on a successful drop 45// (if necessary). 46- (id)initWithWebContentsImpl:(WebContentsImpl*)contents { 47 if ((self = [super init])) { 48 webContents_ = contents; 49 canceled_ = false; 50 } 51 return self; 52} 53 54- (DropData*)currentDropData { 55 return dropData_.get(); 56} 57 58- (void)setDragDelegate:(content::WebDragDestDelegate*)delegate { 59 delegate_ = delegate; 60} 61 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; 66} 67 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 71 view:(NSView*)view { 72 DCHECK(view); 73 NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil]; 74 NSRect viewFrame = [view frame]; 75 viewPoint.y = viewFrame.size.height - viewPoint.y; 76 return viewPoint; 77} 78 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 82 view:(NSView*)view { 83 DCHECK(view); 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; 88 return screenPoint; 89} 90 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 95// browsing warning). 96- (BOOL)onlyAllowsNavigation { 97 return webContents_->ShowingInterstitialPage(); 98} 99 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. 102 103- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info 104 view:(NSView*)view { 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(); 108 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]]; 114 115 NSDragOperation mask = [info draggingSourceOperationMask]; 116 117 // Give the delegate an opportunity to cancel the drag. 118 canceled_ = !webContents_->GetDelegate()->CanDragEnter( 119 webContents_, 120 *dropData, 121 static_cast<WebDragOperationsMask>(mask)); 122 if (canceled_) 123 return NSDragOperationNone; 124 125 if ([self onlyAllowsNavigation]) { 126 if ([[info draggingPasteboard] containsURLData]) 127 return NSDragOperationCopy; 128 return NSDragOperationNone; 129 } 130 131 if (delegate_) { 132 delegate_->DragInitialize(webContents_); 133 delegate_->OnDragEnter(); 134 } 135 136 dropData_.swap(dropData); 137 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( 144 *dropData_, 145 gfx::Point(viewPoint.x, viewPoint.y), 146 gfx::Point(screenPoint.x, screenPoint.y), 147 static_cast<WebDragOperationsMask>(mask), 148 GetModifierFlags()); 149 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_; 154} 155 156- (void)draggingExited:(id<NSDraggingInfo>)info { 157 DCHECK(currentRVH_); 158 if (currentRVH_ != webContents_->GetRenderViewHost()) 159 return; 160 161 if (canceled_) 162 return; 163 164 if ([self onlyAllowsNavigation]) 165 return; 166 167 if (delegate_) 168 delegate_->OnDragLeave(); 169 170 webContents_->GetRenderViewHost()->DragTargetDragLeave(); 171 dropData_.reset(); 172} 173 174- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info 175 view:(NSView*)view { 176 DCHECK(currentRVH_); 177 if (currentRVH_ != webContents_->GetRenderViewHost()) 178 [self draggingEntered:info view:view]; 179 180 if (canceled_) 181 return NSDragOperationNone; 182 183 if ([self onlyAllowsNavigation]) { 184 if ([[info draggingPasteboard] containsURLData]) 185 return NSDragOperationCopy; 186 return NSDragOperationNone; 187 } 188 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), 199 GetModifierFlags()); 200 201 if (delegate_) 202 delegate_->OnDragOver(); 203 204 return currentOperation_; 205} 206 207- (BOOL)performDragOperation:(id<NSDraggingInfo>)info 208 view:(NSView*)view { 209 if (currentRVH_ != webContents_->GetRenderViewHost()) 210 [self draggingEntered:info view:view]; 211 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]) { 216 GURL url; 217 ui::PopulateURLAndTitleFromPasteboard(&url, NULL, pboard, YES); 218 webContents_->OpenURL(OpenURLParams( 219 url, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_AUTO_BOOKMARK, 220 false)); 221 return YES; 222 } else { 223 return NO; 224 } 225 } 226 227 if (delegate_) 228 delegate_->OnDrop(); 229 230 currentRVH_ = NULL; 231 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), 240 GetModifierFlags()); 241 242 dropData_.reset(); 243 244 return YES; 245} 246 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 { 252 DCHECK(data); 253 DCHECK(pboard); 254 NSArray* types = [pboard types]; 255 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, 259 &data->url_title, 260 pboard, 261 NO); 262 263 // Get plain text. 264 if ([types containsObject:NSStringPboardType]) { 265 data->text = base::NullableString16( 266 base::SysNSStringToUTF16([pboard stringForType:NSStringPboardType]), 267 false); 268 } 269 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); 280 } 281 282 // Get files. 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]; 290 if (exists) { 291 data->filenames.push_back( 292 DropData::FileInfo( 293 base::SysNSStringToUTF16(filename), base::string16())); 294 } 295 } 296 } 297 } 298 299 // TODO(pinkerton): Get file contents. http://crbug.com/34661 300 301 // Get custom MIME data. 302 if ([types containsObject:ui::kWebCustomDataPboardType]) { 303 NSData* customData = [pboard dataForType:ui::kWebCustomDataPboardType]; 304 ui::ReadCustomDataIntoMap([customData bytes], 305 [customData length], 306 &data->custom_data); 307 } 308} 309 310@end 311