/*
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
* Copyright (C) 2008, 2009 Google Inc.
*
* 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 "config.h"
#include "ClipboardChromium.h"
#include "CachedImage.h"
#include "ChromiumBridge.h"
#include "ChromiumDataObject.h"
#include "ClipboardUtilitiesChromium.h"
#include "Document.h"
#include "Element.h"
#include "FileList.h"
#include "Frame.h"
#include "HTMLNames.h"
#include "NamedAttrMap.h"
#include "MIMETypeRegistry.h"
#include "markup.h"
#include "NamedNodeMap.h"
#include "Pasteboard.h"
#include "PlatformString.h"
#include "Range.h"
#include "RenderImage.h"
#include "StringBuilder.h"
namespace WebCore {
using namespace HTMLNames;
// 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
enum ClipboardDataType { ClipboardDataTypeNone, ClipboardDataTypeURL, ClipboardDataTypeText, ClipboardDataTypeDownloadURL };
static ClipboardDataType clipboardTypeFromMIMEType(const String& type)
{
String cleanType = type.stripWhiteSpace().lower();
// two special cases for IE compatibility
if (cleanType == "text" || cleanType == "text/plain" || cleanType.startsWith("text/plain;"))
return ClipboardDataTypeText;
if (cleanType == "url" || cleanType == "text/uri-list")
return ClipboardDataTypeURL;
if (cleanType == "downloadurl")
return ClipboardDataTypeDownloadURL;
return ClipboardDataTypeNone;
}
ClipboardChromium::ClipboardChromium(bool isForDragging,
PassRefPtr<ChromiumDataObject> dataObject,
ClipboardAccessPolicy policy)
: Clipboard(policy, isForDragging)
, m_dataObject(dataObject)
{
}
PassRefPtr<ClipboardChromium> ClipboardChromium::create(bool isForDragging,
PassRefPtr<ChromiumDataObject> dataObject, ClipboardAccessPolicy policy)
{
return adoptRef(new ClipboardChromium(isForDragging, dataObject, policy));
}
void ClipboardChromium::clearData(const String& type)
{
if (policy() != ClipboardWritable || !m_dataObject)
return;
ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
if (dataType == ClipboardDataTypeURL) {
m_dataObject->url = KURL();
m_dataObject->urlTitle = "";
}
if (dataType == ClipboardDataTypeText)
m_dataObject->plainText = "";
}
void ClipboardChromium::clearAllData()
{
if (policy() != ClipboardWritable)
return;
m_dataObject->clear();
}
String ClipboardChromium::getData(const String& type, bool& success) const
{
success = false;
if (policy() != ClipboardReadable || !m_dataObject)
return String();
ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
String text;
if (dataType == ClipboardDataTypeText) {
if (!isForDragging()) {
// If this isn't for a drag, it's for a cut/paste event handler.
// In this case, we need to check the clipboard.
PasteboardPrivate::ClipboardBuffer buffer =
Pasteboard::generalPasteboard()->isSelectionMode() ?
PasteboardPrivate::SelectionBuffer :
PasteboardPrivate::StandardBuffer;
text = ChromiumBridge::clipboardReadPlainText(buffer);
success = !text.isEmpty();
} else if (!m_dataObject->plainText.isEmpty()) {
success = true;
text = m_dataObject->plainText;
}
} else if (dataType == ClipboardDataTypeURL) {
// FIXME: Handle the cut/paste event. This requires adding a new IPC
// message to get the URL from the clipboard directly.
if (!m_dataObject->url.isEmpty()) {
success = true;
text = m_dataObject->url.string();
}
}
return text;
}
bool ClipboardChromium::setData(const String& type, const String& data)
{
if (policy() != ClipboardWritable)
return false;
ClipboardDataType winType = clipboardTypeFromMIMEType(type);
if (winType == ClipboardDataTypeURL) {
m_dataObject->url = KURL(ParsedURLString, data);
return m_dataObject->url.isValid();
}
if (winType == ClipboardDataTypeText) {
m_dataObject->plainText = data;
return true;
}
if (winType == ClipboardDataTypeDownloadURL) {
m_dataObject->downloadMetadata = data;
KURL url = KURL(ParsedURLString, data);
if (url.isValid())
m_dataObject->downloadURL = url;
return true;
}
return false;
}
// extensions beyond IE's API
HashSet<String> ClipboardChromium::types() const
{
HashSet<String> results;
if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
return results;
if (!m_dataObject)
return results;
if (!m_dataObject->filenames.isEmpty())
results.add("Files");
if (m_dataObject->url.isValid()) {
results.add("URL");
results.add("text/uri-list");
}
if (!m_dataObject->plainText.isEmpty()) {
results.add("Text");
results.add("text/plain");
}
return results;
}
PassRefPtr<FileList> ClipboardChromium::files() const
{
if (policy() != ClipboardReadable)
return FileList::create();
if (!m_dataObject || m_dataObject->filenames.isEmpty())
return FileList::create();
RefPtr<FileList> fileList = FileList::create();
for (size_t i = 0; i < m_dataObject->filenames.size(); ++i)
fileList->append(File::create(m_dataObject->filenames.at(i)));
return fileList.release();
}
void ClipboardChromium::setDragImage(CachedImage* image, Node* node, const IntPoint& loc)
{
if (policy() != ClipboardImageWritable && policy() != ClipboardWritable)
return;
if (m_dragImage)
m_dragImage->removeClient(this);
m_dragImage = image;
if (m_dragImage)
m_dragImage->addClient(this);
m_dragLoc = loc;
m_dragImageElement = node;
}
void ClipboardChromium::setDragImage(CachedImage* img, const IntPoint& loc)
{
setDragImage(img, 0, loc);
}
void ClipboardChromium::setDragImageElement(Node* node, const IntPoint& loc)
{
setDragImage(0, node, loc);
}
DragImageRef ClipboardChromium::createDragImage(IntPoint& loc) const
{
DragImageRef result = 0;
if (m_dragImage) {
result = createDragImageFromImage(m_dragImage->image());
loc = m_dragLoc;
}
return result;
}
static String imageToMarkup(const String& url, Element* element)
{
StringBuilder markup;
markup.append("<img src=\"");
markup.append(url);
markup.append("\"");
// Copy over attributes. If we are dragging an image, we expect things like
// the id to be copied as well.
NamedNodeMap* attrs = element->attributes();
unsigned length = attrs->length();
for (unsigned i = 0; i < length; ++i) {
Attribute* attr = attrs->attributeItem(i);
if (attr->localName() == "src")
continue;
markup.append(" ");
markup.append(attr->localName());
markup.append("=\"");
String escapedAttr = attr->value();
escapedAttr.replace("\"", """);
markup.append(escapedAttr);
markup.append("\"");
}
markup.append("/>");
return markup.toString();
}
static CachedImage* getCachedImage(Element* element)
{
// Attempt to pull CachedImage from element
ASSERT(element);
RenderObject* renderer = element->renderer();
if (!renderer || !renderer->isImage())
return 0;
RenderImage* image = toRenderImage(renderer);
if (image->cachedImage() && !image->cachedImage()->errorOccurred())
return image->cachedImage();
return 0;
}
static void writeImageToDataObject(ChromiumDataObject* dataObject, Element* element,
const KURL& url)
{
// Shove image data into a DataObject for use as a file
CachedImage* cachedImage = getCachedImage(element);
if (!cachedImage || !cachedImage->image() || !cachedImage->isLoaded())
return;
SharedBuffer* imageBuffer = cachedImage->image()->data();
if (!imageBuffer || !imageBuffer->size())
return;
dataObject->fileContent = imageBuffer;
// Determine the filename for the file contents of the image. We try to
// use the alt tag if one exists, otherwise we fall back on the suggested
// filename in the http header, and finally we resort to using the filename
// in the URL.
String extension = MIMETypeRegistry::getPreferredExtensionForMIMEType(
cachedImage->response().mimeType());
dataObject->fileExtension = extension.isEmpty() ? "" : "." + extension;
String title = element->getAttribute(altAttr);
if (title.isEmpty())
title = cachedImage->response().suggestedFilename();
title = ClipboardChromium::validateFileName(title, dataObject);
dataObject->fileContentFilename = title + dataObject->fileExtension;
}
void ClipboardChromium::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
{
if (!m_dataObject)
return;
m_dataObject->url = url;
m_dataObject->urlTitle = title;
// Write the bytes in the image to the file format.
writeImageToDataObject(m_dataObject.get(), element, url);
AtomicString imageURL = element->getAttribute(srcAttr);
if (imageURL.isEmpty())
return;
String fullURL = frame->document()->completeURL(deprecatedParseURL(imageURL));
if (fullURL.isEmpty())
return;
// Put img tag on the clipboard referencing the image
m_dataObject->textHtml = imageToMarkup(fullURL, element);
}
void ClipboardChromium::writeURL(const KURL& url, const String& title, Frame*)
{
if (!m_dataObject)
return;
m_dataObject->url = url;
m_dataObject->urlTitle = title;
// The URL can also be used as plain text.
m_dataObject->plainText = url.string();
// The URL can also be used as an HTML fragment.
m_dataObject->textHtml = urlToMarkup(url, title);
m_dataObject->htmlBaseUrl = url;
}
void ClipboardChromium::writeRange(Range* selectedRange, Frame* frame)
{
ASSERT(selectedRange);
if (!m_dataObject)
return;
m_dataObject->textHtml = createMarkup(selectedRange, 0,
AnnotateForInterchange);
m_dataObject->htmlBaseUrl = frame->document()->url();
String str = frame->selectedText();
#if OS(WINDOWS)
replaceNewlinesWithWindowsStyleNewlines(str);
#endif
replaceNBSPWithSpace(str);
m_dataObject->plainText = str;
}
void ClipboardChromium::writePlainText(const String& text)
{
if (!m_dataObject)
return;
String str = text;
#if OS(WINDOWS)
replaceNewlinesWithWindowsStyleNewlines(str);
#endif
replaceNBSPWithSpace(str);
m_dataObject->plainText = str;
}
bool ClipboardChromium::hasData()
{
if (!m_dataObject)
return false;
return m_dataObject->hasData();
}
} // namespace WebCore