ClipboardIOS.mm   [plain text]


/*
 * Copyright (C) 2011 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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.
 */

#import "config.h"
#import "ClipboardIOS.h"

#import "DragData.h"
#import "Editor.h"
#import "EditorClient.h"
#import "FileList.h"
#import "Frame.h"
#import "Pasteboard.h"

#include "SoftLinking.h"
#include <MobileCoreServices/MobileCoreServices.h>

SOFT_LINK_FRAMEWORK(MobileCoreServices)

SOFT_LINK(MobileCoreServices, UTTypeCreatePreferredIdentifierForTag, CFStringRef, (CFStringRef inTagClass, CFStringRef inTag, CFStringRef inConformingToUTI), (inTagClass, inTag, inConformingToUTI))
SOFT_LINK(MobileCoreServices, UTTypeCopyPreferredTagWithClass, CFStringRef, (CFStringRef inUTI, CFStringRef inTagClass), (inUTI, inTagClass))

SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeText, CFStringRef)
SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeURL, CFStringRef)
SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassMIMEType, CFStringRef)

#define kUTTypeText getkUTTypeText()
#define kUTTypeURL  getkUTTypeURL()
#define kUTTypeTIFF getkUTTypeTIFF()
#define kUTTagClassMIMEType getkUTTagClassMIMEType()

namespace WebCore {

PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData*, Frame* frame)
{
    return ClipboardIOS::create(DragAndDrop, policy, frame);
}

ClipboardIOS::ClipboardIOS(ClipboardType clipboardType, ClipboardAccessPolicy policy, Frame* frame)
    : Clipboard(policy, clipboardType)
    , m_frame(frame)
{
    m_changeCount = m_frame->editor().client()->pasteboardChangeCount();
}

ClipboardIOS::~ClipboardIOS()
{
}

bool ClipboardIOS::hasData()
{
    return m_frame->editor().client()->getPasteboardItemsCount() != 0;
}

static String utiTypeFromCocoaType(NSString* type)
{
    RetainPtr<CFStringRef> utiType = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL));
    if (utiType) {
        RetainPtr<CFStringRef> mimeType = adoptCF(UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType));
        if (mimeType)
            return String(mimeType.get());
    }
    return String();
}

static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
{
    String qType = type.stripWhiteSpace();

    if (qType == "Text")
        return (NSString*)kUTTypeText;
    if (qType == "URL")
        return (NSString*)kUTTypeURL;

    // Ignore any trailing charset - JS strings are Unicode, which encapsulates the charset issue.
    if (qType.startsWith(ASCIILiteral("text/plain")))
        return (NSString*)kUTTypeText;
    if (qType == "text/uri-list")
        // Special case because UTI doesn't work with Cocoa's URL type.
        return (NSString*)kUTTypeURL;

    // Try UTI now.
    NSString *pbType = utiTypeFromCocoaType(qType);
    if (pbType)
        return pbType;

    // No mapping, just pass the whole string though.
    return (NSString*)qType;
}

static void addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, NSString* cocoaType)
{
    // UTI may not do these right, so make sure we get the right, predictable result.
    if ([cocoaType isEqualToString:(NSString*)kUTTypeText]) {
        resultTypes.add(ASCIILiteral("text/plain"));
        return;
    }
    if ([cocoaType isEqualToString:(NSString*)kUTTypeURL]) {
        resultTypes.add(ASCIILiteral("text/uri-list"));
        return;
    }
    String utiType = utiTypeFromCocoaType(cocoaType);
    if (!utiType.isEmpty()) {
        resultTypes.add(utiType);
        return;
    }
    // No mapping, just pass the whole string though.
    resultTypes.add(cocoaType);
}

void ClipboardIOS::clearData(const String& type)
{
    if (!canWriteData())
        return;

    // Note UIPasteboard enforces changeCount itself on writing - can't write if not the owner.

    if (RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type)) {
        RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]);
        [representations.get() setValue:0 forKey:cocoaType.get()];
        m_frame->editor().client()->writeDataToPasteboard(representations.get());
    }
}

void ClipboardIOS::clearData()
{
    if (!canWriteData())
        return;

    RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]);
    m_frame->editor().client()->writeDataToPasteboard(representations.get());
}

