Add DataTransferItems support for drag-and-drop'ed files and texts
[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 "ClipboardMimeTypes.h"
33 #include "ClipboardUtilitiesChromium.h"
34 #include "DataTransferItemChromium.h"
35 #include "DataTransferItemListChromium.h"
36 #include "Document.h"
37 #include "DragData.h"
38 #include "Element.h"
39 #include "File.h"
40 #include "FileList.h"
41 #include "Frame.h"
42 #include "HTMLNames.h"
43 #include "HTMLParserIdioms.h"
44 #include "Image.h"
45 #include "MIMETypeRegistry.h"
46 #include "NamedNodeMap.h"
47 #include "PlatformSupport.h"
48 #include "Range.h"
49 #include "RenderImage.h"
50 #include "ScriptExecutionContext.h"
51 #include "markup.h"
52
53 #include <wtf/text/WTFString.h>
54
55 namespace WebCore {
56
57 using namespace HTMLNames;
58
59 // We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft
60 // see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
61
62 static String normalizeType(const String& type)
63 {
64     String cleanType = type.stripWhiteSpace().lower();
65     if (cleanType == mimeTypeText || cleanType.startsWith(mimeTypeTextPlainEtc))
66         return mimeTypeTextPlain;
67     return cleanType;
68 }
69
70 PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData* dragData, Frame* frame)
71 {
72     return ClipboardChromium::create(DragAndDrop, dragData->platformData(), policy, frame);
73 }
74
75 ClipboardChromium::ClipboardChromium(ClipboardType clipboardType,
76                                      PassRefPtr<ChromiumDataObject> dataObject,
77                                      ClipboardAccessPolicy policy,
78                                      Frame* frame)
79     : Clipboard(policy, clipboardType)
80     , m_dataObject(dataObject)
81     , m_frame(frame)
82     , m_originalSequenceNumber(PlatformSupport::clipboardSequenceNumber(currentPasteboardBuffer()))
83     , m_dragStorageUpdated(true)
84 {
85 }
86
87 ClipboardChromium::~ClipboardChromium()
88 {
89 }
90
91 PassRefPtr<ClipboardChromium> ClipboardChromium::create(ClipboardType clipboardType,
92     PassRefPtr<ChromiumDataObject> dataObject, ClipboardAccessPolicy policy, Frame* frame)
93 {
94     return adoptRef(new ClipboardChromium(clipboardType, dataObject, policy, frame));
95 }
96
97 void ClipboardChromium::clearData(const String& type)
98 {
99     if (policy() != ClipboardWritable || !m_dataObject)
100         return;
101
102     m_dragStorageUpdated = true;
103     m_dataObject->clearData(normalizeType(type));
104
105     ASSERT_NOT_REACHED();
106 }
107
108 void ClipboardChromium::clearAllData()
109 {
110     if (policy() != ClipboardWritable)
111         return;
112
113     m_dragStorageUpdated = true;
114     m_dataObject->clearAll();
115 }
116
117 String ClipboardChromium::getData(const String& type, bool& success) const
118 {
119     success = false;
120     if (policy() != ClipboardReadable || !m_dataObject)
121         return String();
122
123     if (isForCopyAndPaste() && platformClipboardChanged())
124         return String();
125
126     return m_dataObject->getData(normalizeType(type), success);
127 }
128
129 bool ClipboardChromium::setData(const String& type, const String& data)
130 {
131     if (policy() != ClipboardWritable)
132         return false;
133
134     m_dragStorageUpdated = true;
135     return m_dataObject->setData(normalizeType(type), data);
136 }
137
138 bool ClipboardChromium::platformClipboardChanged() const
139 {
140     return PlatformSupport::clipboardSequenceNumber(currentPasteboardBuffer()) != m_originalSequenceNumber;
141 }
142
143 // extensions beyond IE's API
144 HashSet<String> ClipboardChromium::types() const
145 {
146     HashSet<String> results;
147     if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
148         return results;
149
150     if (!m_dataObject)
151         return results;
152
153     results = m_dataObject->types();
154
155     if (m_dataObject->containsFilenames())
156         results.add(mimeTypeFiles);
157
158     return results;
159 }
160
161 PassRefPtr<FileList> ClipboardChromium::files() const
162 {
163     if (policy() != ClipboardReadable)
164         return FileList::create();
165
166     if (!m_dataObject)
167         return FileList::create();
168
169     const Vector<String>& filenames = m_dataObject->filenames();
170     RefPtr<FileList> fileList = FileList::create();
171     for (size_t i = 0; i < filenames.size(); ++i)
172         fileList->append(File::create(filenames.at(i)));
173
174     return fileList.release();
175 }
176
177 void ClipboardChromium::setDragImage(CachedImage* image, Node* node, const IntPoint& loc)
178 {
179     if (policy() != ClipboardImageWritable && policy() != ClipboardWritable)
180         return;
181
182     if (m_dragImage)
183         m_dragImage->removeClient(this);
184     m_dragImage = image;
185     if (m_dragImage)
186         m_dragImage->addClient(this);
187
188     m_dragLoc = loc;
189     m_dragImageElement = node;
190 }
191
192 void ClipboardChromium::setDragImage(CachedImage* img, const IntPoint& loc)
193 {
194     setDragImage(img, 0, loc);
195 }
196
197 void ClipboardChromium::setDragImageElement(Node* node, const IntPoint& loc)
198 {
199     setDragImage(0, node, loc);
200 }
201
202 DragImageRef ClipboardChromium::createDragImage(IntPoint& loc) const
203 {
204     DragImageRef result = 0;
205     if (m_dragImageElement) {
206         if (m_frame) {
207             result = m_frame->nodeImage(m_dragImageElement.get());
208             loc = m_dragLoc;
209         }
210     } else if (m_dragImage) {
211         result = createDragImageFromImage(m_dragImage->image());
212         loc = m_dragLoc;
213     }
214     return result;
215 }
216
217 static CachedImage* getCachedImage(Element* element)
218 {
219     // Attempt to pull CachedImage from element
220     ASSERT(element);
221     RenderObject* renderer = element->renderer();
222     if (!renderer || !renderer->isImage())
223         return 0;
224
225     RenderImage* image = toRenderImage(renderer);
226     if (image->cachedImage() && !image->cachedImage()->errorOccurred())
227         return image->cachedImage();
228
229     return 0;
230 }
231
232 static void writeImageToDataObject(ChromiumDataObject* dataObject, Element* element,
233                                    const KURL& url)
234 {
235     // Shove image data into a DataObject for use as a file
236     CachedImage* cachedImage = getCachedImage(element);
237     if (!cachedImage || !cachedImage->imageForRenderer(element->renderer()) || !cachedImage->isLoaded())
238         return;
239
240     SharedBuffer* imageBuffer = cachedImage->imageForRenderer(element->renderer())->data();
241     if (!imageBuffer || !imageBuffer->size())
242         return;
243
244     dataObject->setFileContent(imageBuffer);
245
246     // Determine the filename for the file contents of the image.
247     String filename = cachedImage->response().suggestedFilename();
248     if (filename.isEmpty())
249         filename = url.lastPathComponent();
250     if (filename.isEmpty())
251         filename = element->getAttribute(altAttr);
252     else {
253         // Strip any existing extension. Assume that alt text is usually not a filename.
254         int extensionIndex = filename.reverseFind('.');
255         if (extensionIndex != -1)
256             filename.truncate(extensionIndex);
257     }
258     filename = ClipboardChromium::validateFileName(filename, dataObject);
259
260     String extension = MIMETypeRegistry::getPreferredExtensionForMIMEType(
261         cachedImage->response().mimeType());
262     dataObject->setFileExtension(extension.isEmpty() ? emptyString() : "." + extension);
263
264     dataObject->setFileContentFilename(filename + dataObject->fileExtension());
265 }
266
267 void ClipboardChromium::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
268 {
269     if (!m_dataObject)
270         return;
271
272     m_dragStorageUpdated = true;
273     m_dataObject->setData(mimeTypeURL, url);
274     m_dataObject->setUrlTitle(title);
275
276     // Write the bytes in the image to the file format.
277     writeImageToDataObject(m_dataObject.get(), element, url);
278
279     // Put img tag on the clipboard referencing the image
280     m_dataObject->setData(mimeTypeTextHTML, createMarkup(element, IncludeNode, 0, ResolveAllURLs));
281 }
282
283 void ClipboardChromium::writeURL(const KURL& url, const String& title, Frame*)
284 {
285     if (!m_dataObject)
286         return;
287     ASSERT(!url.isEmpty());
288
289     m_dragStorageUpdated = true;
290     m_dataObject->setData(mimeTypeURL, url);
291     m_dataObject->setUrlTitle(title);
292
293     // The URL can also be used as plain text.
294     m_dataObject->setData(mimeTypeTextPlain, url.string());
295
296     // The URL can also be used as an HTML fragment.
297     m_dataObject->setData(mimeTypeTextHTML, urlToMarkup(url, title));
298     m_dataObject->setHtmlBaseUrl(url);
299 }
300
301 void ClipboardChromium::writeRange(Range* selectedRange, Frame* frame)
302 {
303     ASSERT(selectedRange);
304     if (!m_dataObject)
305          return;
306
307     m_dragStorageUpdated = true;
308     m_dataObject->setData(mimeTypeTextHTML, createMarkup(selectedRange, 0, AnnotateForInterchange, false, ResolveNonLocalURLs));
309     m_dataObject->setHtmlBaseUrl(frame->document()->url());
310
311     String str = frame->editor()->selectedText();
312 #if OS(WINDOWS)
313     replaceNewlinesWithWindowsStyleNewlines(str);
314 #endif
315     replaceNBSPWithSpace(str);
316     m_dataObject->setData(mimeTypeTextPlain, str);
317 }
318
319 void ClipboardChromium::writePlainText(const String& text)
320 {
321     if (!m_dataObject)
322         return;
323
324     String str = text;
325 #if OS(WINDOWS)
326     replaceNewlinesWithWindowsStyleNewlines(str);
327 #endif
328     replaceNBSPWithSpace(str);
329
330     m_dragStorageUpdated = true;
331     m_dataObject->setData(mimeTypeTextPlain, str);
332 }
333
334 bool ClipboardChromium::hasData()
335 {
336     ASSERT(isForDragAndDrop());
337     if (!m_dataObject)
338         return false;
339
340     return m_dataObject->hasData();
341 }
342
343 #if ENABLE(DATA_TRANSFER_ITEMS)
344 PassRefPtr<DataTransferItemList> ClipboardChromium::items()
345 {
346     if (!m_dataObject || (policy() != ClipboardReadable && policy() != ClipboardWritable)) {
347         // Return an unassociated empty list.
348         return DataTransferItemListChromium::create(this, m_frame->document()->scriptExecutionContext());
349     }
350
351     if (!m_itemList)
352         m_itemList = DataTransferItemListChromium::create(this, m_frame->document()->scriptExecutionContext());
353
354     return m_itemList;
355 }
356
357 // FIXME: integrate ChromiumDataObject and DataTransferItemList rather than holding them separately and keeping them synced.
358 void ClipboardChromium::mayUpdateItems(Vector<RefPtr<DataTransferItem> >& items)
359 {
360     if (!items.isEmpty() && !storageHasUpdated())
361         return;
362
363     items.clear();
364
365     ScriptExecutionContext* scriptExecutionContext = m_frame->document()->scriptExecutionContext();
366
367     if (isForCopyAndPaste() && policy() == ClipboardReadable) {
368         // Iterate through the types and add them.
369         HashSet<String> types = m_dataObject->types();
370         for (HashSet<String>::const_iterator it = types.begin(); it != types.end(); ++it)
371             items.append(DataTransferItemChromium::createFromPasteboard(this, scriptExecutionContext, *it));
372         return;
373     }
374
375     bool success = false;
376     String plainText = m_dataObject->getData(mimeTypeTextPlain, success);
377     if (success)
378         items.append(DataTransferItem::create(this, scriptExecutionContext, plainText, mimeTypeTextPlain));
379
380     success = false;
381     String htmlText = m_dataObject->getData(mimeTypeTextHTML, success);
382     if (success)
383         items.append(DataTransferItem::create(this, scriptExecutionContext, htmlText, mimeTypeTextHTML));
384
385     if (m_dataObject->containsFilenames()) {
386         const Vector<String>& filenames = m_dataObject->filenames();
387         for (Vector<String>::const_iterator it = filenames.begin(); it != filenames.end(); ++it)
388             items.append(DataTransferItem::create(this, scriptExecutionContext, File::create(*it)));
389     }
390     m_dragStorageUpdated = false;
391 }
392
393 bool ClipboardChromium::storageHasUpdated() const
394 {
395     return (isForCopyAndPaste() && platformClipboardChanged()) || (isForDragAndDrop() && m_dragStorageUpdated);
396 }
397
398 #endif
399
400 } // namespace WebCore