| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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 "chrome/browser/ui/cocoa/web_drag_source.h" | |
| 6 | |
| 7 #include "app/mac/nsimage_cache.h" | |
| 8 #include "base/file_path.h" | |
| 9 #include "base/string_util.h" | |
| 10 #include "base/sys_string_conversions.h" | |
| 11 #include "base/task.h" | |
| 12 #include "base/threading/thread.h" | |
| 13 #include "base/utf_string_conversions.h" | |
| 14 #include "chrome/browser/browser_process.h" | |
| 15 #include "chrome/browser/download/download_manager.h" | |
| 16 #include "chrome/browser/download/download_util.h" | |
| 17 #include "chrome/browser/download/drag_download_file.h" | |
| 18 #include "chrome/browser/download/drag_download_util.h" | |
| 19 #include "chrome/browser/renderer_host/render_view_host.h" | |
| 20 #include "chrome/browser/tab_contents/tab_contents.h" | |
| 21 #include "chrome/browser/tab_contents/tab_contents_view_mac.h" | |
| 22 #include "net/base/file_stream.h" | |
| 23 #include "net/base/net_util.h" | |
| 24 #import "third_party/mozilla/NSPasteboard+Utils.h" | |
| 25 #include "webkit/glue/webdropdata.h" | |
| 26 | |
| 27 using base::SysNSStringToUTF8; | |
| 28 using base::SysUTF8ToNSString; | |
| 29 using base::SysUTF16ToNSString; | |
| 30 using net::FileStream; | |
| 31 | |
| 32 | |
| 33 namespace { | |
| 34 | |
| 35 // An unofficial standard pasteboard title type to be provided alongside the | |
| 36 // |NSURLPboardType|. | |
| 37 NSString* const kNSURLTitlePboardType = @"public.url-name"; | |
| 38 | |
| 39 // Returns a filename appropriate for the drop data | |
| 40 // TODO(viettrungluu): Refactor to make it common across platforms, | |
| 41 // and move it somewhere sensible. | |
| 42 FilePath GetFileNameFromDragData(const WebDropData& drop_data) { | |
| 43 // Images without ALT text will only have a file extension so we need to | |
| 44 // synthesize one from the provided extension and URL. | |
| 45 FilePath file_name([SysUTF16ToNSString(drop_data.file_description_filename) | |
| 46 fileSystemRepresentation]); | |
| 47 file_name = file_name.BaseName().RemoveExtension(); | |
| 48 | |
| 49 if (file_name.empty()) { | |
| 50 // Retrieve the name from the URL. | |
| 51 file_name = net::GetSuggestedFilename(drop_data.url, "", "", FilePath()); | |
| 52 } | |
| 53 | |
| 54 file_name = file_name.ReplaceExtension([SysUTF16ToNSString( | |
| 55 drop_data.file_extension) fileSystemRepresentation]); | |
| 56 | |
| 57 return file_name; | |
| 58 } | |
| 59 | |
| 60 // This class's sole task is to write out data for a promised file; the caller | |
| 61 // is responsible for opening the file. | |
| 62 class PromiseWriterTask : public Task { | |
| 63 public: | |
| 64 // Assumes ownership of file_stream. | |
| 65 PromiseWriterTask(const WebDropData& drop_data, | |
| 66 FileStream* file_stream); | |
| 67 virtual ~PromiseWriterTask(); | |
| 68 virtual void Run(); | |
| 69 | |
| 70 private: | |
| 71 WebDropData drop_data_; | |
| 72 | |
| 73 // This class takes ownership of file_stream_ and will close and delete it. | |
| 74 scoped_ptr<FileStream> file_stream_; | |
| 75 }; | |
| 76 | |
| 77 // Takes the drop data and an open file stream (which it takes ownership of and | |
| 78 // will close and delete). | |
| 79 PromiseWriterTask::PromiseWriterTask(const WebDropData& drop_data, | |
| 80 FileStream* file_stream) : | |
| 81 drop_data_(drop_data) { | |
| 82 file_stream_.reset(file_stream); | |
| 83 DCHECK(file_stream_.get()); | |
| 84 } | |
| 85 | |
| 86 PromiseWriterTask::~PromiseWriterTask() { | |
| 87 DCHECK(file_stream_.get()); | |
| 88 if (file_stream_.get()) | |
| 89 file_stream_->Close(); | |
| 90 } | |
| 91 | |
| 92 void PromiseWriterTask::Run() { | |
| 93 CHECK(file_stream_.get()); | |
| 94 file_stream_->Write(drop_data_.file_contents.data(), | |
| 95 drop_data_.file_contents.length(), | |
| 96 NULL); | |
| 97 | |
| 98 // Let our destructor take care of business. | |
| 99 } | |
| 100 | |
| 101 } // namespace | |
| 102 | |
| 103 | |
| 104 @interface WebDragSource(Private) | |
| 105 | |
| 106 - (void)fillPasteboard; | |
| 107 - (NSImage*)dragImage; | |
| 108 | |
| 109 @end // @interface WebDragSource(Private) | |
| 110 | |
| 111 | |
| 112 @implementation WebDragSource | |
| 113 | |
| 114 - (id)initWithContentsView:(TabContentsViewCocoa*)contentsView | |
| 115 dropData:(const WebDropData*)dropData | |
| 116 image:(NSImage*)image | |
| 117 offset:(NSPoint)offset | |
| 118 pasteboard:(NSPasteboard*)pboard | |
| 119 dragOperationMask:(NSDragOperation)dragOperationMask { | |
| 120 if ((self = [super init])) { | |
| 121 contentsView_ = contentsView; | |
| 122 DCHECK(contentsView_); | |
| 123 | |
| 124 dropData_.reset(new WebDropData(*dropData)); | |
| 125 DCHECK(dropData_.get()); | |
| 126 | |
| 127 dragImage_.reset([image retain]); | |
| 128 imageOffset_ = offset; | |
| 129 | |
| 130 pasteboard_.reset([pboard retain]); | |
| 131 DCHECK(pasteboard_.get()); | |
| 132 | |
| 133 dragOperationMask_ = dragOperationMask; | |
| 134 | |
| 135 [self fillPasteboard]; | |
| 136 } | |
| 137 | |
| 138 return self; | |
| 139 } | |
| 140 | |
| 141 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { | |
| 142 return dragOperationMask_; | |
| 143 } | |
| 144 | |
| 145 - (void)lazyWriteToPasteboard:(NSPasteboard*)pboard forType:(NSString*)type { | |
| 146 // NSHTMLPboardType requires the character set to be declared. Otherwise, it | |
| 147 // assumes US-ASCII. Awesome. | |
| 148 static const string16 kHtmlHeader = | |
| 149 ASCIIToUTF16("<meta http-equiv=\"Content-Type\" " | |
| 150 "content=\"text/html;charset=UTF-8\">"); | |
| 151 | |
| 152 // Be extra paranoid; avoid crashing. | |
| 153 if (!dropData_.get()) { | |
| 154 NOTREACHED() << "No drag-and-drop data available for lazy write."; | |
| 155 return; | |
| 156 } | |
| 157 | |
| 158 // HTML. | |
| 159 if ([type isEqualToString:NSHTMLPboardType]) { | |
| 160 DCHECK(!dropData_->text_html.empty()); | |
| 161 // See comment on |kHtmlHeader| above. | |
| 162 [pboard setString:SysUTF16ToNSString(kHtmlHeader + dropData_->text_html) | |
| 163 forType:NSHTMLPboardType]; | |
| 164 | |
| 165 // URL. | |
| 166 } else if ([type isEqualToString:NSURLPboardType]) { | |
| 167 DCHECK(dropData_->url.is_valid()); | |
| 168 NSURL* url = [NSURL URLWithString:SysUTF8ToNSString(dropData_->url.spec())]; | |
| 169 [url writeToPasteboard:pboard]; | |
| 170 | |
| 171 // URL title. | |
| 172 } else if ([type isEqualToString:kNSURLTitlePboardType]) { | |
| 173 [pboard setString:SysUTF16ToNSString(dropData_->url_title) | |
| 174 forType:kNSURLTitlePboardType]; | |
| 175 | |
| 176 // File contents. | |
| 177 } else if ([type isEqualToString:NSFileContentsPboardType] || | |
| 178 [type isEqualToString:NSCreateFileContentsPboardType( | |
| 179 SysUTF16ToNSString(dropData_->file_extension))]) { | |
| 180 // TODO(viettrungluu: find something which is known to accept | |
| 181 // NSFileContentsPboardType to check that this actually works! | |
| 182 scoped_nsobject<NSFileWrapper> file_wrapper( | |
| 183 [[NSFileWrapper alloc] initRegularFileWithContents:[NSData | |
| 184 dataWithBytes:dropData_->file_contents.data() | |
| 185 length:dropData_->file_contents.length()]]); | |
| 186 [file_wrapper setPreferredFilename:SysUTF8ToNSString( | |
| 187 GetFileNameFromDragData(*dropData_).value())]; | |
| 188 [pboard writeFileWrapper:file_wrapper]; | |
| 189 | |
| 190 // TIFF. | |
| 191 } else if ([type isEqualToString:NSTIFFPboardType]) { | |
| 192 // TODO(viettrungluu): This is a bit odd since we rely on Cocoa to render | |
| 193 // our image into a TIFF. This is also suboptimal since this is all done | |
| 194 // synchronously. I'm not sure there's much we can easily do about it. | |
| 195 scoped_nsobject<NSImage> image( | |
| 196 [[NSImage alloc] initWithData:[NSData | |
| 197 dataWithBytes:dropData_->file_contents.data() | |
| 198 length:dropData_->file_contents.length()]]); | |
| 199 [pboard setData:[image TIFFRepresentation] forType:NSTIFFPboardType]; | |
| 200 | |
| 201 // Plain text. | |
| 202 } else if ([type isEqualToString:NSStringPboardType]) { | |
| 203 DCHECK(!dropData_->plain_text.empty()); | |
| 204 [pboard setString:SysUTF16ToNSString(dropData_->plain_text) | |
| 205 forType:NSStringPboardType]; | |
| 206 | |
| 207 // Oops! | |
| 208 } else { | |
| 209 NOTREACHED() << "Asked for a drag pasteboard type we didn't offer."; | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 - (NSPoint)convertScreenPoint:(NSPoint)screenPoint { | |
| 214 DCHECK([contentsView_ window]); | |
| 215 NSPoint basePoint = [[contentsView_ window] convertScreenToBase:screenPoint]; | |
| 216 return [contentsView_ convertPoint:basePoint fromView:nil]; | |
| 217 } | |
| 218 | |
| 219 - (void)startDrag { | |
| 220 NSEvent* currentEvent = [NSApp currentEvent]; | |
| 221 | |
| 222 // Synthesize an event for dragging, since we can't be sure that | |
| 223 // [NSApp currentEvent] will return a valid dragging event. | |
| 224 NSWindow* window = [contentsView_ window]; | |
| 225 NSPoint position = [window mouseLocationOutsideOfEventStream]; | |
| 226 NSTimeInterval eventTime = [currentEvent timestamp]; | |
| 227 NSEvent* dragEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged | |
| 228 location:position | |
| 229 modifierFlags:NSLeftMouseDraggedMask | |
| 230 timestamp:eventTime | |
| 231 windowNumber:[window windowNumber] | |
| 232 context:nil | |
| 233 eventNumber:0 | |
| 234 clickCount:1 | |
| 235 pressure:1.0]; | |
| 236 | |
| 237 if (dragImage_) { | |
| 238 position.x -= imageOffset_.x; | |
| 239 // Deal with Cocoa's flipped coordinate system. | |
| 240 position.y -= [dragImage_.get() size].height - imageOffset_.y; | |
| 241 } | |
| 242 // Per kwebster, offset arg is ignored, see -_web_DragImageForElement: in | |
| 243 // third_party/WebKit/Source/WebKit/mac/Misc/WebNSViewExtras.m. | |
| 244 [window dragImage:[self dragImage] | |
| 245 at:position | |
| 246 offset:NSZeroSize | |
| 247 event:dragEvent | |
| 248 pasteboard:pasteboard_ | |
| 249 source:contentsView_ | |
| 250 slideBack:YES]; | |
| 251 } | |
| 252 | |
| 253 - (void)endDragAt:(NSPoint)screenPoint | |
| 254 operation:(NSDragOperation)operation { | |
| 255 RenderViewHost* rvh = [contentsView_ tabContents]->render_view_host(); | |
| 256 if (rvh) { | |
| 257 rvh->DragSourceSystemDragEnded(); | |
| 258 | |
| 259 // Convert |screenPoint| to view coordinates and flip it. | |
| 260 NSPoint localPoint = NSMakePoint(0, 0); | |
| 261 if ([contentsView_ window]) | |
| 262 localPoint = [self convertScreenPoint:screenPoint]; | |
| 263 NSRect viewFrame = [contentsView_ frame]; | |
| 264 localPoint.y = viewFrame.size.height - localPoint.y; | |
| 265 // Flip |screenPoint|. | |
| 266 NSRect screenFrame = [[[contentsView_ window] screen] frame]; | |
| 267 screenPoint.y = screenFrame.size.height - screenPoint.y; | |
| 268 | |
| 269 rvh->DragSourceEndedAt(localPoint.x, localPoint.y, | |
| 270 screenPoint.x, screenPoint.y, | |
| 271 static_cast<WebKit::WebDragOperation>(operation)); | |
| 272 } | |
| 273 | |
| 274 // Make sure the pasteboard owner isn't us. | |
| 275 [pasteboard_ declareTypes:[NSArray array] owner:nil]; | |
| 276 } | |
| 277 | |
| 278 - (void)moveDragTo:(NSPoint)screenPoint { | |
| 279 RenderViewHost* rvh = [contentsView_ tabContents]->render_view_host(); | |
| 280 if (rvh) { | |
| 281 // Convert |screenPoint| to view coordinates and flip it. | |
| 282 NSPoint localPoint = NSMakePoint(0, 0); | |
| 283 if ([contentsView_ window]) | |
| 284 localPoint = [self convertScreenPoint:screenPoint]; | |
| 285 NSRect viewFrame = [contentsView_ frame]; | |
| 286 localPoint.y = viewFrame.size.height - localPoint.y; | |
| 287 // Flip |screenPoint|. | |
| 288 NSRect screenFrame = [[[contentsView_ window] screen] frame]; | |
| 289 screenPoint.y = screenFrame.size.height - screenPoint.y; | |
| 290 | |
| 291 rvh->DragSourceMovedTo(localPoint.x, localPoint.y, | |
| 292 screenPoint.x, screenPoint.y); | |
| 293 } | |
| 294 } | |
| 295 | |
| 296 - (NSString*)dragPromisedFileTo:(NSString*)path { | |
| 297 // Be extra paranoid; avoid crashing. | |
| 298 if (!dropData_.get()) { | |
| 299 NOTREACHED() << "No drag-and-drop data available for promised file."; | |
| 300 return nil; | |
| 301 } | |
| 302 | |
| 303 FilePath fileName = downloadFileName_.empty() ? | |
| 304 GetFileNameFromDragData(*dropData_) : downloadFileName_; | |
| 305 FilePath filePath(SysNSStringToUTF8(path)); | |
| 306 filePath = filePath.Append(fileName); | |
| 307 FileStream* fileStream = | |
| 308 drag_download_util::CreateFileStreamForDrop(&filePath); | |
| 309 if (!fileStream) | |
| 310 return nil; | |
| 311 | |
| 312 if (downloadURL_.is_valid()) { | |
| 313 TabContents* tabContents = [contentsView_ tabContents]; | |
| 314 scoped_refptr<DragDownloadFile> dragFileDownloader(new DragDownloadFile( | |
| 315 filePath, | |
| 316 linked_ptr<net::FileStream>(fileStream), | |
| 317 downloadURL_, | |
| 318 tabContents->GetURL(), | |
| 319 tabContents->encoding(), | |
| 320 tabContents)); | |
| 321 | |
| 322 // The finalizer will take care of closing and deletion. | |
| 323 dragFileDownloader->Start( | |
| 324 new drag_download_util::PromiseFileFinalizer(dragFileDownloader)); | |
| 325 } else { | |
| 326 // The writer will take care of closing and deletion. | |
| 327 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, | |
| 328 new PromiseWriterTask(*dropData_, fileStream)); | |
| 329 } | |
| 330 | |
| 331 // Once we've created the file, we should return the file name. | |
| 332 return SysUTF8ToNSString(filePath.BaseName().value()); | |
| 333 } | |
| 334 | |
| 335 @end // @implementation WebDragSource | |
| 336 | |
| 337 | |
| 338 @implementation WebDragSource (Private) | |
| 339 | |
| 340 - (void)fillPasteboard { | |
| 341 DCHECK(pasteboard_.get()); | |
| 342 | |
| 343 [pasteboard_ declareTypes:[NSArray array] owner:contentsView_]; | |
| 344 | |
| 345 // HTML. | |
| 346 if (!dropData_->text_html.empty()) | |
| 347 [pasteboard_ addTypes:[NSArray arrayWithObject:NSHTMLPboardType] | |
| 348 owner:contentsView_]; | |
| 349 | |
| 350 // URL (and title). | |
| 351 if (dropData_->url.is_valid()) | |
| 352 [pasteboard_ addTypes:[NSArray arrayWithObjects:NSURLPboardType, | |
| 353 kNSURLTitlePboardType, nil] | |
| 354 owner:contentsView_]; | |
| 355 | |
| 356 // File. | |
| 357 if (!dropData_->file_contents.empty() || | |
| 358 !dropData_->download_metadata.empty()) { | |
| 359 NSString* fileExtension = 0; | |
| 360 | |
| 361 if (dropData_->download_metadata.empty()) { | |
| 362 // |dropData_->file_extension| comes with the '.', which we must strip. | |
| 363 fileExtension = (dropData_->file_extension.length() > 0) ? | |
| 364 SysUTF16ToNSString(dropData_->file_extension.substr(1)) : @""; | |
| 365 } else { | |
| 366 string16 mimeType; | |
| 367 FilePath fileName; | |
| 368 if (drag_download_util::ParseDownloadMetadata( | |
| 369 dropData_->download_metadata, | |
| 370 &mimeType, | |
| 371 &fileName, | |
| 372 &downloadURL_)) { | |
| 373 std::string contentDisposition = | |
| 374 "attachment; filename=" + fileName.value(); | |
| 375 download_util::GenerateFileName(downloadURL_, | |
| 376 contentDisposition, | |
| 377 std::string(), | |
| 378 UTF16ToUTF8(mimeType), | |
| 379 &downloadFileName_); | |
| 380 fileExtension = SysUTF8ToNSString(downloadFileName_.Extension()); | |
| 381 } | |
| 382 } | |
| 383 | |
| 384 if (fileExtension) { | |
| 385 // File contents (with and without specific type), file (HFS) promise, | |
| 386 // TIFF. | |
| 387 // TODO(viettrungluu): others? | |
| 388 [pasteboard_ addTypes:[NSArray arrayWithObjects: | |
| 389 NSFileContentsPboardType, | |
| 390 NSCreateFileContentsPboardType(fileExtension), | |
| 391 NSFilesPromisePboardType, | |
| 392 NSTIFFPboardType, | |
| 393 nil] | |
| 394 owner:contentsView_]; | |
| 395 | |
| 396 // For the file promise, we need to specify the extension. | |
| 397 [pasteboard_ setPropertyList:[NSArray arrayWithObject:fileExtension] | |
| 398 forType:NSFilesPromisePboardType]; | |
| 399 } | |
| 400 } | |
| 401 | |
| 402 // Plain text. | |
| 403 if (!dropData_->plain_text.empty()) | |
| 404 [pasteboard_ addTypes:[NSArray arrayWithObject:NSStringPboardType] | |
| 405 owner:contentsView_]; | |
| 406 } | |
| 407 | |
| 408 - (NSImage*)dragImage { | |
| 409 if (dragImage_) | |
| 410 return dragImage_; | |
| 411 | |
| 412 // Default to returning a generic image. | |
| 413 return app::mac::GetCachedImageWithName(@"nav.pdf"); | |
| 414 } | |
| 415 | |
| 416 @end // @implementation WebDragSource (Private) | |
| OLD | NEW |