String ClipboardIOS::getData(const String& type) const
{
    if (!canReadData())
        return String();

    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
    NSString *cocoaValue = nil;

    // Grab the value off the pasteboard corresponding to the cocoaType.
    RetainPtr<NSArray> pasteboardItem = m_frame->editor().client()->readDataFromPasteboard(cocoaType.get(), 0);

    if ([pasteboardItem.get() count] == 0)
        return String();

    if ([cocoaType.get() isEqualToString:(NSString*)kUTTypeURL]) {
        id value = [pasteboardItem.get() objectAtIndex:0];
        if (![value isKindOfClass:[NSURL class]]) {
            ASSERT([value isKindOfClass:[NSURL class]]);
            return String();
        }
        NSURL* absoluteURL = (NSURL*)value;

        if (absoluteURL)
            cocoaValue = [absoluteURL absoluteString];
    } else if ([cocoaType.get() isEqualToString:(NSString*)kUTTypeText]) {
        id value = [pasteboardItem.get() objectAtIndex:0];
        if (![value isKindOfClass:[NSString class]]) {
            ASSERT([value isKindOfClass:[NSString class]]);
            return String();
        }

        cocoaValue = [(NSString*)value precomposedStringWithCanonicalMapping];
    } else if (cocoaType) {
        ASSERT([pasteboardItem.get() count] == 1);
        id value = [pasteboardItem.get() objectAtIndex:0];
        if (![value isKindOfClass:[NSData class]]) {
            ASSERT([value isKindOfClass:[NSData class]]);
            return String();
        }
        cocoaValue = [[[NSString alloc] initWithData:(NSData *)value encoding:NSUTF8StringEncoding] autorelease];
    }

    // Enforce changeCount ourselves for security.  We check after reading instead of before to be
    // sure it doesn't change between our testing the change count and accessing the data.
    if (cocoaValue && m_changeCount == m_frame->editor().client()->pasteboardChangeCount())
        return cocoaValue;

    return String();
}

bool ClipboardIOS::setData(const String& type, const String& data)
{
    if (!canWriteData())
        return false;

    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
    NSString *cocoaData = data;

    RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]);

    if ([cocoaType.get() isEqualToString:(NSString*)kUTTypeURL]) {
        RetainPtr<NSURL> url = adoptNS([[NSURL alloc] initWithString:cocoaData]);
        [representations.get() setValue:url.get() forKey:(NSString*)kUTTypeURL];
        m_frame->editor().client()->writeDataToPasteboard(representations.get());
        return true;
    }

    if (cocoaType) {
        // Everything else we know of goes on the pboard as the original string
        // we received as parameter.
        [representations.get() setValue:cocoaData forKey:cocoaType.get()];
        m_frame->editor().client()->writeDataToPasteboard(representations.get());
        return true;
    }

    return false;
}

ListHashSet<String> ClipboardIOS::types() const
{
    if (!canReadTypes())
        return ListHashSet<String>();

    NSArray* types = Pasteboard::supportedPasteboardTypes();

    // Enforce changeCount ourselves for security.  We check after reading instead of before to be
    // sure it doesn't change between our testing the change count and accessing the data.
    if (m_changeCount != m_frame->editor().client()->pasteboardChangeCount())
        return ListHashSet<String>();

    ListHashSet<String> result;
    NSUInteger count = [types count];
    for (NSUInteger i = 0; i < count; i++) {
        NSString* pbType = [types objectAtIndex:i];
        addHTMLClipboardTypesForCocoaType(result, pbType);
    }

    return result;
}

PassRefPtr<FileList> ClipboardIOS::files() const
{
    return 0;
}

void ClipboardIOS::writeRange(Range* range, Frame* frame)
{
    ASSERT(range);
    ASSERT(frame);
    Pasteboard::generalPasteboard()->writeSelection(range, frame->editor().smartInsertDeleteEnabled() && frame->selection()->granularity() == WordGranularity, frame);
}

void ClipboardIOS::writePlainText(const String& text)
{
    Pasteboard::generalPasteboard()->writePlainText(text, m_frame);
}

void ClipboardIOS::writeURL(const KURL&, const String&, Frame*)
{
}

#if ENABLE(DRAG_SUPPORT)
void ClipboardIOS::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
{
    ASSERT(frame);
    if (Page* page = frame->page())
        page->dragController()->client()->declareAndWriteDragImage(m_pasteboard.get(), kit(element), url, title, frame);
}
#endif // ENABLE(DRAG_SUPPORT)

DragImageRef ClipboardIOS::createDragImage(IntPoint&) const
{
    return 0;
}

void ClipboardIOS::setDragImage(CachedImage*, const IntPoint&)
{
}

void ClipboardIOS::setDragImageElement(Node *, const IntPoint&)
{
}

}