Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: content/browser/web_contents/web_drag_source_mac.mm

Issue 23775005: Go back to the old method of doing file drop promises. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: more detail in brokenness Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « content/browser/web_contents/web_drag_source_mac.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "content/browser/web_contents/web_drag_source_mac.h" 5 #import "content/browser/web_contents/web_drag_source_mac.h"
6 6
7 #include <sys/param.h> 7 #include <sys/param.h>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/files/file_path.h" 10 #include "base/files/file_path.h"
11 #include "base/mac/mac_logging.h"
12 #include "base/mac/mac_util.h" 11 #include "base/mac/mac_util.h"
13 #include "base/mac/scoped_aedesc.h"
14 #include "base/pickle.h" 12 #include "base/pickle.h"
15 #include "base/strings/string_util.h" 13 #include "base/strings/string_util.h"
16 #include "base/strings/sys_string_conversions.h" 14 #include "base/strings/sys_string_conversions.h"
17 #include "base/strings/utf_string_conversions.h" 15 #include "base/strings/utf_string_conversions.h"
18 #include "base/threading/thread.h" 16 #include "base/threading/thread.h"
19 #include "base/threading/thread_restrictions.h" 17 #include "base/threading/thread_restrictions.h"
20 #include "content/browser/browser_thread_impl.h" 18 #include "content/browser/browser_thread_impl.h"
21 #include "content/browser/download/drag_download_file.h" 19 #include "content/browser/download/drag_download_file.h"
22 #include "content/browser/download/drag_download_util.h" 20 #include "content/browser/download/drag_download_util.h"
23 #include "content/browser/renderer_host/render_view_host_impl.h" 21 #include "content/browser/renderer_host/render_view_host_impl.h"
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
86 // This helper's sole task is to write out data for a promised file; the caller 84 // This helper's sole task is to write out data for a promised file; the caller
87 // is responsible for opening the file. It takes the drop data and an open file 85 // is responsible for opening the file. It takes the drop data and an open file
88 // stream. 86 // stream.
89 void PromiseWriterHelper(const DropData& drop_data, 87 void PromiseWriterHelper(const DropData& drop_data,
90 scoped_ptr<FileStream> file_stream) { 88 scoped_ptr<FileStream> file_stream) {
91 DCHECK(file_stream); 89 DCHECK(file_stream);
92 file_stream->WriteSync(drop_data.file_contents.data(), 90 file_stream->WriteSync(drop_data.file_contents.data(),
93 drop_data.file_contents.length()); 91 drop_data.file_contents.length());
94 } 92 }
95 93
96 // Returns the drop location from a pasteboard.
97 NSString* GetDropLocation(NSPasteboard* pboard) {
98 // The API to get the drop location during a callback from
99 // kPasteboardTypeFileURLPromise is PasteboardCopyPasteLocation, which takes
100 // a PasteboardRef, which isn't bridged with NSPasteboard. Ugh.
101
102 PasteboardRef pb_ref = NULL;
103 OSStatus status = PasteboardCreate(base::mac::NSToCFCast([pboard name]),
104 &pb_ref);
105 if (status != noErr || !pb_ref) {
106 OSSTATUS_DCHECK(false, status) << "Error finding Carbon pasteboard";
107 return nil;
108 }
109 base::ScopedCFTypeRef<PasteboardRef> pb_ref_scoper(pb_ref);
110 PasteboardSynchronize(pb_ref);
111
112 CFURLRef drop_url = NULL;
113 status = PasteboardCopyPasteLocation(pb_ref, &drop_url);
114 if (status != noErr || !drop_url) {
115 OSSTATUS_DCHECK(false, status) << "Error finding drop location";
116 return nil;
117 }
118 base::ScopedCFTypeRef<CFURLRef> drop_url_scoper(drop_url);
119
120 NSString* drop_path = [base::mac::CFToNSCast(drop_url) path];
121 return drop_path;
122 }
123
124 void SelectFileInFinder(const base::FilePath& file_path) {
125 DCHECK([NSThread isMainThread]);
126
127 // Create the target of this AppleEvent, the Finder.
128 base::mac::ScopedAEDesc<AEAddressDesc> address;
129 const OSType finder_creator_code = 'MACS';
130 OSErr status = AECreateDesc(typeApplSignature, // type
131 &finder_creator_code, // data
132 sizeof(finder_creator_code), // dataSize
133 address.OutPointer()); // result
134 if (status != noErr) {
135 OSSTATUS_LOG(WARNING, status) << "Could not create SelectFile() AE target";
136 return;
137 }
138
139 // Build the AppleEvent data structure that instructs Finder to select files.
140 base::mac::ScopedAEDesc<AppleEvent> the_event;
141 status = AECreateAppleEvent(kAEMiscStandards, // theAEEventClass
142 kAESelect, // theAEEventID
143 address, // target
144 kAutoGenerateReturnID, // returnID
145 kAnyTransactionID, // transactionID
146 the_event.OutPointer()); // result
147 if (status != noErr) {
148 OSSTATUS_LOG(WARNING, status) << "Could not create SelectFile() AE event";
149 return;
150 }
151
152 // Create the list of files (only ever one) to select.
153 base::mac::ScopedAEDesc<AEDescList> file_list;
154 status = AECreateList(NULL, // factoringPtr
155 0, // factoredSize
156 false, // isRecord
157 file_list.OutPointer()); // resultList
158 if (status != noErr) {
159 OSSTATUS_LOG(WARNING, status)
160 << "Could not create SelectFile() AE file list";
161 return;
162 }
163
164 // Add the single path to the file list.
165 NSURL* url = [NSURL fileURLWithPath:SysUTF8ToNSString(file_path.value())];
166 base::ScopedCFTypeRef<CFDataRef> url_data(
167 CFURLCreateData(kCFAllocatorDefault, base::mac::NSToCFCast(url),
168 kCFStringEncodingUTF8, true));
169 status = AEPutPtr(file_list.OutPointer(), // theAEDescList
170 0, // index
171 typeFileURL, // typeCode
172 CFDataGetBytePtr(url_data), // dataPtr
173 CFDataGetLength(url_data)); // dataSize
174 if (status != noErr) {
175 OSSTATUS_LOG(WARNING, status)
176 << "Could not add file path to AE list in SelectFile()";
177 return;
178 }
179
180 // Attach the file list to the AppleEvent.
181 status = AEPutParamDesc(the_event.OutPointer(), // theAppleEvent
182 keyDirectObject, // theAEKeyword
183 file_list); // theAEDesc
184 if (status != noErr) {
185 OSSTATUS_LOG(WARNING, status)
186 << "Could not put the AE file list the path in SelectFile()";
187 return;
188 }
189
190 // Send the actual event. Do not care about the reply.
191 base::mac::ScopedAEDesc<AppleEvent> reply;
192 status = AESend(the_event, // theAppleEvent
193 reply.OutPointer(), // reply
194 kAENoReply + kAEAlwaysInteract, // sendMode
195 kAENormalPriority, // sendPriority
196 kAEDefaultTimeout, // timeOutInTicks
197 NULL, // idleProc
198 NULL); // filterProc
199 if (status != noErr) {
200 OSSTATUS_LOG(WARNING, status)
201 << "Could not send AE to Finder in SelectFile()";
202 }
203 }
204
205 } // namespace 94 } // namespace
206 95
207 96
208 @interface WebDragSource(Private) 97 @interface WebDragSource(Private)
209 98
210 - (void)writePromisedFileTo:(NSString*)path;
211 - (void)fillPasteboard; 99 - (void)fillPasteboard;
212 - (NSImage*)dragImage; 100 - (NSImage*)dragImage;
213 101
214 @end // @interface WebDragSource(Private) 102 @end // @interface WebDragSource(Private)
215 103
216 104
217 @implementation WebDragSource 105 @implementation WebDragSource
218 106
219 - (id)initWithContents:(content::WebContentsImpl*)contents 107 - (id)initWithContents:(content::WebContentsImpl*)contents
220 view:(NSView*)contentsView 108 view:(NSView*)contentsView
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
317 ui::WriteCustomDataToPickle(dropData_->custom_data, &pickle); 205 ui::WriteCustomDataToPickle(dropData_->custom_data, &pickle);
318 [pboard setData:[NSData dataWithBytes:pickle.data() length:pickle.size()] 206 [pboard setData:[NSData dataWithBytes:pickle.data() length:pickle.size()]
319 forType:ui::kWebCustomDataPboardType]; 207 forType:ui::kWebCustomDataPboardType];
320 208
321 // Dummy type. 209 // Dummy type.
322 } else if ([type isEqualToString:ui::kChromeDragDummyPboardType]) { 210 } else if ([type isEqualToString:ui::kChromeDragDummyPboardType]) {
323 // The dummy type _was_ promised and someone decided to call the bluff. 211 // The dummy type _was_ promised and someone decided to call the bluff.
324 [pboard setData:[NSData data] 212 [pboard setData:[NSData data]
325 forType:ui::kChromeDragDummyPboardType]; 213 forType:ui::kChromeDragDummyPboardType];
326 214
327 // File promise.
328 } else if ([type isEqualToString:
329 base::mac::CFToNSCast(kPasteboardTypeFileURLPromise)]) {
330 NSString* destination = GetDropLocation(pboard);
331 if (destination) {
332 [self writePromisedFileTo:destination];
333
334 // And set some data.
335 [pboard setData:[NSData data]
336 forType:base::mac::CFToNSCast(kPasteboardTypeFileURLPromise)];
337 }
338
339 // Oops! 215 // Oops!
340 } else { 216 } else {
341 // Unknown drag pasteboard type. 217 // Unknown drag pasteboard type.
342 NOTREACHED(); 218 NOTREACHED();
343 } 219 }
344 } 220 }
345 221
346 - (NSPoint)convertScreenPoint:(NSPoint)screenPoint { 222 - (NSPoint)convertScreenPoint:(NSPoint)screenPoint {
347 DCHECK([contentsView_ window]); 223 DCHECK([contentsView_ window]);
348 NSPoint basePoint = [[contentsView_ window] convertScreenToBase:screenPoint]; 224 NSPoint basePoint = [[contentsView_ window] convertScreenToBase:screenPoint];
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
430 localPoint.y = viewFrame.size.height - localPoint.y; 306 localPoint.y = viewFrame.size.height - localPoint.y;
431 // Flip |screenPoint|. 307 // Flip |screenPoint|.
432 NSRect screenFrame = [[[contentsView_ window] screen] frame]; 308 NSRect screenFrame = [[[contentsView_ window] screen] frame];
433 screenPoint.y = screenFrame.size.height - screenPoint.y; 309 screenPoint.y = screenFrame.size.height - screenPoint.y;
434 310
435 contents_->DragSourceMovedTo(localPoint.x, localPoint.y, 311 contents_->DragSourceMovedTo(localPoint.x, localPoint.y,
436 screenPoint.x, screenPoint.y); 312 screenPoint.x, screenPoint.y);
437 } 313 }
438 } 314 }
439 315
440 @end // @implementation WebDragSource 316 - (NSString*)dragPromisedFileTo:(NSString*)path {
441
442 @implementation WebDragSource (Private)
443
444 - (void)writePromisedFileTo:(NSString*)path {
445 // Be extra paranoid; avoid crashing. 317 // Be extra paranoid; avoid crashing.
446 if (!dropData_) { 318 if (!dropData_) {
447 NOTREACHED() << "No drag-and-drop data available for promised file."; 319 NOTREACHED() << "No drag-and-drop data available for promised file.";
448 return; 320 return nil;
449 } 321 }
450 322
451 base::FilePath filePath(SysNSStringToUTF8(path)); 323 base::FilePath filePath(SysNSStringToUTF8(path));
452 filePath = filePath.Append(downloadFileName_); 324 filePath = filePath.Append(downloadFileName_);
453 325
454 // CreateFileStreamForDrop() will call base::PathExists(), 326 // CreateFileStreamForDrop() will call base::PathExists(),
455 // which is blocking. Since this operation is already blocking the 327 // which is blocking. Since this operation is already blocking the
456 // UI thread on OSX, it should be reasonable to let it happen. 328 // UI thread on OSX, it should be reasonable to let it happen.
457 base::ThreadRestrictions::ScopedAllowIO allowIO; 329 base::ThreadRestrictions::ScopedAllowIO allowIO;
458 scoped_ptr<FileStream> fileStream(content::CreateFileStreamForDrop( 330 scoped_ptr<FileStream> fileStream(content::CreateFileStreamForDrop(
459 &filePath, content::GetContentClient()->browser()->GetNetLog())); 331 &filePath, content::GetContentClient()->browser()->GetNetLog()));
460 if (!fileStream) 332 if (!fileStream)
461 return; 333 return nil;
462 334
463 if (downloadURL_.is_valid()) { 335 if (downloadURL_.is_valid()) {
464 scoped_refptr<DragDownloadFile> dragFileDownloader(new DragDownloadFile( 336 scoped_refptr<DragDownloadFile> dragFileDownloader(new DragDownloadFile(
465 filePath, 337 filePath,
466 fileStream.Pass(), 338 fileStream.Pass(),
467 downloadURL_, 339 downloadURL_,
468 content::Referrer(contents_->GetLastCommittedURL(), 340 content::Referrer(contents_->GetLastCommittedURL(),
469 dropData_->referrer_policy), 341 dropData_->referrer_policy),
470 contents_->GetEncoding(), 342 contents_->GetEncoding(),
471 contents_)); 343 contents_));
472 344
473 // The finalizer will take care of closing and deletion. 345 // The finalizer will take care of closing and deletion.
474 dragFileDownloader->Start(new PromiseFileFinalizer( 346 dragFileDownloader->Start(new PromiseFileFinalizer(
475 dragFileDownloader.get())); 347 dragFileDownloader.get()));
476 } else { 348 } else {
477 // The writer will take care of closing and deletion. 349 // The writer will take care of closing and deletion.
478 BrowserThread::PostTask(BrowserThread::FILE, 350 BrowserThread::PostTask(BrowserThread::FILE,
479 FROM_HERE, 351 FROM_HERE,
480 base::Bind(&PromiseWriterHelper, 352 base::Bind(&PromiseWriterHelper,
481 *dropData_, 353 *dropData_,
482 base::Passed(&fileStream))); 354 base::Passed(&fileStream)));
483 } 355 }
484 SelectFileInFinder(filePath); 356
357 // The DragDownloadFile constructor may have altered the value of |filePath|
358 // if, say, an existing file at the drop site has the same name. Return the
359 // actual name that was used to write the file.
360 return SysUTF8ToNSString(filePath.BaseName().value());
485 } 361 }
486 362
363 @end // @implementation WebDragSource
364
365 @implementation WebDragSource (Private)
366
487 - (void)fillPasteboard { 367 - (void)fillPasteboard {
488 DCHECK(pasteboard_.get()); 368 DCHECK(pasteboard_.get());
489 369
490 [pasteboard_ declareTypes:@[ui::kChromeDragDummyPboardType] 370 [pasteboard_ declareTypes:@[ ui::kChromeDragDummyPboardType ]
491 owner:contentsView_]; 371 owner:contentsView_];
492 372
493 // URL (and title). 373 // URL (and title).
494 if (dropData_->url.is_valid()) { 374 if (dropData_->url.is_valid()) {
495 [pasteboard_ addTypes:@[NSURLPboardType, kNSURLTitlePboardType] 375 [pasteboard_ addTypes:@[ NSURLPboardType, kNSURLTitlePboardType ]
496 owner:contentsView_]; 376 owner:contentsView_];
497 } 377 }
498 378
499 // MIME type. 379 // MIME type.
500 std::string mimeType; 380 std::string mimeType;
501 381
502 // File. 382 // File.
503 if (!dropData_->file_contents.empty() || 383 if (!dropData_->file_contents.empty() ||
504 !dropData_->download_metadata.empty()) { 384 !dropData_->download_metadata.empty()) {
505 if (dropData_->download_metadata.empty()) { 385 if (dropData_->download_metadata.empty()) {
(...skipping 21 matching lines...) Expand all
527 defaultName); 407 defaultName);
528 } 408 }
529 } 409 }
530 410
531 if (!mimeType.empty()) { 411 if (!mimeType.empty()) {
532 base::ScopedCFTypeRef<CFStringRef> mimeTypeCF( 412 base::ScopedCFTypeRef<CFStringRef> mimeTypeCF(
533 base::SysUTF8ToCFStringRef(mimeType)); 413 base::SysUTF8ToCFStringRef(mimeType));
534 fileUTI_.reset(UTTypeCreatePreferredIdentifierForTag( 414 fileUTI_.reset(UTTypeCreatePreferredIdentifierForTag(
535 kUTTagClassMIMEType, mimeTypeCF.get(), NULL)); 415 kUTTagClassMIMEType, mimeTypeCF.get(), NULL));
536 416
537 NSArray* types = 417 // File (HFS) promise.
538 @[base::mac::CFToNSCast(kPasteboardTypeFileURLPromise), 418 // There are two ways to drag/drop files. NSFilesPromisePboardType is the
539 base::mac::CFToNSCast(kPasteboardTypeFilePromiseContent)]; 419 // deprecated way, and kPasteboardTypeFilePromiseContent is the way that
540 [pasteboard_ addTypes:types owner:contentsView_]; 420 // does not work. kPasteboardTypeFilePromiseContent is thoroughly broken:
421 // * API: There is no good way to get the location for the drop.
422 // <http://lists.apple.com/archives/cocoa-dev/2012/Feb/msg00706.html>
423 // * Behavior: A file dropped in the Finder is not selected. This can be
424 // worked around by selecting the file in the Finder using AppleEvents,
425 // but the drop target window will come to the front of the Finder's
426 // window list (unlike the previous behavior). <http://crbug.com/278515>
427 // * Behavior: Files dragged over app icons in the dock do not highlight
428 // the dock icons, and the dock icons do not accept the drop.
429 // <http://crbug.com/282916>
430 // * Behavior: A file dropped onto the desktop is positioned at the upper
431 // right of the desktop rather than at the position at which it was
432 // dropped. <http://crbug.com/284942>
433 NSArray* fileUTIList = @[ base::mac::CFToNSCast(fileUTI_.get()) ];
434 [pasteboard_ addTypes:@[ NSFilesPromisePboardType ] owner:contentsView_];
435 [pasteboard_ setPropertyList:fileUTIList
436 forType:NSFilesPromisePboardType];
541 437
542 [pasteboard_ setPropertyList:@[base::mac::CFToNSCast(fileUTI_.get())] 438 if (!dropData_->file_contents.empty())
543 forType:base::mac::CFToNSCast(kPasteboardTypeFilePromiseContent)]; 439 [pasteboard_ addTypes:fileUTIList owner:contentsView_];
544
545 if (!dropData_->file_contents.empty()) {
546 NSArray* types = @[base::mac::CFToNSCast(fileUTI_.get())];
547 [pasteboard_ addTypes:types owner:contentsView_];
548 }
549 } 440 }
550 } 441 }
551 442
552 // HTML. 443 // HTML.
553 bool hasHTMLData = !dropData_->html.string().empty(); 444 bool hasHTMLData = !dropData_->html.string().empty();
554 // Mail.app and TextEdit accept drags that have both HTML and image flavors on 445 // Mail.app and TextEdit accept drags that have both HTML and image flavors on
555 // them, but don't process them correctly <http://crbug.com/55879>. Therefore, 446 // them, but don't process them correctly <http://crbug.com/55879>. Therefore,
556 // if there is an image flavor, don't put the HTML data on as HTML, but rather 447 // if there is an image flavor, don't put the HTML data on as HTML, but rather
557 // put it on as this Chrome-only flavor. 448 // put it on as this Chrome-only flavor.
558 // 449 //
559 // (The only time that Blink fills in the DropData::file_contents is with 450 // (The only time that Blink fills in the DropData::file_contents is with
560 // an image drop, but the MIME time is tested anyway for paranoia's sake.) 451 // an image drop, but the MIME time is tested anyway for paranoia's sake.)
561 bool hasImageData = !dropData_->file_contents.empty() && 452 bool hasImageData = !dropData_->file_contents.empty() &&
562 fileUTI_ && 453 fileUTI_ &&
563 UTTypeConformsTo(fileUTI_.get(), kUTTypeImage); 454 UTTypeConformsTo(fileUTI_.get(), kUTTypeImage);
564 if (hasHTMLData) { 455 if (hasHTMLData) {
565 if (hasImageData) { 456 if (hasImageData) {
566 [pasteboard_ addTypes:@[ui::kChromeDragImageHTMLPboardType] 457 [pasteboard_ addTypes:@[ ui::kChromeDragImageHTMLPboardType ]
567 owner:contentsView_]; 458 owner:contentsView_];
568 } else { 459 } else {
569 [pasteboard_ addTypes:@[NSHTMLPboardType] owner:contentsView_]; 460 [pasteboard_ addTypes:@[ NSHTMLPboardType ] owner:contentsView_];
570 } 461 }
571 } 462 }
572 463
573 // Plain text. 464 // Plain text.
574 if (!dropData_->text.string().empty()) { 465 if (!dropData_->text.string().empty()) {
575 [pasteboard_ addTypes:@[NSStringPboardType] 466 [pasteboard_ addTypes:@[ NSStringPboardType ]
576 owner:contentsView_]; 467 owner:contentsView_];
577 } 468 }
578 469
579 if (!dropData_->custom_data.empty()) { 470 if (!dropData_->custom_data.empty()) {
580 [pasteboard_ addTypes:@[ui::kWebCustomDataPboardType] 471 [pasteboard_ addTypes:@[ ui::kWebCustomDataPboardType ]
581 owner:contentsView_]; 472 owner:contentsView_];
582 } 473 }
583 } 474 }
584 475
585 - (NSImage*)dragImage { 476 - (NSImage*)dragImage {
586 if (dragImage_) 477 if (dragImage_)
587 return dragImage_; 478 return dragImage_;
588 479
589 // Default to returning a generic image. 480 // Default to returning a generic image.
590 return content::GetContentClient()->GetNativeImageNamed( 481 return content::GetContentClient()->GetNativeImageNamed(
591 IDR_DEFAULT_FAVICON).ToNSImage(); 482 IDR_DEFAULT_FAVICON).ToNSImage();
592 } 483 }
593 484
594 @end // @implementation WebDragSource (Private) 485 @end // @implementation WebDragSource (Private)
OLDNEW
« no previous file with comments | « content/browser/web_contents/web_drag_source_mac.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698