Clipboard::getData should return an empty string instead of undefined
[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 "DOMStringList.h"
35 #include "DataTransferItemChromium.h"
36 #include "DataTransferItemListChromium.h"
37 #include "Document.h"
38 #include "DragData.h"
39 #include "Element.h"
40 #include "ExceptionCode.h"
41 #include "File.h"
42 #include "FileList.h"
43 #include "Frame.h"
44 #include "HTMLNames.h"
45 #include "HTMLParserIdioms.h"
46 #include "Image.h"
47 #include "MIMETypeRegistry.h"
48 #include "NamedNodeMap.h"
49 #include "PlatformSupport.h"
50 #include "Range.h"
51 #include "RenderImage.h"
52 #include "StringCallback.h"
53 #include "markup.h"
54
55 #include <wtf/text/WTFString.h>
56
57 namespace WebCore {
58
59 namespace {
60
61 // These wrapper classes invalidate a DataTransferItem/DataTransferItemList when the associated
62 // Clipboard object goes out of scope.
63 class DataTransferItemListPolicyWrapper : public DataTransferItemList {
64 public:
65     static PassRefPtr<DataTransferItemListPolicyWrapper> create(
66         PassRefPtr<ClipboardChromium>, PassRefPtr<DataTransferItemListChromium>);
67
68     virtual size_t length() const;
69     virtual PassRefPtr<DataTransferItem> item(unsigned long index);
70     virtual void deleteItem(unsigned long index, ExceptionCode&);
71     virtual void clear();
72     virtual void add(const String& data, const String& type, ExceptionCode&);
73     virtual void add(PassRefPtr<File>);
74
75 private:
76     DataTransferItemListPolicyWrapper(PassRefPtr<ClipboardChromium>, PassRefPtr<DataTransferItemListChromium>);
77
78     RefPtr<ClipboardChromium> m_clipboard;
79     RefPtr<DataTransferItemListChromium> m_list;
80 };
81
82 class DataTransferItemPolicyWrapper : public DataTransferItem {
83 public:
84     static PassRefPtr<DataTransferItemPolicyWrapper> create(
85         PassRefPtr<ClipboardChromium>, PassRefPtr<DataTransferItem>);
86
87     virtual String kind() const;
88     virtual String type() const;
89
90     virtual void getAsString(PassRefPtr<StringCallback>) const;
91     virtual PassRefPtr<Blob> getAsFile() const;
92
93 private:
94     DataTransferItemPolicyWrapper(PassRefPtr<ClipboardChromium>, PassRefPtr<DataTransferItem>);
95
96     RefPtr<ClipboardChromium> m_clipboard;
97     RefPtr<DataTransferItem> m_item;
98 };
99
100 PassRefPtr<DataTransferItemListPolicyWrapper> DataTransferItemListPolicyWrapper::create(
101     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<DataTransferItemListChromium> list)
102 {
103     return adoptRef(new DataTransferItemListPolicyWrapper(clipboard, list));
104 }
105
106 size_t DataTransferItemListPolicyWrapper::length() const
107 {
108     if (m_clipboard->policy() == ClipboardNumb)
109         return 0;
110     return m_list->length();
111 }
112
113 PassRefPtr<DataTransferItem> DataTransferItemListPolicyWrapper::item(unsigned long index)
114 {
115     if (m_clipboard->policy() == ClipboardNumb)
116         return 0;
117     RefPtr<DataTransferItem> item = m_list->item(index);
118     if (!item)
119         return 0;
120     return DataTransferItemPolicyWrapper::create(m_clipboard, item);
121 }
122
123 void DataTransferItemListPolicyWrapper::deleteItem(unsigned long index, ExceptionCode& ec)
124 {
125     if (m_clipboard->policy() != ClipboardWritable) {
126         ec = INVALID_STATE_ERR;
127         return;
128     }
129     // FIXME: We handle all the exceptions here, so we don't need to propogate ec.
130     m_list->deleteItem(index, ec);
131 }
132
133 void DataTransferItemListPolicyWrapper::clear()
134 {
135     if (m_clipboard->policy() != ClipboardWritable)
136         return;
137     m_list->clear();
138 }
139
140 void DataTransferItemListPolicyWrapper::add(const String& data, const String& type, ExceptionCode& ec)
141 {
142     if (m_clipboard->policy() != ClipboardWritable)
143         return;
144     m_list->add(data, type, ec);
145 }
146
147 void DataTransferItemListPolicyWrapper::add(PassRefPtr<File> file)
148 {
149     if (m_clipboard->policy() != ClipboardWritable)
150         return;
151     m_list->add(file);
152 }
153
154 DataTransferItemListPolicyWrapper::DataTransferItemListPolicyWrapper(
155     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<DataTransferItemListChromium> list)
156     : m_clipboard(clipboard)
157     , m_list(list)
158 {
159 }
160
161 PassRefPtr<DataTransferItemPolicyWrapper> DataTransferItemPolicyWrapper::create(
162     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<DataTransferItem> item)
163 {
164     return adoptRef(new DataTransferItemPolicyWrapper(clipboard, item));
165 }
166
167 String DataTransferItemPolicyWrapper::kind() const
168 {
169     if (m_clipboard->policy() == ClipboardNumb)
170         return String();
171     return m_item->kind();
172 }
173
174 String DataTransferItemPolicyWrapper::type() const
175 {
176     if (m_clipboard->policy() == ClipboardNumb)
177         return String();
178     return m_item->type();
179 }
180
181 void DataTransferItemPolicyWrapper::getAsString(PassRefPtr<StringCallback> callback) const
182 {
183     if (m_clipboard->policy() != ClipboardReadable && m_clipboard->policy() != ClipboardWritable)
184         return;
185
186     m_item->getAsString(callback);
187 }
188
189 PassRefPtr<Blob> DataTransferItemPolicyWrapper::getAsFile() const
190 {
191     if (m_clipboard->policy() != ClipboardReadable && m_clipboard->policy() != ClipboardWritable)
192         return 0;
193
194     return m_item->getAsFile();
195 }
196
197 DataTransferItemPolicyWrapper::DataTransferItemPolicyWrapper(
198     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<DataTransferItem> item)
199     : m_clipboard(clipboard)
200     , m_item(item)
201 {
202 }
203
204 } // namespace
205
206 using namespace HTMLNames;
207
208 // We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft
209 // see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
210
211 static String normalizeType(const String& type)
212 {
213     String cleanType = type.stripWhiteSpace().lower();
214     if (cleanType == mimeTypeText || cleanType.startsWith(mimeTypeTextPlainEtc))
215         return mimeTypeTextPlain;
216     return cleanType;
217 }
218
219 PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData* dragData, Frame* frame)
220 {
221     return ClipboardChromium::create(DragAndDrop, dragData->platformData(), policy, frame);
222 }
223
224 ClipboardChromium::ClipboardChromium(ClipboardType clipboardType,
225                                      PassRefPtr<ChromiumDataObject> dataObject,
226                                      ClipboardAccessPolicy policy,
227                                      Frame* frame)
228     : Clipboard(policy, clipboardType)
229     , m_dataObject(dataObject)
230     , m_frame(frame)
231     , m_originalSequenceNumber(PlatformSupport::clipboardSequenceNumber(currentPasteboardBuffer()))
232     , m_dragStorageUpdated(true)
233 {
234 }
235
236 ClipboardChromium::~ClipboardChromium()
237 {
238 }
239
240 PassRefPtr<ClipboardChromium> ClipboardChromium::create(ClipboardType clipboardType,
241     PassRefPtr<ChromiumDataObject> dataObject, ClipboardAccessPolicy policy, Frame* frame)
242 {
243     return adoptRef(new ClipboardChromium(clipboardType, dataObject, policy, frame));
244 }
245
246 void ClipboardChromium::clearData(const String& type)
247 {
248     if (policy() != ClipboardWritable || !m_dataObject)
249         return;
250
251     m_dragStorageUpdated = true;
252     m_dataObject->clearData(normalizeType(type));
253
254     ASSERT_NOT_REACHED();
255 }
256
257 void ClipboardChromium::clearAllData()
258 {
259     if (policy() != ClipboardWritable)
260         return;
261
262     m_dragStorageUpdated = true;
263     m_dataObject->clearAll();
264 }
265
266 String ClipboardChromium::getData(const String& type) const
267 {
268     bool ignoredSuccess = false;
269     if (policy() != ClipboardReadable || !m_dataObject)
270         return String();
271
272     if (isForCopyAndPaste() && platformClipboardChanged())
273         return String();
274
275     return m_dataObject->getData(normalizeType(type), ignoredSuccess);
276 }
277
278 bool ClipboardChromium::setData(const String& type, const String& data)
279 {
280     if (policy() != ClipboardWritable)
281         return false;
282
283     m_dragStorageUpdated = true;
284     return m_dataObject->setData(normalizeType(type), data);
285 }
286
287 bool ClipboardChromium::platformClipboardChanged() const
288 {
289     return PlatformSupport::clipboardSequenceNumber(currentPasteboardBuffer()) != m_originalSequenceNumber;
290 }
291
292 // extensions beyond IE's API
293 PassRefPtr<DOMStringList> ClipboardChromium::types() const
294 {
295     if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
296         return DOMStringList::create();
297
298     if (!m_dataObject)
299         return DOMStringList::create();
300
301     RefPtr<DOMStringList> results = m_dataObject->types();
302
303     if (m_dataObject->containsFilenames())
304         results->append(mimeTypeFiles);
305
306     return results.release();
307 }
308
309 PassRefPtr<FileList> ClipboardChromium::files() const
310 {
311     if (policy() != ClipboardReadable)
312         return FileList::create();
313
314     if (!m_dataObject)
315         return FileList::create();
316
317     const Vector<String>& filenames = m_dataObject->filenames();
318     RefPtr<FileList> fileList = FileList::create();
319     for (size_t i = 0; i < filenames.size(); ++i)
320         fileList->append(File::create(filenames.at(i)));
321
322     return fileList.release();
323 }
324
325 void ClipboardChromium::setDragImage(CachedImage* image, Node* node, const IntPoint& loc)
326 {
327     if (policy() != ClipboardImageWritable && policy() != ClipboardWritable)
328         return;
329
330     if (m_dragImage)
331         m_dragImage->removeClient(this);
332     m_dragImage = image;
333     if (m_dragImage)
334         m_dragImage->addClient(this);
335
336     m_dragLoc = loc;
337     m_dragImageElement = node;
338 }
339
340 void ClipboardChromium::setDragImage(CachedImage* img, const IntPoint& loc)
341 {
342     setDragImage(img, 0, loc);
343 }
344
345 void ClipboardChromium::setDragImageElement(Node* node, const IntPoint& loc)
346 {
347     setDragImage(0, node, loc);
348 }
349
350 DragImageRef ClipboardChromium::createDragImage(IntPoint& loc) const
351 {
352     DragImageRef result = 0;
353     if (m_dragImageElement) {
354         if (m_frame) {
355             result = m_frame->nodeImage(m_dragImageElement.get());
356             loc = m_dragLoc;
357         }
358     } else if (m_dragImage) {
359         result = createDragImageFromImage(m_dragImage->image());
360         loc = m_dragLoc;
361     }
362     return result;
363 }
364
365 static CachedImage* getCachedImage(Element* element)
366 {
367     // Attempt to pull CachedImage from element
368     ASSERT(element);
369     RenderObject* renderer = element->renderer();
370     if (!renderer || !renderer->isImage())
371         return 0;
372
373     RenderImage* image = toRenderImage(renderer);
374     if (image->cachedImage() && !image->cachedImage()->errorOccurred())
375         return image->cachedImage();
376
377     return 0;
378 }
379
380 static void writeImageToDataObject(ChromiumDataObject* dataObject, Element* element,
381                                    const KURL& url)
382 {
383     // Shove image data into a DataObject for use as a file
384     CachedImage* cachedImage = getCachedImage(element);
385     if (!cachedImage || !cachedImage->imageForRenderer(element->renderer()) || !cachedImage->isLoaded())
386         return;
387
388     SharedBuffer* imageBuffer = cachedImage->imageForRenderer(element->renderer())->data();
389     if (!imageBuffer || !imageBuffer->size())
390         return;
391
392     dataObject->setFileContent(imageBuffer);
393
394     // Determine the filename for the file contents of the image.
395     String filename = cachedImage->response().suggestedFilename();
396     if (filename.isEmpty())
397         filename = url.lastPathComponent();
398     if (filename.isEmpty())
399         filename = element->getAttribute(altAttr);
400     else {
401         // Strip any existing extension. Assume that alt text is usually not a filename.
402         int extensionIndex = filename.reverseFind('.');
403         if (extensionIndex != -1)
404             filename.truncate(extensionIndex);
405     }
406
407     String extension = MIMETypeRegistry::getPreferredExtensionForMIMEType(
408         cachedImage->response().mimeType());
409     extension = extension.isEmpty() ? emptyString() : "." + extension;
410
411     ClipboardChromium::validateFilename(filename, extension);
412
413     dataObject->setFileContentFilename(filename + extension);
414     dataObject->setFileExtension(extension);
415 }
416
417 void ClipboardChromium::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
418 {
419     if (!m_dataObject)
420         return;
421
422     m_dragStorageUpdated = true;
423     m_dataObject->setData(mimeTypeURL, url);
424     m_dataObject->setUrlTitle(title);
425
426     // Write the bytes in the image to the file format.
427     writeImageToDataObject(m_dataObject.get(), element, url);
428
429     // Put img tag on the clipboard referencing the image
430     m_dataObject->setData(mimeTypeTextHTML, createMarkup(element, IncludeNode, 0, ResolveAllURLs));
431 }
432
433 void ClipboardChromium::writeURL(const KURL& url, const String& title, Frame*)
434 {
435     if (!m_dataObject)
436         return;
437     ASSERT(!url.isEmpty());
438
439     m_dragStorageUpdated = true;
440     m_dataObject->setData(mimeTypeURL, url);
441     m_dataObject->setUrlTitle(title);
442
443     // The URL can also be used as plain text.
444     m_dataObject->setData(mimeTypeTextPlain, url.string());
445
446     // The URL can also be used as an HTML fragment.
447     m_dataObject->setData(mimeTypeTextHTML, urlToMarkup(url, title));
448     m_dataObject->setHtmlBaseUrl(url);
449 }
450
451 void ClipboardChromium::writeRange(Range* selectedRange, Frame* frame)
452 {
453     ASSERT(selectedRange);
454     if (!m_dataObject)
455          return;
456
457     m_dragStorageUpdated = true;
458     m_dataObject->setData(mimeTypeTextHTML, createMarkup(selectedRange, 0, AnnotateForInterchange, false, ResolveNonLocalURLs));
459     m_dataObject->setHtmlBaseUrl(frame->document()->url());
460
461     String str = frame->editor()->selectedText();
462 #if OS(WINDOWS)
463     replaceNewlinesWithWindowsStyleNewlines(str);
464 #endif
465     replaceNBSPWithSpace(str);
466     m_dataObject->setData(mimeTypeTextPlain, str);
467 }
468
469 void ClipboardChromium::writePlainText(const String& text)
470 {
471     if (!m_dataObject)
472         return;
473
474     String str = text;
475 #if OS(WINDOWS)
476     replaceNewlinesWithWindowsStyleNewlines(str);
477 #endif
478     replaceNBSPWithSpace(str);
479
480     m_dragStorageUpdated = true;
481     m_dataObject->setData(mimeTypeTextPlain, str);
482 }
483
484 bool ClipboardChromium::hasData()
485 {
486     ASSERT(isForDragAndDrop());
487     if (!m_dataObject)
488         return false;
489
490     return m_dataObject->hasData();
491 }
492
493 #if ENABLE(DATA_TRANSFER_ITEMS)
494 PassRefPtr<DataTransferItemList> ClipboardChromium::items()
495 {
496     if (!m_dataObject)
497         // Return an unassociated empty list.
498         return DataTransferItemListChromium::create(this, m_frame->document()->scriptExecutionContext());
499
500     if (!m_itemList)
501         m_itemList = DataTransferItemListChromium::create(this, m_frame->document()->scriptExecutionContext());
502
503     // FIXME: According to the spec, we are supposed to return the same collection of items each
504     // time. We now return a wrapper that always wraps the *same* set of items, so JS shouldn't be
505     // able to tell, but we probably still want to fix this.
506     return DataTransferItemListPolicyWrapper::create(this, m_itemList);
507 }
508
509 // FIXME: integrate ChromiumDataObject and DataTransferItemList rather than holding them separately and keeping them synced.
510 void ClipboardChromium::mayUpdateItems(Vector<RefPtr<DataTransferItem> >& items)
511 {
512     if (!items.isEmpty() && !storageHasUpdated())
513         return;
514
515     items.clear();
516
517     ScriptExecutionContext* scriptExecutionContext = m_frame->document()->scriptExecutionContext();
518
519     if (isForCopyAndPaste() && policy() == ClipboardReadable) {
520         // Iterate through the types and add them.
521         RefPtr<DOMStringList> types = m_dataObject->types();
522         for (size_t i = 0; i < types->length(); ++i)
523             items.append(DataTransferItemChromium::createFromPasteboard(this, scriptExecutionContext, types->item(i)));
524         return;
525     }
526
527     bool success = false;
528     String plainText = m_dataObject->getData(mimeTypeTextPlain, success);
529     if (success)
530         items.append(DataTransferItemChromium::create(this, scriptExecutionContext, plainText, mimeTypeTextPlain));
531
532     success = false;
533     String htmlText = m_dataObject->getData(mimeTypeTextHTML, success);
534     if (success)
535         items.append(DataTransferItemChromium::create(this, scriptExecutionContext, htmlText, mimeTypeTextHTML));
536
537     if (m_dataObject->containsFilenames()) {
538         const Vector<String>& filenames = m_dataObject->filenames();
539         for (Vector<String>::const_iterator it = filenames.begin(); it != filenames.end(); ++it)
540             items.append(DataTransferItemChromium::create(this, scriptExecutionContext, File::create(*it)));
541     }
542     m_dragStorageUpdated = false;
543 }
544
545 bool ClipboardChromium::storageHasUpdated() const
546 {
547     return (isForCopyAndPaste() && platformClipboardChanged()) || (isForDragAndDrop() && m_dragStorageUpdated);
548 }
549
550 #endif
551
552 } // namespace WebCore