Unreviewed, rolling out r107894.
[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 "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 "PlatformSupport.h"
49 #include "Range.h"
50 #include "RenderImage.h"
51 #include "StringCallback.h"
52 #include "markup.h"
53
54 #include <wtf/text/WTFString.h>
55
56 namespace WebCore {
57
58 namespace {
59
60 // These wrapper classes invalidate a DataTransferItem/DataTransferItemList when the associated
61 // Clipboard object goes out of scope.
62 class DataTransferItemListPolicyWrapper : public DataTransferItemList {
63 public:
64     static PassRefPtr<DataTransferItemListPolicyWrapper> create(
65         PassRefPtr<ClipboardChromium>, PassRefPtr<DataTransferItemListChromium>);
66
67     virtual size_t length() const;
68     virtual PassRefPtr<DataTransferItem> item(unsigned long index);
69     virtual void deleteItem(unsigned long index, ExceptionCode&);
70     virtual void clear();
71     virtual void add(const String& data, const String& type, ExceptionCode&);
72     virtual void add(PassRefPtr<File>);
73
74 private:
75     DataTransferItemListPolicyWrapper(PassRefPtr<ClipboardChromium>, PassRefPtr<DataTransferItemListChromium>);
76
77     RefPtr<ClipboardChromium> m_clipboard;
78     RefPtr<DataTransferItemListChromium> m_list;
79 };
80
81 class DataTransferItemPolicyWrapper : public DataTransferItem {
82 public:
83     static PassRefPtr<DataTransferItemPolicyWrapper> create(
84         PassRefPtr<ClipboardChromium>, PassRefPtr<DataTransferItem>);
85
86     virtual String kind() const;
87     virtual String type() const;
88
89     virtual void getAsString(PassRefPtr<StringCallback>) const;
90     virtual PassRefPtr<Blob> getAsFile() const;
91
92 private:
93     DataTransferItemPolicyWrapper(PassRefPtr<ClipboardChromium>, PassRefPtr<DataTransferItem>);
94
95     RefPtr<ClipboardChromium> m_clipboard;
96     RefPtr<DataTransferItem> m_item;
97 };
98
99 PassRefPtr<DataTransferItemListPolicyWrapper> DataTransferItemListPolicyWrapper::create(
100     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<DataTransferItemListChromium> list)
101 {
102     return adoptRef(new DataTransferItemListPolicyWrapper(clipboard, list));
103 }
104
105 size_t DataTransferItemListPolicyWrapper::length() const
106 {
107     if (m_clipboard->policy() == ClipboardNumb)
108         return 0;
109     return m_list->length();
110 }
111
112 PassRefPtr<DataTransferItem> DataTransferItemListPolicyWrapper::item(unsigned long index)
113 {
114     if (m_clipboard->policy() == ClipboardNumb)
115         return 0;
116     RefPtr<DataTransferItem> item = m_list->item(index);
117     if (!item)
118         return 0;
119     return DataTransferItemPolicyWrapper::create(m_clipboard, item);
120 }
121
122 void DataTransferItemListPolicyWrapper::deleteItem(unsigned long index, ExceptionCode& ec)
123 {
124     if (m_clipboard->policy() != ClipboardWritable) {
125         ec = INVALID_STATE_ERR;
126         return;
127     }
128     // FIXME: We handle all the exceptions here, so we don't need to propogate ec.
129     m_list->deleteItem(index, ec);
130 }
131
132 void DataTransferItemListPolicyWrapper::clear()
133 {
134     if (m_clipboard->policy() != ClipboardWritable)
135         return;
136     m_list->clear();
137 }
138
139 void DataTransferItemListPolicyWrapper::add(const String& data, const String& type, ExceptionCode& ec)
140 {
141     if (m_clipboard->policy() != ClipboardWritable)
142         return;
143     m_list->add(data, type, ec);
144 }
145
146 void DataTransferItemListPolicyWrapper::add(PassRefPtr<File> file)
147 {
148     if (m_clipboard->policy() != ClipboardWritable)
149         return;
150     m_list->add(file);
151 }
152
153 DataTransferItemListPolicyWrapper::DataTransferItemListPolicyWrapper(
154     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<DataTransferItemListChromium> list)
155     : m_clipboard(clipboard)
156     , m_list(list)
157 {
158 }
159
160 PassRefPtr<DataTransferItemPolicyWrapper> DataTransferItemPolicyWrapper::create(
161     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<DataTransferItem> item)
162 {
163     return adoptRef(new DataTransferItemPolicyWrapper(clipboard, item));
164 }
165
166 String DataTransferItemPolicyWrapper::kind() const
167 {
168     if (m_clipboard->policy() == ClipboardNumb)
169         return String();
170     return m_item->kind();
171 }
172
173 String DataTransferItemPolicyWrapper::type() const
174 {
175     if (m_clipboard->policy() == ClipboardNumb)
176         return String();
177     return m_item->type();
178 }
179
180 void DataTransferItemPolicyWrapper::getAsString(PassRefPtr<StringCallback> callback) const
181 {
182     if (m_clipboard->policy() != ClipboardReadable && m_clipboard->policy() != ClipboardWritable)
183         return;
184
185     m_item->getAsString(callback);
186 }
187
188 PassRefPtr<Blob> DataTransferItemPolicyWrapper::getAsFile() const
189 {
190     if (m_clipboard->policy() != ClipboardReadable && m_clipboard->policy() != ClipboardWritable)
191         return 0;
192
193     return m_item->getAsFile();
194 }
195
196 DataTransferItemPolicyWrapper::DataTransferItemPolicyWrapper(
197     PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<DataTransferItem> item)
198     : m_clipboard(clipboard)
199     , m_item(item)
200 {
201 }
202
203 } // namespace
204
205 using namespace HTMLNames;
206
207 // We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft
208 // see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
209
210 static String normalizeType(const String& type)
211 {
212     String cleanType = type.stripWhiteSpace().lower();
213     if (cleanType == mimeTypeText || cleanType.startsWith(mimeTypeTextPlainEtc))
214         return mimeTypeTextPlain;
215     return cleanType;
216 }
217
218 PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData* dragData, Frame* frame)
219 {
220     return ClipboardChromium::create(DragAndDrop, dragData->platformData(), policy, frame);
221 }
222
223 ClipboardChromium::ClipboardChromium(ClipboardType clipboardType,
224                                      PassRefPtr<ChromiumDataObject> dataObject,
225                                      ClipboardAccessPolicy policy,
226                                      Frame* frame)
227     : Clipboard(policy, clipboardType)
228     , m_dataObject(dataObject)
229     , m_frame(frame)
230     , m_originalSequenceNumber(PlatformSupport::clipboardSequenceNumber(currentPasteboardBuffer()))
231     , m_dragStorageUpdated(true)
232 {
233 }
234
235 ClipboardChromium::~ClipboardChromium()
236 {
237 }
238
239 PassRefPtr<ClipboardChromium> ClipboardChromium::create(ClipboardType clipboardType,
240     PassRefPtr<ChromiumDataObject> dataObject, ClipboardAccessPolicy policy, Frame* frame)
241 {
242     return adoptRef(new ClipboardChromium(clipboardType, dataObject, policy, frame));
243 }
244
245 void ClipboardChromium::clearData(const String& type)
246 {
247     if (policy() != ClipboardWritable || !m_dataObject)
248         return;
249
250     m_dragStorageUpdated = true;
251     m_dataObject->clearData(normalizeType(type));
252
253     ASSERT_NOT_REACHED();
254 }
255
256 void ClipboardChromium::clearAllData()
257 {
258     if (policy() != ClipboardWritable)
259         return;
260
261     m_dragStorageUpdated = true;
262     m_dataObject->clearAll();
263 }
264
265 String ClipboardChromium::getData(const String& type) const
266 {
267     bool ignoredSuccess = false;
268     if (policy() != ClipboardReadable || !m_dataObject)
269         return String();
270
271     if (isForCopyAndPaste() && platformClipboardChanged())
272         return String();
273
274     return m_dataObject->getData(normalizeType(type), ignoredSuccess);
275 }
276
277 bool ClipboardChromium::setData(const String& type, const String& data)
278 {
279     if (policy() != ClipboardWritable)
280         return false;
281
282     m_dragStorageUpdated = true;
283     return m_dataObject->setData(normalizeType(type), data);
284 }
285
286 bool ClipboardChromium::platformClipboardChanged() const
287 {
288     return PlatformSupport::clipboardSequenceNumber(currentPasteboardBuffer()) != m_originalSequenceNumber;
289 }
290
291 // extensions beyond IE's API
292 HashSet<String> ClipboardChromium::types() const
293 {
294     HashSet<String> results;
295     if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
296         return results;
297
298     if (!m_dataObject)
299         return results;
300
301     results = m_dataObject->types();
302
303     if (m_dataObject->containsFilenames())
304         results.add(mimeTypeFiles);
305
306     return results;
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         HashSet<String> types = m_dataObject->types();
522         for (HashSet<String>::const_iterator it = types.begin(); it != types.end(); ++it)
523             items.append(DataTransferItemChromium::createFromPasteboard(this, scriptExecutionContext, *it));
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