Dragging a .jpg to Finder saves it as .jpeg
[WebKit-https.git] / Source / WebCore / platform / chromium / ClipboardChromium.cpp
1 /*
2  * Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
3  * Copyright (C) 2008, 2009 Google Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "ClipboardChromium.h"
29
30 #include "CachedImage.h"
31 #include "ChromiumDataObject.h"
32 #include "ChromiumDataObjectItem.h"
33 #include "ClipboardMimeTypes.h"
34 #include "ClipboardUtilitiesChromium.h"
35 #include "DataTransferItemList.h"
36 #include "Document.h"
37 #include "DragData.h"
38 #include "Element.h"
39 #include "ExceptionCode.h"
40 #include "File.h"
41 #include "FileList.h"
42 #include "Frame.h"
43 #include "HTMLNames.h"
44 #include "HTMLParserIdioms.h"
45 #include "Image.h"
46 #include "MIMETypeRegistry.h"
47 #include "NamedNodeMap.h"
48 #include "Range.h"
49 #include "RenderImage.h"
50 #include "StringCallback.h"
51 #include "markup.h"
52
53 #include <wtf/text/WTFString.h>
54
55 namespace WebCore {
56
57 namespace {
58
59 // A wrapper class that invalidates a DataTransferItemList when the associated Clipboard object goes out of scope.
60 class DataTransferItemListPolicyWrapper : public DataTransferItemList {
61 public:
62     static PassRefPtr<DataTransferItemListPolicyWrapper> create(PassRefPtr<ClipboardChromium>, PassRefPtr<ChromiumDataObject>);
63     virtual ~DataTransferItemListPolicyWrapper();
64
65     virtual size_t length() const;
66     virtual PassRefPtr<DataTransferItem> item(unsigned long index) OVERRIDE;
67     virtual void deleteItem(unsigned long index, ExceptionCode&) OVERRIDE;
68     virtual void clear() OVERRIDE;
69     virtual void add(const String& data, const String& type, ExceptionCode&) OVERRIDE;
70     virtual void add(PassRefPtr<File>) OVERRIDE;
71
72 private:
73     DataTransferItemListPolicyWrapper(PassRefPtr<ClipboardChromium>, PassRefPtr<ChromiumDataObject>);
74
75     RefPtr<ClipboardChromium> m_clipboard;
76     RefPtr<ChromiumDataObject> m_dataObject;
77 };
78
79
80 PassRefPtr<DataTransferItemListPolicyWrapper> DataTransferItemListPolicyWrapper::create(
81     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<ChromiumDataObject> list)
82 {
83     return adoptRef(new DataTransferItemListPolicyWrapper(clipboard, list));
84 }
85
86 DataTransferItemListPolicyWrapper::~DataTransferItemListPolicyWrapper()
87 {
88 }
89
90 size_t DataTransferItemListPolicyWrapper::length() const
91 {
92     if (m_clipboard->policy() == ClipboardNumb)
93         return 0;
94     return m_dataObject->length();
95 }
96
97 PassRefPtr<DataTransferItem> DataTransferItemListPolicyWrapper::item(unsigned long index)
98 {
99     if (m_clipboard->policy() == ClipboardNumb)
100         return 0;
101     RefPtr<ChromiumDataObjectItem> item = m_dataObject->item(index);
102     if (!item)
103         return 0;
104
105     return DataTransferItemPolicyWrapper::create(m_clipboard, item);
106 }
107
108 void DataTransferItemListPolicyWrapper::deleteItem(unsigned long index, ExceptionCode& ec)
109 {
110     if (m_clipboard->policy() != ClipboardWritable) {
111         ec = INVALID_STATE_ERR;
112         return;
113     }
114     m_dataObject->deleteItem(index);
115 }
116
117 void DataTransferItemListPolicyWrapper::clear()
118 {
119     if (m_clipboard->policy() != ClipboardWritable)
120         return;
121     m_dataObject->clearAll();
122 }
123
124 void DataTransferItemListPolicyWrapper::add(const String& data, const String& type, ExceptionCode& ec)
125 {
126     if (m_clipboard->policy() != ClipboardWritable)
127         return;
128     m_dataObject->add(data, type, ec);
129 }
130
131 void DataTransferItemListPolicyWrapper::add(PassRefPtr<File> file)
132 {
133     if (m_clipboard->policy() != ClipboardWritable)
134         return;
135     m_dataObject->add(file, m_clipboard->frame()->document()->scriptExecutionContext());
136 }
137
138 DataTransferItemListPolicyWrapper::DataTransferItemListPolicyWrapper(
139     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<ChromiumDataObject> dataObject)
140     : m_clipboard(clipboard)
141     , m_dataObject(dataObject)
142 {
143 }
144
145 } // namespace
146
147 PassRefPtr<DataTransferItemPolicyWrapper> DataTransferItemPolicyWrapper::create(
148     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<ChromiumDataObjectItem> item)
149 {
150     return adoptRef(new DataTransferItemPolicyWrapper(clipboard, item));
151 }
152
153 DataTransferItemPolicyWrapper::~DataTransferItemPolicyWrapper()
154 {
155 }
156
157 String DataTransferItemPolicyWrapper::kind() const
158 {
159     if (m_clipboard->policy() == ClipboardNumb)
160         return String();
161     return m_item->kind();
162 }
163
164 String DataTransferItemPolicyWrapper::type() const
165 {
166     if (m_clipboard->policy() == ClipboardNumb)
167         return String();
168     return m_item->type();
169 }
170
171 void DataTransferItemPolicyWrapper::getAsString(PassRefPtr<StringCallback> callback) const
172 {
173     if (m_clipboard->policy() != ClipboardReadable && m_clipboard->policy() != ClipboardWritable)
174         return;
175
176     m_item->getAsString(callback, m_clipboard->frame()->document()->scriptExecutionContext());
177 }
178
179 PassRefPtr<Blob> DataTransferItemPolicyWrapper::getAsFile() const
180 {
181     if (m_clipboard->policy() != ClipboardReadable && m_clipboard->policy() != ClipboardWritable)
182         return 0;
183
184     return m_item->getAsFile();
185 }
186
187 DataTransferItemPolicyWrapper::DataTransferItemPolicyWrapper(
188     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<ChromiumDataObjectItem> item)
189     : m_clipboard(clipboard)
190     , m_item(item)
191 {
192 }
193
194 using namespace HTMLNames;
195
196 // We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft
197 // see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
198
199 static String normalizeType(const String& type, bool* convertToURL = 0)
200 {
201     String cleanType = type.stripWhiteSpace().lower();
202     if (cleanType == mimeTypeText || cleanType.startsWith(mimeTypeTextPlainEtc))
203         return mimeTypeTextPlain;
204     if (cleanType == mimeTypeURL) {
205         if (convertToURL)
206           *convertToURL = true;
207         return mimeTypeTextURIList;
208     }
209     return cleanType;
210 }
211
212 PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData* dragData, Frame* frame)
213 {
214     return ClipboardChromium::create(DragAndDrop, dragData->platformData(), policy, frame);
215 }
216
217 ClipboardChromium::ClipboardChromium(ClipboardType clipboardType,
218                                      PassRefPtr<ChromiumDataObject> dataObject,
219                                      ClipboardAccessPolicy policy,
220                                      Frame* frame)
221     : Clipboard(policy, clipboardType)
222     , m_dataObject(dataObject)
223     , m_frame(frame)
224 {
225 }
226
227 ClipboardChromium::~ClipboardChromium()
228 {
229     if (m_dragImage)
230         m_dragImage->removeClient(this);
231 }
232
233 PassRefPtr<ClipboardChromium> ClipboardChromium::create(ClipboardType clipboardType,
234     PassRefPtr<ChromiumDataObject> dataObject, ClipboardAccessPolicy policy, Frame* frame)
235 {
236     return adoptRef(new ClipboardChromium(clipboardType, dataObject, policy, frame));
237 }
238
239 void ClipboardChromium::clearData(const String& type)
240 {
241     if (policy() != ClipboardWritable)
242         return;
243
244     m_dataObject->clearData(normalizeType(type));
245
246     ASSERT_NOT_REACHED();
247 }
248
249 void ClipboardChromium::clearAllData()
250 {
251     if (policy() != ClipboardWritable)
252         return;
253
254     m_dataObject->clearAll();
255 }
256
257 String ClipboardChromium::getData(const String& type) const
258 {
259     if (policy() != ClipboardReadable)
260         return String();
261
262     bool convertToURL = false;
263     String data = m_dataObject->getData(normalizeType(type, &convertToURL));
264     if (!convertToURL)
265         return data;
266     return convertURIListToURL(data);
267 }
268
269 bool ClipboardChromium::setData(const String& type, const String& data)
270 {
271     if (policy() != ClipboardWritable)
272         return false;
273
274     return m_dataObject->setData(normalizeType(type), data);
275 }
276
277 // extensions beyond IE's API
278 ListHashSet<String> ClipboardChromium::types() const
279 {
280     if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
281         return ListHashSet<String>();
282
283     return m_dataObject->types();
284 }
285
286 PassRefPtr<FileList> ClipboardChromium::files() const
287 {
288     RefPtr<FileList> files = FileList::create();
289     if (policy() != ClipboardReadable)
290         return files.release();
291
292     for (size_t i = 0; i < m_dataObject->length(); ++i) {
293         if (m_dataObject->item(i)->kind() == DataTransferItem::kindFile) {
294             RefPtr<Blob> blob = m_dataObject->item(i)->getAsFile();
295             if (blob && blob->isFile())
296                 files->append(toFile(blob.get()));
297         }
298     }
299
300     return files.release();
301 }
302
303 void ClipboardChromium::setDragImage(CachedImage* image, Node* node, const IntPoint& loc)
304 {
305     if (policy() != ClipboardImageWritable && policy() != ClipboardWritable)
306         return;
307
308     if (m_dragImage)
309         m_dragImage->removeClient(this);
310     m_dragImage = image;
311     if (m_dragImage)
312         m_dragImage->addClient(this);
313
314     m_dragLoc = loc;
315     m_dragImageElement = node;
316 }
317
318 void ClipboardChromium::setDragImage(CachedImage* img, const IntPoint& loc)
319 {
320     setDragImage(img, 0, loc);
321 }
322
323 void ClipboardChromium::setDragImageElement(Node* node, const IntPoint& loc)
324 {
325     setDragImage(0, node, loc);
326 }
327
328 DragImageRef ClipboardChromium::createDragImage(IntPoint& loc) const
329 {
330     DragImageRef result = 0;
331     if (m_dragImageElement) {
332         if (m_frame) {
333             result = m_frame->nodeImage(m_dragImageElement.get());
334             loc = m_dragLoc;
335         }
336     } else if (m_dragImage) {
337         result = createDragImageFromImage(m_dragImage->image());
338         loc = m_dragLoc;
339     }
340     return result;
341 }
342
343 static CachedImage* getCachedImage(Element* element)
344 {
345     // Attempt to pull CachedImage from element
346     ASSERT(element);
347     RenderObject* renderer = element->renderer();
348     if (!renderer || !renderer->isImage())
349         return 0;
350
351     RenderImage* image = toRenderImage(renderer);
352     if (image->cachedImage() && !image->cachedImage()->errorOccurred())
353         return image->cachedImage();
354
355     return 0;
356 }
357
358 static void writeImageToDataObject(ChromiumDataObject* dataObject, Element* element,
359                                    const KURL& url)
360 {
361     // Shove image data into a DataObject for use as a file
362     CachedImage* cachedImage = getCachedImage(element);
363     if (!cachedImage || !cachedImage->imageForRenderer(element->renderer()) || !cachedImage->isLoaded())
364         return;
365
366     SharedBuffer* imageBuffer = cachedImage->imageForRenderer(element->renderer())->data();
367     if (!imageBuffer || !imageBuffer->size())
368         return;
369
370     // Determine the filename for the file contents of the image.
371     String filename = cachedImage->response().suggestedFilename();
372     String extension;
373     if (filename.isEmpty())
374         filename = url.lastPathComponent();
375     if (filename.isEmpty())
376         filename = element->getAttribute(altAttr);
377     else {
378         // Strip any existing extension. Assume that alt text is usually not a filename.
379         int extensionIndex = filename.reverseFind('.');
380         if (extensionIndex != -1) {
381             extension = filename.substring(extensionIndex + 1);
382             filename.truncate(extensionIndex);
383         }
384     }
385
386     String extensionMimeType = MIMETypeRegistry::getMIMETypeForExtension(extension);
387     if (extensionMimeType != cachedImage->response().mimeType()) {
388         extension = MIMETypeRegistry::getPreferredExtensionForMIMEType(
389             cachedImage->response().mimeType());
390     }
391
392     extension = extension.isEmpty() ? emptyString() : "." + extension;
393     ClipboardChromium::validateFilename(filename, extension);
394
395     dataObject->addSharedBuffer(filename + extension, imageBuffer);
396 }
397
398 void ClipboardChromium::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
399 {
400     if (!m_dataObject)
401         return;
402
403     m_dataObject->setURLAndTitle(url, title);
404
405     // Write the bytes in the image to the file format.
406     writeImageToDataObject(m_dataObject.get(), element, url);
407
408     // Put img tag on the clipboard referencing the image
409     m_dataObject->setData(mimeTypeTextHTML, createMarkup(element, IncludeNode, 0, ResolveAllURLs));
410 }
411
412 void ClipboardChromium::writeURL(const KURL& url, const String& title, Frame*)
413 {
414     if (!m_dataObject)
415         return;
416     ASSERT(!url.isEmpty());
417
418     m_dataObject->setURLAndTitle(url, title);
419
420     // The URL can also be used as plain text.
421     m_dataObject->setData(mimeTypeTextPlain, url.string());
422
423     // The URL can also be used as an HTML fragment.
424     m_dataObject->setHTMLAndBaseURL(urlToMarkup(url, title), url);
425 }
426
427 void ClipboardChromium::writeRange(Range* selectedRange, Frame* frame)
428 {
429     ASSERT(selectedRange);
430     if (!m_dataObject)
431          return;
432
433     m_dataObject->setHTMLAndBaseURL(createMarkup(selectedRange, 0, AnnotateForInterchange, false, ResolveNonLocalURLs), frame->document()->url());
434
435     String str = frame->editor()->selectedText();
436 #if OS(WINDOWS)
437     replaceNewlinesWithWindowsStyleNewlines(str);
438 #endif
439     replaceNBSPWithSpace(str);
440     m_dataObject->setData(mimeTypeTextPlain, str);
441 }
442
443 void ClipboardChromium::writePlainText(const String& text)
444 {
445     if (!m_dataObject)
446         return;
447
448     String str = text;
449 #if OS(WINDOWS)
450     replaceNewlinesWithWindowsStyleNewlines(str);
451 #endif
452     replaceNBSPWithSpace(str);
453
454     m_dataObject->setData(mimeTypeTextPlain, str);
455 }
456
457 bool ClipboardChromium::hasData()
458 {
459     ASSERT(isForDragAndDrop());
460
461     return m_dataObject->length() > 0;
462 }
463
464 #if ENABLE(DATA_TRANSFER_ITEMS)
465 PassRefPtr<DataTransferItemList> ClipboardChromium::items()
466 {
467     // FIXME: According to the spec, we are supposed to return the same collection of items each
468     // time. We now return a wrapper that always wraps the *same* set of items, so JS shouldn't be
469     // able to tell, but we probably still want to fix this.
470     return DataTransferItemListPolicyWrapper::create(this, m_dataObject);
471 }
472 #endif
473
474 } // namespace WebCore