web_drag_dest_mac.mm revision effb81e5f8246d0db0270817048dc992db66e9fb
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 data->did_originate_from_renderer = 257 [types containsObject:ui::kChromeDragDummyPboardType]; 258 259 // Get URL if possible. To avoid exposing file system paths to web content, 260 // filenames in the drag are not converted to file URLs. 261 ui::PopulateURLAndTitleFromPasteboard(&data->url, 262 &data->url_title, 263 pboard, 264 NO); 265 266 // Get plain text. 267 if ([types containsObject:NSStringPboardType]) { 268 data->text = base::NullableString16( 269 base::SysNSStringToUTF16([pboard stringForType:NSStringPboardType]), 270 false); 271 } 272 273 // Get HTML. If there's no HTML, try RTF. 274 if ([types containsObject:NSHTMLPboardType]) { 275 NSString* html = [pboard stringForType:NSHTMLPboardType]; 276 data->html = base::NullableString16(base::SysNSStringToUTF16(html), false); 277 } else if ([types containsObject:ui::kChromeDragImageHTMLPboardType]) { 278 NSString* html = [pboard stringForType:ui::kChromeDragImageHTMLPboardType]; 279 data->html = base::NullableString16(base::SysNSStringToUTF16(html), false); 280 } else if ([types containsObject:NSRTFPboardType]) { 281 NSString* html = [pboard htmlFromRtf]; 282 data->html = base::NullableString16(base::SysNSStringToUTF16(html), false); 283 } 284 285 // Get files. 286 if ([types containsObject:NSFilenamesPboardType]) { 287 NSArray* files = [pboard propertyListForType:NSFilenamesPboardType]; 288 if ([files isKindOfClass:[NSArray class]] && [files count]) { 289 for (NSUInteger i = 0; i < [files count]; i++) { 290 NSString* filename = [files objectAtIndex:i]; 291 BOOL exists = [[NSFileManager defaultManager] 292 fileExistsAtPath:filename]; 293 if (exists) { 294 data->filenames.push_back(ui::FileInfo( 295 base::FilePath::FromUTF8Unsafe(base::SysNSStringToUTF8(filename)), 296 base::FilePath())); 297 } 298 } 299 } 300 } 301 302 // TODO(pinkerton): Get file contents. http://crbug.com/34661 303 304 // Get custom MIME data. 305 if ([types containsObject:ui::kWebCustomDataPboardType]) { 306 NSData* customData = [pboard dataForType:ui::kWebCustomDataPboardType]; 307 ui::ReadCustomDataIntoMap([customData bytes], 308 [customData length], 309 &data->custom_data); 310 } 311} 312 313@end 314