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 "ChromiumBridge.h"
32#include "ChromiumDataObject.h"
33#include "ClipboardUtilitiesChromium.h"
34#include "Document.h"
35#include "Element.h"
36#include "FileList.h"
37#include "Frame.h"
38#include "HTMLNames.h"
39#include "NamedAttrMap.h"
40#include "MIMETypeRegistry.h"
41#include "markup.h"
42#include "NamedNodeMap.h"
43#include "Pasteboard.h"
44#include "PlatformString.h"
45#include "Range.h"
46#include "RenderImage.h"
47#include "StringBuilder.h"
48
49namespace WebCore {
50
51using namespace HTMLNames;
52
53// We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft
54// see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
55
56enum ClipboardDataType { ClipboardDataTypeNone, ClipboardDataTypeURL, ClipboardDataTypeText, ClipboardDataTypeDownloadURL };
57
58static ClipboardDataType clipboardTypeFromMIMEType(const String& type)
59{
60    String cleanType = type.stripWhiteSpace().lower();
61
62    // two special cases for IE compatibility
63    if (cleanType == "text" || cleanType == "text/plain" || cleanType.startsWith("text/plain;"))
64        return ClipboardDataTypeText;
65    if (cleanType == "url" || cleanType == "text/uri-list")
66        return ClipboardDataTypeURL;
67    if (cleanType == "downloadurl")
68        return ClipboardDataTypeDownloadURL;
69
70    return ClipboardDataTypeNone;
71}
72
73ClipboardChromium::ClipboardChromium(bool isForDragging,
74                                     PassRefPtr<ChromiumDataObject> dataObject,
75                                     ClipboardAccessPolicy policy)
76    : Clipboard(policy, isForDragging)
77    , m_dataObject(dataObject)
78{
79}
80
81PassRefPtr<ClipboardChromium> ClipboardChromium::create(bool isForDragging,
82    PassRefPtr<ChromiumDataObject> dataObject, ClipboardAccessPolicy policy)
83{
84    return adoptRef(new ClipboardChromium(isForDragging, dataObject, policy));
85}
86
87void ClipboardChromium::clearData(const String& type)
88{
89    if (policy() != ClipboardWritable || !m_dataObject)
90        return;
91
92    ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
93
94    if (dataType == ClipboardDataTypeURL) {
95        m_dataObject->url = KURL();
96        m_dataObject->urlTitle = "";
97    }
98
99    if (dataType == ClipboardDataTypeText)
100        m_dataObject->plainText = "";
101}
102
103void ClipboardChromium::clearAllData()
104{
105    if (policy() != ClipboardWritable)
106        return;
107
108    m_dataObject->clear();
109}
110
111String ClipboardChromium::getData(const String& type, bool& success) const
112{
113    success = false;
114    if (policy() != ClipboardReadable || !m_dataObject)
115        return String();
116
117    ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
118    String text;
119    if (dataType == ClipboardDataTypeText) {
120        if (!isForDragging()) {
121            // If this isn't for a drag, it's for a cut/paste event handler.
122            // In this case, we need to check the clipboard.
123            PasteboardPrivate::ClipboardBuffer buffer =
124                Pasteboard::generalPasteboard()->isSelectionMode() ?
125                PasteboardPrivate::SelectionBuffer :
126                PasteboardPrivate::StandardBuffer;
127            text = ChromiumBridge::clipboardReadPlainText(buffer);
128            success = !text.isEmpty();
129        } else if (!m_dataObject->plainText.isEmpty()) {
130            success = true;
131            text = m_dataObject->plainText;
132        }
133    } else if (dataType == ClipboardDataTypeURL) {
134        // FIXME: Handle the cut/paste event.  This requires adding a new IPC
135        // message to get the URL from the clipboard directly.
136        if (!m_dataObject->url.isEmpty()) {
137            success = true;
138            text = m_dataObject->url.string();
139        }
140    }
141
142    return text;
143}
144
145bool ClipboardChromium::setData(const String& type, const String& data)
146{
147    if (policy() != ClipboardWritable)
148        return false;
149
150    ClipboardDataType winType = clipboardTypeFromMIMEType(type);
151
152    if (winType == ClipboardDataTypeURL) {
153        m_dataObject->url = KURL(ParsedURLString, data);
154        return m_dataObject->url.isValid();
155    }
156
157    if (winType == ClipboardDataTypeText) {
158        m_dataObject->plainText = data;
159        return true;
160    }
161
162    if (winType == ClipboardDataTypeDownloadURL) {
163        m_dataObject->downloadMetadata = data;
164        KURL url = KURL(ParsedURLString, data);
165        if (url.isValid())
166            m_dataObject->downloadURL = url;
167        return true;
168    }
169
170    return false;
171}
172
173// extensions beyond IE's API
174HashSet<String> ClipboardChromium::types() const
175{
176    HashSet<String> results;
177    if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
178        return results;
179
180    if (!m_dataObject)
181        return results;
182
183    if (!m_dataObject->filenames.isEmpty())
184        results.add("Files");
185
186    if (m_dataObject->url.isValid()) {
187        results.add("URL");
188        results.add("text/uri-list");
189    }
190
191    if (!m_dataObject->plainText.isEmpty()) {
192        results.add("Text");
193        results.add("text/plain");
194    }
195
196    return results;
197}
198
199PassRefPtr<FileList> ClipboardChromium::files() const
200{
201    if (policy() != ClipboardReadable)
202        return FileList::create();
203
204    if (!m_dataObject || m_dataObject->filenames.isEmpty())
205        return FileList::create();
206
207    RefPtr<FileList> fileList = FileList::create();
208    for (size_t i = 0; i < m_dataObject->filenames.size(); ++i)
209        fileList->append(File::create(m_dataObject->filenames.at(i)));
210
211    return fileList.release();
212}
213
214void ClipboardChromium::setDragImage(CachedImage* image, Node* node, const IntPoint& loc)
215{
216    if (policy() != ClipboardImageWritable && policy() != ClipboardWritable)
217        return;
218
219    if (m_dragImage)
220        m_dragImage->removeClient(this);
221    m_dragImage = image;
222    if (m_dragImage)
223        m_dragImage->addClient(this);
224
225    m_dragLoc = loc;
226    m_dragImageElement = node;
227}
228
229void ClipboardChromium::setDragImage(CachedImage* img, const IntPoint& loc)
230{
231    setDragImage(img, 0, loc);
232}
233
234void ClipboardChromium::setDragImageElement(Node* node, const IntPoint& loc)
235{
236    setDragImage(0, node, loc);
237}
238
239DragImageRef ClipboardChromium::createDragImage(IntPoint& loc) const
240{
241    DragImageRef result = 0;
242    if (m_dragImage) {
243        result = createDragImageFromImage(m_dragImage->image());
244        loc = m_dragLoc;
245    }
246    return result;
247}
248
249static String imageToMarkup(const String& url, Element* element)
250{
251    StringBuilder markup;
252    markup.append("<img src=\"");
253    markup.append(url);
254    markup.append("\"");
255    // Copy over attributes.  If we are dragging an image, we expect things like
256    // the id to be copied as well.
257    NamedNodeMap* attrs = element->attributes();
258    unsigned length = attrs->length();
259    for (unsigned i = 0; i < length; ++i) {
260        Attribute* attr = attrs->attributeItem(i);
261        if (attr->localName() == "src")
262            continue;
263        markup.append(" ");
264        markup.append(attr->localName());
265        markup.append("=\"");
266        String escapedAttr = attr->value();
267        escapedAttr.replace("\"", "&quot;");
268        markup.append(escapedAttr);
269        markup.append("\"");
270    }
271
272    markup.append("/>");
273    return markup.toString();
274}
275
276static CachedImage* getCachedImage(Element* element)
277{
278    // Attempt to pull CachedImage from element
279    ASSERT(element);
280    RenderObject* renderer = element->renderer();
281    if (!renderer || !renderer->isImage())
282        return 0;
283
284    RenderImage* image = toRenderImage(renderer);
285    if (image->cachedImage() && !image->cachedImage()->errorOccurred())
286        return image->cachedImage();
287
288    return 0;
289}
290
291static void writeImageToDataObject(ChromiumDataObject* dataObject, Element* element,
292                                   const KURL& url)
293{
294    // Shove image data into a DataObject for use as a file
295    CachedImage* cachedImage = getCachedImage(element);
296    if (!cachedImage || !cachedImage->image() || !cachedImage->isLoaded())
297        return;
298
299    SharedBuffer* imageBuffer = cachedImage->image()->data();
300    if (!imageBuffer || !imageBuffer->size())
301        return;
302
303    dataObject->fileContent = imageBuffer;
304
305    // Determine the filename for the file contents of the image.  We try to
306    // use the alt tag if one exists, otherwise we fall back on the suggested
307    // filename in the http header, and finally we resort to using the filename
308    // in the URL.
309    String extension = MIMETypeRegistry::getPreferredExtensionForMIMEType(
310        cachedImage->response().mimeType());
311    dataObject->fileExtension = extension.isEmpty() ? "" : "." + extension;
312    String title = element->getAttribute(altAttr);
313    if (title.isEmpty())
314        title = cachedImage->response().suggestedFilename();
315
316    title = ClipboardChromium::validateFileName(title, dataObject);
317    dataObject->fileContentFilename = title + dataObject->fileExtension;
318}
319
320void ClipboardChromium::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
321{
322    if (!m_dataObject)
323        return;
324
325    m_dataObject->url = url;
326    m_dataObject->urlTitle = title;
327
328    // Write the bytes in the image to the file format.
329    writeImageToDataObject(m_dataObject.get(), element, url);
330
331    AtomicString imageURL = element->getAttribute(srcAttr);
332    if (imageURL.isEmpty())
333        return;
334
335    String fullURL = frame->document()->completeURL(deprecatedParseURL(imageURL));
336    if (fullURL.isEmpty())
337        return;
338
339    // Put img tag on the clipboard referencing the image
340    m_dataObject->textHtml = imageToMarkup(fullURL, element);
341}
342
343void ClipboardChromium::writeURL(const KURL& url, const String& title, Frame*)
344{
345    if (!m_dataObject)
346        return;
347    m_dataObject->url = url;
348    m_dataObject->urlTitle = title;
349
350    // The URL can also be used as plain text.
351    m_dataObject->plainText = url.string();
352
353    // The URL can also be used as an HTML fragment.
354    m_dataObject->textHtml = urlToMarkup(url, title);
355    m_dataObject->htmlBaseUrl = url;
356}
357
358void ClipboardChromium::writeRange(Range* selectedRange, Frame* frame)
359{
360    ASSERT(selectedRange);
361    if (!m_dataObject)
362         return;
363
364    m_dataObject->textHtml = createMarkup(selectedRange, 0,
365        AnnotateForInterchange);
366    m_dataObject->htmlBaseUrl = frame->document()->url();
367
368    String str = frame->selectedText();
369#if OS(WINDOWS)
370    replaceNewlinesWithWindowsStyleNewlines(str);
371#endif
372    replaceNBSPWithSpace(str);
373    m_dataObject->plainText = str;
374}
375
376void ClipboardChromium::writePlainText(const String& text)
377{
378    if (!m_dataObject)
379        return;
380
381    String str = text;
382#if OS(WINDOWS)
383    replaceNewlinesWithWindowsStyleNewlines(str);
384#endif
385    replaceNBSPWithSpace(str);
386    m_dataObject->plainText = str;
387}
388
389bool ClipboardChromium::hasData()
390{
391    if (!m_dataObject)
392        return false;
393
394    return m_dataObject->hasData();
395}
396
397} // namespace WebCore
398