Home | History | Annotate | Download | only in clipboard
      1 /*
      2  * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "core/clipboard/Clipboard.h"
     28 
     29 #include "core/HTMLNames.h"
     30 #include "core/clipboard/DataObject.h"
     31 #include "core/clipboard/DataTransferItem.h"
     32 #include "core/clipboard/DataTransferItemList.h"
     33 #include "core/editing/markup.h"
     34 #include "core/fetch/ImageResource.h"
     35 #include "core/fileapi/FileList.h"
     36 #include "core/frame/LocalFrame.h"
     37 #include "core/html/HTMLImageElement.h"
     38 #include "core/rendering/RenderImage.h"
     39 #include "core/rendering/RenderLayer.h"
     40 #include "core/rendering/RenderObject.h"
     41 #include "platform/DragImage.h"
     42 #include "platform/MIMETypeRegistry.h"
     43 #include "platform/clipboard/ClipboardMimeTypes.h"
     44 #include "platform/clipboard/ClipboardUtilities.h"
     45 
     46 namespace WebCore {
     47 
     48 // These "conversion" methods are called by both WebCore and WebKit, and never make sense to JS, so we don't
     49 // worry about security for these. They don't allow access to the pasteboard anyway.
     50 static DragOperation dragOpFromIEOp(const String& op)
     51 {
     52     // yep, it's really just this fixed set
     53     if (op == "uninitialized")
     54         return DragOperationEvery;
     55     if (op == "none")
     56         return DragOperationNone;
     57     if (op == "copy")
     58         return DragOperationCopy;
     59     if (op == "link")
     60         return DragOperationLink;
     61     if (op == "move")
     62         return (DragOperation)(DragOperationGeneric | DragOperationMove);
     63     if (op == "copyLink")
     64         return (DragOperation)(DragOperationCopy | DragOperationLink);
     65     if (op == "copyMove")
     66         return (DragOperation)(DragOperationCopy | DragOperationGeneric | DragOperationMove);
     67     if (op == "linkMove")
     68         return (DragOperation)(DragOperationLink | DragOperationGeneric | DragOperationMove);
     69     if (op == "all")
     70         return DragOperationEvery;
     71     return DragOperationPrivate; // really a marker for "no conversion"
     72 }
     73 
     74 static String IEOpFromDragOp(DragOperation op)
     75 {
     76     bool moveSet = !!((DragOperationGeneric | DragOperationMove) & op);
     77 
     78     if ((moveSet && (op & DragOperationCopy) && (op & DragOperationLink))
     79         || (op == DragOperationEvery))
     80         return "all";
     81     if (moveSet && (op & DragOperationCopy))
     82         return "copyMove";
     83     if (moveSet && (op & DragOperationLink))
     84         return "linkMove";
     85     if ((op & DragOperationCopy) && (op & DragOperationLink))
     86         return "copyLink";
     87     if (moveSet)
     88         return "move";
     89     if (op & DragOperationCopy)
     90         return "copy";
     91     if (op & DragOperationLink)
     92         return "link";
     93     return "none";
     94 }
     95 
     96 // We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft
     97 // see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
     98 static String normalizeType(const String& type, bool* convertToURL = 0)
     99 {
    100     String cleanType = type.stripWhiteSpace().lower();
    101     if (cleanType == mimeTypeText || cleanType.startsWith(mimeTypeTextPlainEtc))
    102         return mimeTypeTextPlain;
    103     if (cleanType == mimeTypeURL) {
    104         if (convertToURL)
    105             *convertToURL = true;
    106         return mimeTypeTextURIList;
    107     }
    108     return cleanType;
    109 }
    110 
    111 PassRefPtrWillBeRawPtr<Clipboard> Clipboard::create(ClipboardType type, ClipboardAccessPolicy policy, PassRefPtrWillBeRawPtr<DataObject> dataObject)
    112 {
    113     return adoptRefWillBeNoop(new Clipboard(type, policy , dataObject));
    114 }
    115 
    116 Clipboard::~Clipboard()
    117 {
    118 }
    119 
    120 void Clipboard::setDropEffect(const String &effect)
    121 {
    122     if (!isForDragAndDrop())
    123         return;
    124 
    125     // The attribute must ignore any attempts to set it to a value other than none, copy, link, and move.
    126     if (effect != "none" && effect != "copy"  && effect != "link" && effect != "move")
    127         return;
    128 
    129     // FIXME: The spec actually allows this in all circumstances, even though there's no point in
    130     // setting the drop effect when this condition is not true.
    131     if (canReadTypes())
    132         m_dropEffect = effect;
    133 }
    134 
    135 void Clipboard::setEffectAllowed(const String &effect)
    136 {
    137     if (!isForDragAndDrop())
    138         return;
    139 
    140     if (dragOpFromIEOp(effect) == DragOperationPrivate) {
    141         // This means that there was no conversion, and the effectAllowed that
    142         // we are passed isn't a valid effectAllowed, so we should ignore it,
    143         // and not set m_effectAllowed.
    144 
    145         // The attribute must ignore any attempts to set it to a value other than
    146         // none, copy, copyLink, copyMove, link, linkMove, move, all, and uninitialized.
    147         return;
    148     }
    149 
    150 
    151     if (canWriteData())
    152         m_effectAllowed = effect;
    153 }
    154 
    155 void Clipboard::clearData(const String& type)
    156 {
    157     if (!canWriteData())
    158         return;
    159 
    160     if (type.isNull())
    161         m_dataObject->clearAll();
    162     else
    163         m_dataObject->clearData(normalizeType(type));
    164 }
    165 
    166 String Clipboard::getData(const String& type) const
    167 {
    168     if (!canReadData())
    169         return String();
    170 
    171     bool convertToURL = false;
    172     String data = m_dataObject->getData(normalizeType(type, &convertToURL));
    173     if (!convertToURL)
    174         return data;
    175     return convertURIListToURL(data);
    176 }
    177 
    178 bool Clipboard::setData(const String& type, const String& data)
    179 {
    180     if (!canWriteData())
    181         return false;
    182 
    183     return m_dataObject->setData(normalizeType(type), data);
    184 }
    185 
    186 // extensions beyond IE's API
    187 Vector<String> Clipboard::types() const
    188 {
    189     Vector<String> types;
    190     if (!canReadTypes())
    191         return types;
    192 
    193     ListHashSet<String> typesSet = m_dataObject->types();
    194     types.appendRange(typesSet.begin(), typesSet.end());
    195     return types;
    196 }
    197 
    198 PassRefPtrWillBeRawPtr<FileList> Clipboard::files() const
    199 {
    200     RefPtrWillBeRawPtr<FileList> files = FileList::create();
    201     if (!canReadData())
    202         return files.release();
    203 
    204     for (size_t i = 0; i < m_dataObject->length(); ++i) {
    205         if (m_dataObject->item(i)->kind() == DataObjectItem::FileKind) {
    206             RefPtrWillBeRawPtr<Blob> blob = m_dataObject->item(i)->getAsFile();
    207             if (blob && blob->isFile())
    208                 files->append(toFile(blob.get()));
    209         }
    210     }
    211 
    212     return files.release();
    213 }
    214 
    215 void Clipboard::setDragImage(Element* image, int x, int y, ExceptionState& exceptionState)
    216 {
    217     if (!isForDragAndDrop())
    218         return;
    219 
    220     if (!image) {
    221         exceptionState.throwTypeError("setDragImage: Invalid first argument");
    222         return;
    223     }
    224     IntPoint location(x, y);
    225     if (isHTMLImageElement(*image) && !image->inDocument())
    226         setDragImageResource(toHTMLImageElement(*image).cachedImage(), location);
    227     else
    228         setDragImageElement(image, location);
    229 }
    230 
    231 void Clipboard::clearDragImage()
    232 {
    233     if (!canSetDragImage())
    234         return;
    235 
    236     m_dragImage = 0;
    237     m_dragLoc = IntPoint();
    238     m_dragImageElement = nullptr;
    239 }
    240 
    241 void Clipboard::setDragImageResource(ImageResource* img, const IntPoint& loc)
    242 {
    243     setDragImage(img, 0, loc);
    244 }
    245 
    246 void Clipboard::setDragImageElement(Node* node, const IntPoint& loc)
    247 {
    248     setDragImage(0, node, loc);
    249 }
    250 
    251 PassOwnPtr<DragImage> Clipboard::createDragImage(IntPoint& loc, LocalFrame* frame) const
    252 {
    253     if (m_dragImageElement) {
    254         loc = m_dragLoc;
    255 
    256         return frame->nodeImage(*m_dragImageElement);
    257     }
    258     if (m_dragImage) {
    259         loc = m_dragLoc;
    260         return DragImage::create(m_dragImage->image());
    261     }
    262     return nullptr;
    263 }
    264 
    265 static ImageResource* getImageResource(Element* element)
    266 {
    267     // Attempt to pull ImageResource from element
    268     ASSERT(element);
    269     RenderObject* renderer = element->renderer();
    270     if (!renderer || !renderer->isImage())
    271         return 0;
    272 
    273     RenderImage* image = toRenderImage(renderer);
    274     if (image->cachedImage() && !image->cachedImage()->errorOccurred())
    275         return image->cachedImage();
    276 
    277     return 0;
    278 }
    279 
    280 static void writeImageToDataObject(DataObject* dataObject, Element* element, const KURL& url)
    281 {
    282     // Shove image data into a DataObject for use as a file
    283     ImageResource* cachedImage = getImageResource(element);
    284     if (!cachedImage || !cachedImage->imageForRenderer(element->renderer()) || !cachedImage->isLoaded())
    285         return;
    286 
    287     SharedBuffer* imageBuffer = cachedImage->imageForRenderer(element->renderer())->data();
    288     if (!imageBuffer || !imageBuffer->size())
    289         return;
    290 
    291     String imageExtension = cachedImage->image()->filenameExtension();
    292     ASSERT(!imageExtension.isEmpty());
    293 
    294     // Determine the filename for the file contents of the image.
    295     String filename = cachedImage->response().suggestedFilename();
    296     if (filename.isEmpty())
    297         filename = url.lastPathComponent();
    298 
    299     String fileExtension;
    300     if (filename.isEmpty()) {
    301         filename = element->getAttribute(HTMLNames::altAttr);
    302     } else {
    303         // Strip any existing extension. Assume that alt text is usually not a filename.
    304         int extensionIndex = filename.reverseFind('.');
    305         if (extensionIndex != -1) {
    306             fileExtension = filename.substring(extensionIndex + 1);
    307             filename.truncate(extensionIndex);
    308         }
    309     }
    310 
    311     if (!fileExtension.isEmpty() && fileExtension != imageExtension) {
    312         String imageMimeType = MIMETypeRegistry::getMIMETypeForExtension(imageExtension);
    313         ASSERT(imageMimeType.startsWith("image/"));
    314         // Use the file extension only if it has imageMimeType: it's untrustworthy otherwise.
    315         if (imageMimeType == MIMETypeRegistry::getMIMETypeForExtension(fileExtension))
    316             imageExtension = fileExtension;
    317     }
    318 
    319     imageExtension = "." + imageExtension;
    320     validateFilename(filename, imageExtension);
    321 
    322     dataObject->addSharedBuffer(filename + imageExtension, imageBuffer);
    323 }
    324 
    325 void Clipboard::declareAndWriteDragImage(Element* element, const KURL& url, const String& title)
    326 {
    327     if (!m_dataObject)
    328         return;
    329 
    330     m_dataObject->setURLAndTitle(url, title);
    331 
    332     // Write the bytes in the image to the file format.
    333     writeImageToDataObject(m_dataObject.get(), element, url);
    334 
    335     // Put img tag on the clipboard referencing the image
    336     m_dataObject->setData(mimeTypeTextHTML, createMarkup(element, IncludeNode, 0, ResolveAllURLs));
    337 }
    338 
    339 void Clipboard::writeURL(const KURL& url, const String& title)
    340 {
    341     if (!m_dataObject)
    342         return;
    343     ASSERT(!url.isEmpty());
    344 
    345     m_dataObject->setURLAndTitle(url, title);
    346 
    347     // The URL can also be used as plain text.
    348     m_dataObject->setData(mimeTypeTextPlain, url.string());
    349 
    350     // The URL can also be used as an HTML fragment.
    351     m_dataObject->setHTMLAndBaseURL(urlToMarkup(url, title), url);
    352 }
    353 
    354 void Clipboard::writeRange(Range* selectedRange, LocalFrame* frame)
    355 {
    356     ASSERT(selectedRange);
    357     if (!m_dataObject)
    358         return;
    359 
    360     m_dataObject->setHTMLAndBaseURL(createMarkup(selectedRange, 0, AnnotateForInterchange, false, ResolveNonLocalURLs), frame->document()->url());
    361 
    362     String str = frame->selectedTextForClipboard();
    363 #if OS(WIN)
    364     replaceNewlinesWithWindowsStyleNewlines(str);
    365 #endif
    366     replaceNBSPWithSpace(str);
    367     m_dataObject->setData(mimeTypeTextPlain, str);
    368 }
    369 
    370 void Clipboard::writePlainText(const String& text)
    371 {
    372     if (!m_dataObject)
    373         return;
    374 
    375     String str = text;
    376 #if OS(WIN)
    377     replaceNewlinesWithWindowsStyleNewlines(str);
    378 #endif
    379     replaceNBSPWithSpace(str);
    380 
    381     m_dataObject->setData(mimeTypeTextPlain, str);
    382 }
    383 
    384 bool Clipboard::hasData()
    385 {
    386     ASSERT(isForDragAndDrop());
    387 
    388     return m_dataObject->length() > 0;
    389 }
    390 
    391 void Clipboard::setAccessPolicy(ClipboardAccessPolicy policy)
    392 {
    393     // once you go numb, can never go back
    394     ASSERT(m_policy != ClipboardNumb || policy == ClipboardNumb);
    395     m_policy = policy;
    396 }
    397 
    398 bool Clipboard::canReadTypes() const
    399 {
    400     return m_policy == ClipboardReadable || m_policy == ClipboardTypesReadable || m_policy == ClipboardWritable;
    401 }
    402 
    403 bool Clipboard::canReadData() const
    404 {
    405     return m_policy == ClipboardReadable || m_policy == ClipboardWritable;
    406 }
    407 
    408 bool Clipboard::canWriteData() const
    409 {
    410     return m_policy == ClipboardWritable;
    411 }
    412 
    413 bool Clipboard::canSetDragImage() const
    414 {
    415     return m_policy == ClipboardImageWritable || m_policy == ClipboardWritable;
    416 }
    417 
    418 DragOperation Clipboard::sourceOperation() const
    419 {
    420     DragOperation op = dragOpFromIEOp(m_effectAllowed);
    421     ASSERT(op != DragOperationPrivate);
    422     return op;
    423 }
    424 
    425 DragOperation Clipboard::destinationOperation() const
    426 {
    427     DragOperation op = dragOpFromIEOp(m_dropEffect);
    428     ASSERT(op == DragOperationCopy || op == DragOperationNone || op == DragOperationLink || op == (DragOperation)(DragOperationGeneric | DragOperationMove) || op == DragOperationEvery);
    429     return op;
    430 }
    431 
    432 void Clipboard::setSourceOperation(DragOperation op)
    433 {
    434     ASSERT_ARG(op, op != DragOperationPrivate);
    435     m_effectAllowed = IEOpFromDragOp(op);
    436 }
    437 
    438 void Clipboard::setDestinationOperation(DragOperation op)
    439 {
    440     ASSERT_ARG(op, op == DragOperationCopy || op == DragOperationNone || op == DragOperationLink || op == DragOperationGeneric || op == DragOperationMove || op == (DragOperation)(DragOperationGeneric | DragOperationMove));
    441     m_dropEffect = IEOpFromDragOp(op);
    442 }
    443 
    444 bool Clipboard::hasDropZoneType(const String& keyword)
    445 {
    446     if (keyword.startsWith("file:"))
    447         return hasFileOfType(keyword.substring(5));
    448 
    449     if (keyword.startsWith("string:"))
    450         return hasStringOfType(keyword.substring(7));
    451 
    452     return false;
    453 }
    454 
    455 PassRefPtrWillBeRawPtr<DataTransferItemList> Clipboard::items()
    456 {
    457     // FIXME: According to the spec, we are supposed to return the same collection of items each
    458     // time. We now return a wrapper that always wraps the *same* set of items, so JS shouldn't be
    459     // able to tell, but we probably still want to fix this.
    460     return DataTransferItemList::create(this, m_dataObject);
    461 }
    462 
    463 PassRefPtrWillBeRawPtr<DataObject> Clipboard::dataObject() const
    464 {
    465     return m_dataObject;
    466 }
    467 
    468 Clipboard::Clipboard(ClipboardType type, ClipboardAccessPolicy policy, PassRefPtrWillBeRawPtr<DataObject> dataObject)
    469     : m_policy(policy)
    470     , m_dropEffect("uninitialized")
    471     , m_effectAllowed("uninitialized")
    472     , m_clipboardType(type)
    473     , m_dataObject(dataObject)
    474 {
    475     ScriptWrappable::init(this);
    476 }
    477 
    478 void Clipboard::setDragImage(ImageResource* image, Node* node, const IntPoint& loc)
    479 {
    480     if (!canSetDragImage())
    481         return;
    482 
    483     m_dragImage = image;
    484     m_dragLoc = loc;
    485     m_dragImageElement = node;
    486 }
    487 
    488 bool Clipboard::hasFileOfType(const String& type) const
    489 {
    490     if (!canReadTypes())
    491         return false;
    492 
    493     RefPtrWillBeRawPtr<FileList> fileList = files();
    494     if (fileList->isEmpty())
    495         return false;
    496 
    497     for (unsigned f = 0; f < fileList->length(); f++) {
    498         if (equalIgnoringCase(fileList->item(f)->type(), type))
    499             return true;
    500     }
    501     return false;
    502 }
    503 
    504 bool Clipboard::hasStringOfType(const String& type) const
    505 {
    506     if (!canReadTypes())
    507         return false;
    508 
    509     return types().contains(type);
    510 }
    511 
    512 DragOperation convertDropZoneOperationToDragOperation(const String& dragOperation)
    513 {
    514     if (dragOperation == "copy")
    515         return DragOperationCopy;
    516     if (dragOperation == "move")
    517         return DragOperationMove;
    518     if (dragOperation == "link")
    519         return DragOperationLink;
    520     return DragOperationNone;
    521 }
    522 
    523 String convertDragOperationToDropZoneOperation(DragOperation operation)
    524 {
    525     switch (operation) {
    526     case DragOperationCopy:
    527         return String("copy");
    528     case DragOperationMove:
    529         return String("move");
    530     case DragOperationLink:
    531         return String("link");
    532     default:
    533         return String("copy");
    534     }
    535 }
    536 
    537 void Clipboard::trace(Visitor* visitor)
    538 {
    539     visitor->trace(m_dataObject);
    540     visitor->trace(m_dragImageElement);
    541 }
    542 
    543 } // namespace WebCore
    544