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 |