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