| /* |
| * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/clipboard/DataTransfer.h" |
| |
| #include "core/HTMLNames.h" |
| #include "core/clipboard/DataObject.h" |
| #include "core/clipboard/DataTransferItem.h" |
| #include "core/clipboard/DataTransferItemList.h" |
| #include "core/editing/EphemeralRange.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/editing/serializers/Serialization.h" |
| #include "core/fileapi/FileList.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/html/HTMLImageElement.h" |
| #include "core/html/TextControlElement.h" |
| #include "core/layout/LayoutImage.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/loader/resource/ImageResourceContent.h" |
| #include "platform/DragImage.h" |
| #include "platform/clipboard/ClipboardMimeTypes.h" |
| #include "platform/clipboard/ClipboardUtilities.h" |
| #include "platform/network/mime/MIMETypeRegistry.h" |
| #include <memory> |
| |
| namespace blink { |
| |
| static DragOperation convertEffectAllowedToDragOperation(const String& op) { |
| // Values specified in |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dom-datatransfer-effectallowed |
| if (op == "uninitialized") |
| return DragOperationEvery; |
| if (op == "none") |
| return DragOperationNone; |
| if (op == "copy") |
| return DragOperationCopy; |
| if (op == "link") |
| return DragOperationLink; |
| if (op == "move") |
| return (DragOperation)(DragOperationGeneric | DragOperationMove); |
| if (op == "copyLink") |
| return (DragOperation)(DragOperationCopy | DragOperationLink); |
| if (op == "copyMove") |
| return (DragOperation)(DragOperationCopy | DragOperationGeneric | |
| DragOperationMove); |
| if (op == "linkMove") |
| return (DragOperation)(DragOperationLink | DragOperationGeneric | |
| DragOperationMove); |
| if (op == "all") |
| return DragOperationEvery; |
| return DragOperationPrivate; // really a marker for "no conversion" |
| } |
| |
| static String convertDragOperationToEffectAllowed(DragOperation op) { |
| bool moveSet = !!((DragOperationGeneric | DragOperationMove) & op); |
| |
| if ((moveSet && (op & DragOperationCopy) && (op & DragOperationLink)) || |
| (op == DragOperationEvery)) |
| return "all"; |
| if (moveSet && (op & DragOperationCopy)) |
| return "copyMove"; |
| if (moveSet && (op & DragOperationLink)) |
| return "linkMove"; |
| if ((op & DragOperationCopy) && (op & DragOperationLink)) |
| return "copyLink"; |
| if (moveSet) |
| return "move"; |
| if (op & DragOperationCopy) |
| return "copy"; |
| if (op & DragOperationLink) |
| return "link"; |
| return "none"; |
| } |
| |
| // We provide the IE clipboard types (URL and Text), and the clipboard types |
| // specified in the WHATWG Web Applications 1.0 draft see |
| // http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3 |
| static String normalizeType(const String& type, bool* convertToURL = 0) { |
| String cleanType = type.stripWhiteSpace().lower(); |
| if (cleanType == mimeTypeText || cleanType.startsWith(mimeTypeTextPlainEtc)) |
| return mimeTypeTextPlain; |
| if (cleanType == mimeTypeURL) { |
| if (convertToURL) |
| *convertToURL = true; |
| return mimeTypeTextURIList; |
| } |
| return cleanType; |
| } |
| |
| DataTransfer* DataTransfer::create(DataTransferType type, |
| DataTransferAccessPolicy policy, |
| DataObject* dataObject) { |
| return new DataTransfer(type, policy, dataObject); |
| } |
| |
| DataTransfer::~DataTransfer() {} |
| |
| void DataTransfer::setDropEffect(const String& effect) { |
| if (!isForDragAndDrop()) |
| return; |
| |
| // The attribute must ignore any attempts to set it to a value other than |
| // none, copy, link, and move. |
| if (effect != "none" && effect != "copy" && effect != "link" && |
| effect != "move") |
| return; |
| |
| // The specification states that dropEffect can be changed at all times, even |
| // if the DataTransfer instance is protected or neutered. |
| // |
| // Allowing these changes seems inconsequential, but findDropZone() in |
| // EventHandler.cpp relies on being able to call setDropEffect during |
| // dragenter, when the DataTransfer policy is DataTransferTypesReadable. |
| m_dropEffect = effect; |
| } |
| |
| void DataTransfer::setEffectAllowed(const String& effect) { |
| if (!isForDragAndDrop()) |
| return; |
| |
| if (convertEffectAllowedToDragOperation(effect) == DragOperationPrivate) { |
| // This means that there was no conversion, and the effectAllowed that |
| // we are passed isn't a valid effectAllowed, so we should ignore it, |
| // and not set m_effectAllowed. |
| |
| // The attribute must ignore any attempts to set it to a value other than |
| // none, copy, copyLink, copyMove, link, linkMove, move, all, and |
| // uninitialized. |
| return; |
| } |
| |
| if (canWriteData()) |
| m_effectAllowed = effect; |
| } |
| |
| void DataTransfer::clearData(const String& type) { |
| if (!canWriteData()) |
| return; |
| |
| if (type.isNull()) |
| m_dataObject->clearAll(); |
| else |
| m_dataObject->clearData(normalizeType(type)); |
| } |
| |
| String DataTransfer::getData(const String& type) const { |
| if (!canReadData()) |
| return String(); |
| |
| bool convertToURL = false; |
| String data = m_dataObject->getData(normalizeType(type, &convertToURL)); |
| if (!convertToURL) |
| return data; |
| return convertURIListToURL(data); |
| } |
| |
| void DataTransfer::setData(const String& type, const String& data) { |
| if (!canWriteData()) |
| return; |
| |
| m_dataObject->setData(normalizeType(type), data); |
| } |
| |
| // extensions beyond IE's API |
| Vector<String> DataTransfer::types() const { |
| Vector<String> types; |
| if (!canReadTypes()) |
| return types; |
| |
| return m_dataObject->types(); |
| } |
| |
| FileList* DataTransfer::files() const { |
| FileList* files = FileList::create(); |
| if (!canReadData()) |
| return files; |
| |
| for (size_t i = 0; i < m_dataObject->length(); ++i) { |
| if (m_dataObject->item(i)->kind() == DataObjectItem::FileKind) { |
| Blob* blob = m_dataObject->item(i)->getAsFile(); |
| if (blob && blob->isFile()) |
| files->append(toFile(blob)); |
| } |
| } |
| |
| return files; |
| } |
| |
| void DataTransfer::setDragImage(Element* image, int x, int y) { |
| ASSERT(image); |
| |
| if (!isForDragAndDrop()) |
| return; |
| |
| IntPoint location(x, y); |
| if (isHTMLImageElement(*image) && !image->isConnected()) |
| setDragImageResource(toHTMLImageElement(*image).cachedImage(), location); |
| else |
| setDragImageElement(image, location); |
| } |
| |
| void DataTransfer::clearDragImage() { |
| if (!canSetDragImage()) |
| return; |
| |
| m_dragImage = nullptr; |
| m_dragLoc = IntPoint(); |
| m_dragImageElement = nullptr; |
| } |
| |
| void DataTransfer::setDragImageResource(ImageResourceContent* img, |
| const IntPoint& loc) { |
| setDragImage(img, 0, loc); |
| } |
| |
| void DataTransfer::setDragImageElement(Node* node, const IntPoint& loc) { |
| setDragImage(0, node, loc); |
| } |
| |
| std::unique_ptr<DragImage> DataTransfer::createDragImage( |
| IntPoint& loc, |
| LocalFrame* frame) const { |
| if (m_dragImageElement) { |
| loc = m_dragLoc; |
| |
| return frame->nodeImage(*m_dragImageElement); |
| } |
| if (m_dragImage) { |
| loc = m_dragLoc; |
| return DragImage::create(m_dragImage->getImage()); |
| } |
| return nullptr; |
| } |
| |
| static ImageResourceContent* getImageResourceContent(Element* element) { |
| // Attempt to pull ImageResourceContent from element |
| ASSERT(element); |
| LayoutObject* layoutObject = element->layoutObject(); |
| if (!layoutObject || !layoutObject->isImage()) |
| return 0; |
| |
| LayoutImage* image = toLayoutImage(layoutObject); |
| if (image->cachedImage() && !image->cachedImage()->errorOccurred()) |
| return image->cachedImage(); |
| |
| return 0; |
| } |
| |
| static void writeImageToDataObject(DataObject* dataObject, |
| Element* element, |
| const KURL& imageURL) { |
| // Shove image data into a DataObject for use as a file |
| ImageResourceContent* cachedImage = getImageResourceContent(element); |
| if (!cachedImage || !cachedImage->getImage() || !cachedImage->isLoaded()) |
| return; |
| |
| RefPtr<SharedBuffer> imageBuffer = cachedImage->getImage()->data(); |
| if (!imageBuffer || !imageBuffer->size()) |
| return; |
| |
| dataObject->addSharedBuffer(imageBuffer, imageURL, |
| cachedImage->getImage()->filenameExtension(), |
| cachedImage->response().httpHeaderFields().get( |
| HTTPNames::Content_Disposition)); |
| } |
| |
| void DataTransfer::declareAndWriteDragImage(Element* element, |
| const KURL& linkURL, |
| const KURL& imageURL, |
| const String& title) { |
| if (!m_dataObject) |
| return; |
| |
| m_dataObject->setURLAndTitle(linkURL.isValid() ? linkURL : imageURL, title); |
| |
| // Write the bytes in the image to the file format. |
| writeImageToDataObject(m_dataObject.get(), element, imageURL); |
| |
| // Put img tag on the clipboard referencing the image |
| m_dataObject->setData(mimeTypeTextHTML, |
| createMarkup(element, IncludeNode, ResolveAllURLs)); |
| } |
| |
| void DataTransfer::writeURL(Node* node, const KURL& url, const String& title) { |
| if (!m_dataObject) |
| return; |
| ASSERT(!url.isEmpty()); |
| |
| m_dataObject->setURLAndTitle(url, title); |
| |
| // The URL can also be used as plain text. |
| m_dataObject->setData(mimeTypeTextPlain, url.getString()); |
| |
| // The URL can also be used as an HTML fragment. |
| m_dataObject->setHTMLAndBaseURL( |
| createMarkup(node, IncludeNode, ResolveAllURLs), url); |
| } |
| |
| void DataTransfer::writeSelection(const FrameSelection& selection) { |
| if (!m_dataObject) |
| return; |
| |
| if (!enclosingTextControl( |
| selection.computeVisibleSelectionInDOMTreeDeprecated().start())) { |
| m_dataObject->setHTMLAndBaseURL(selection.selectedHTMLForClipboard(), |
| selection.frame()->document()->url()); |
| } |
| |
| String str = selection.selectedTextForClipboard(); |
| #if OS(WIN) |
| replaceNewlinesWithWindowsStyleNewlines(str); |
| #endif |
| replaceNBSPWithSpace(str); |
| m_dataObject->setData(mimeTypeTextPlain, str); |
| } |
| |
| void DataTransfer::setAccessPolicy(DataTransferAccessPolicy policy) { |
| // once you go numb, can never go back |
| ASSERT(m_policy != DataTransferNumb || policy == DataTransferNumb); |
| m_policy = policy; |
| } |
| |
| bool DataTransfer::canReadTypes() const { |
| return m_policy == DataTransferReadable || |
| m_policy == DataTransferTypesReadable || |
| m_policy == DataTransferWritable; |
| } |
| |
| bool DataTransfer::canReadData() const { |
| return m_policy == DataTransferReadable || m_policy == DataTransferWritable; |
| } |
| |
| bool DataTransfer::canWriteData() const { |
| return m_policy == DataTransferWritable; |
| } |
| |
| bool DataTransfer::canSetDragImage() const { |
| return m_policy == DataTransferImageWritable || |
| m_policy == DataTransferWritable; |
| } |
| |
| DragOperation DataTransfer::sourceOperation() const { |
| DragOperation op = convertEffectAllowedToDragOperation(m_effectAllowed); |
| ASSERT(op != DragOperationPrivate); |
| return op; |
| } |
| |
| DragOperation DataTransfer::destinationOperation() const { |
| DragOperation op = convertEffectAllowedToDragOperation(m_dropEffect); |
| ASSERT(op == DragOperationCopy || op == DragOperationNone || |
| op == DragOperationLink || |
| op == (DragOperation)(DragOperationGeneric | DragOperationMove) || |
| op == DragOperationEvery); |
| return op; |
| } |
| |
| void DataTransfer::setSourceOperation(DragOperation op) { |
| DCHECK_NE(op, DragOperationPrivate); |
| m_effectAllowed = convertDragOperationToEffectAllowed(op); |
| } |
| |
| void DataTransfer::setDestinationOperation(DragOperation op) { |
| DCHECK(op == DragOperationCopy || op == DragOperationNone || |
| op == DragOperationLink || op == DragOperationGeneric || |
| op == DragOperationMove || |
| op == static_cast<DragOperation>(DragOperationGeneric | |
| DragOperationMove)); |
| m_dropEffect = convertDragOperationToEffectAllowed(op); |
| } |
| |
| bool DataTransfer::hasDropZoneType(const String& keyword) { |
| if (keyword.startsWith("file:")) |
| return hasFileOfType(keyword.substring(5)); |
| |
| if (keyword.startsWith("string:")) |
| return hasStringOfType(keyword.substring(7)); |
| |
| return false; |
| } |
| |
| DataTransferItemList* DataTransfer::items() { |
| // FIXME: According to the spec, we are supposed to return the same collection |
| // of items each time. We now return a wrapper that always wraps the *same* |
| // set of items, so JS shouldn't be able to tell, but we probably still want |
| // to fix this. |
| return DataTransferItemList::create(this, m_dataObject); |
| } |
| |
| DataObject* DataTransfer::dataObject() const { |
| return m_dataObject; |
| } |
| |
| DataTransfer::DataTransfer(DataTransferType type, |
| DataTransferAccessPolicy policy, |
| DataObject* dataObject) |
| : m_policy(policy), |
| m_dropEffect("uninitialized"), |
| m_effectAllowed("uninitialized"), |
| m_transferType(type), |
| m_dataObject(dataObject) {} |
| |
| void DataTransfer::setDragImage(ImageResourceContent* image, |
| Node* node, |
| const IntPoint& loc) { |
| if (!canSetDragImage()) |
| return; |
| |
| m_dragImage = image; |
| m_dragLoc = loc; |
| m_dragImageElement = node; |
| } |
| |
| bool DataTransfer::hasFileOfType(const String& type) const { |
| if (!canReadTypes()) |
| return false; |
| |
| for (size_t i = 0; i < m_dataObject->length(); ++i) { |
| if (m_dataObject->item(i)->kind() == DataObjectItem::FileKind) { |
| Blob* blob = m_dataObject->item(i)->getAsFile(); |
| if (blob && blob->isFile() && equalIgnoringCase(blob->type(), type)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool DataTransfer::hasStringOfType(const String& type) const { |
| if (!canReadTypes()) |
| return false; |
| |
| return types().contains(type); |
| } |
| |
| DragOperation convertDropZoneOperationToDragOperation( |
| const String& dragOperation) { |
| if (dragOperation == "copy") |
| return DragOperationCopy; |
| if (dragOperation == "move") |
| return DragOperationMove; |
| if (dragOperation == "link") |
| return DragOperationLink; |
| return DragOperationNone; |
| } |
| |
| String convertDragOperationToDropZoneOperation(DragOperation operation) { |
| switch (operation) { |
| case DragOperationCopy: |
| return String("copy"); |
| case DragOperationMove: |
| return String("move"); |
| case DragOperationLink: |
| return String("link"); |
| default: |
| return String("copy"); |
| } |
| } |
| |
| DEFINE_TRACE(DataTransfer) { |
| visitor->trace(m_dataObject); |
| visitor->trace(m_dragImage); |
| visitor->trace(m_dragImageElement); |
| } |
| |
| } // namespace blink |