blob: 6a3b79d2030cd9fe0fb1c2a0bc98c6e360cb2cb0 [file] [log] [blame]
/*
* Copyright (C) 2013-2017 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. ``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
* 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 "PlatformPasteboard.h"
#if PLATFORM(IOS_FAMILY)
#import "ColorCocoa.h"
#import "Image.h"
#import "Pasteboard.h"
#import "RuntimeApplicationChecks.h"
#import "SharedBuffer.h"
#import "UTIUtilities.h"
#import "WebItemProviderPasteboard.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <UIKit/UIColor.h>
#import <UIKit/UIImage.h>
#import <UIKit/UIPasteboard.h>
#import <pal/spi/ios/UIKitSPI.h>
#import <wtf/ListHashSet.h>
#import <wtf/URL.h>
#import <wtf/cocoa/NSURLExtras.h>
#import <wtf/cocoa/VectorCocoa.h>
#import <wtf/text/StringHash.h>
#import <pal/ios/UIKitSoftLink.h>
#define PASTEBOARD_SUPPORTS_ITEM_PROVIDERS (PLATFORM(IOS_FAMILY) && !(PLATFORM(WATCHOS) || PLATFORM(APPLETV)))
#define PASTEBOARD_SUPPORTS_PRESENTATION_STYLE_AND_TEAM_DATA (PASTEBOARD_SUPPORTS_ITEM_PROVIDERS && !PLATFORM(MACCATALYST))
#define NSURL_SUPPORTS_TITLE (!PLATFORM(MACCATALYST))
namespace WebCore {
PlatformPasteboard::PlatformPasteboard()
: m_pasteboard([PAL::getUIPasteboardClass() generalPasteboard])
{
}
#if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS
PlatformPasteboard::PlatformPasteboard(const String& name)
{
if (name == Pasteboard::nameOfDragPasteboard())
m_pasteboard = [WebItemProviderPasteboard sharedInstance];
else
m_pasteboard = [PAL::getUIPasteboardClass() generalPasteboard];
}
#else
PlatformPasteboard::PlatformPasteboard(const String&)
: m_pasteboard([PAL::getUIPasteboardClass() generalPasteboard])
{
}
#endif
void PlatformPasteboard::getTypes(Vector<String>& types) const
{
types = makeVector<String>([m_pasteboard pasteboardTypes]);
}
RefPtr<SharedBuffer> PlatformPasteboard::bufferForType(const String& type) const
{
if (NSData *data = [m_pasteboard dataForPasteboardType:type])
return SharedBuffer::create(data);
return nullptr;
}
void PlatformPasteboard::performAsDataOwner(DataOwnerType type, Function<void()>&& actions)
{
#if HAVE(PASTEBOARD_DATA_OWNER)
auto dataOwner = _UIDataOwnerUndefined;
switch (type) {
case DataOwnerType::Undefined:
dataOwner = _UIDataOwnerUndefined;
break;
case DataOwnerType::User:
dataOwner = _UIDataOwnerUser;
break;
case DataOwnerType::Enterprise:
dataOwner = _UIDataOwnerEnterprise;
break;
case DataOwnerType::Shared:
dataOwner = _UIDataOwnerShared;
break;
}
[PAL::getUIPasteboardClass() _performAsDataOwner:dataOwner block:^{
actions();
}];
#else
UNUSED_PARAM(type);
actions();
#endif
}
void PlatformPasteboard::getPathnamesForType(Vector<String>&, const String&) const
{
}
int PlatformPasteboard::numberOfFiles() const
{
return [m_pasteboard respondsToSelector:@selector(numberOfFiles)] ? [m_pasteboard numberOfFiles] : 0;
}
static bool shouldTreatAtLeastOneTypeAsFile(NSArray<NSString *> *platformTypes)
{
for (NSString *type in platformTypes) {
if (Pasteboard::shouldTreatCocoaTypeAsFile(type))
return true;
}
return false;
}
#if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS
static const char *safeTypeForDOMToReadAndWriteForPlatformType(const String& platformType, PlatformPasteboard::IncludeImageTypes includeImageTypes)
{
auto cfType = platformType.createCFString();
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (UTTypeConformsTo(cfType.get(), kUTTypePlainText))
return "text/plain"_s;
if (UTTypeConformsTo(cfType.get(), kUTTypeHTML) || UTTypeConformsTo(cfType.get(), (CFStringRef)WebArchivePboardType)
|| UTTypeConformsTo(cfType.get(), kUTTypeRTF) || UTTypeConformsTo(cfType.get(), kUTTypeFlatRTFD))
return "text/html"_s;
if (UTTypeConformsTo(cfType.get(), kUTTypeURL))
return "text/uri-list"_s;
if (includeImageTypes == PlatformPasteboard::IncludeImageTypes::Yes && UTTypeConformsTo(cfType.get(), kUTTypePNG))
return "image/png"_s;
ALLOW_DEPRECATED_DECLARATIONS_END
return nullptr;
}
static Vector<String> webSafeTypes(NSArray<NSString *> *platformTypes, PlatformPasteboard::IncludeImageTypes includeImageTypes, Function<bool()>&& shouldAvoidExposingURLType)
{
ListHashSet<String> domPasteboardTypes;
for (NSString *type in platformTypes) {
if ([type isEqualToString:@(PasteboardCustomData::cocoaType().characters())])
continue;
if (Pasteboard::isSafeTypeForDOMToReadAndWrite(type)) {
domPasteboardTypes.add(type);
continue;
}
if (auto* coercedType = safeTypeForDOMToReadAndWriteForPlatformType(type, includeImageTypes)) {
auto domTypeAsString = String::fromUTF8(coercedType);
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (domTypeAsString == "text/uri-list"_s && ([platformTypes containsObject:(__bridge NSString *)kUTTypeFileURL] || shouldAvoidExposingURLType()))
continue;
ALLOW_DEPRECATED_DECLARATIONS_END
domPasteboardTypes.add(WTFMove(domTypeAsString));
}
}
return copyToVector(domPasteboardTypes);
}
#if PASTEBOARD_SUPPORTS_PRESENTATION_STYLE_AND_TEAM_DATA
static PasteboardItemPresentationStyle pasteboardItemPresentationStyle(UIPreferredPresentationStyle style)
{
switch (style) {
case UIPreferredPresentationStyleUnspecified:
return PasteboardItemPresentationStyle::Unspecified;
case UIPreferredPresentationStyleInline:
return PasteboardItemPresentationStyle::Inline;
case UIPreferredPresentationStyleAttachment:
return PasteboardItemPresentationStyle::Attachment;
default:
ASSERT_NOT_REACHED();
return PasteboardItemPresentationStyle::Unspecified;
}
}
#endif // PASTEBOARD_SUPPORTS_PRESENTATION_STYLE_AND_TEAM_DATA
std::optional<PasteboardItemInfo> PlatformPasteboard::informationForItemAtIndex(size_t index, int64_t changeCount)
{
if (index >= static_cast<NSUInteger>([m_pasteboard numberOfItems]))
return std::nullopt;
if (this->changeCount() != changeCount)
return std::nullopt;
PasteboardItemInfo info;
NSItemProvider *itemProvider = [[m_pasteboard itemProviders] objectAtIndex:index];
if ([m_pasteboard respondsToSelector:@selector(fileUploadURLsAtIndex:fileTypes:)]) {
NSArray<NSString *> *fileTypes = nil;
NSArray *urls = [m_pasteboard fileUploadURLsAtIndex:index fileTypes:&fileTypes];
ASSERT(fileTypes.count == urls.count);
info.pathsForFileUpload.reserveInitialCapacity(urls.count);
for (NSURL *url in urls)
info.pathsForFileUpload.uncheckedAppend(url.path);
info.platformTypesForFileUpload.reserveInitialCapacity(fileTypes.count);
for (NSString *fileType in fileTypes)
info.platformTypesForFileUpload.uncheckedAppend(fileType);
} else {
NSArray *fileTypes = itemProvider.web_fileUploadContentTypes;
info.platformTypesForFileUpload.reserveInitialCapacity(fileTypes.count);
info.pathsForFileUpload.reserveInitialCapacity(fileTypes.count);
for (NSString *fileType in fileTypes) {
info.platformTypesForFileUpload.uncheckedAppend(fileType);
info.pathsForFileUpload.uncheckedAppend({ });
}
}
#if PASTEBOARD_SUPPORTS_PRESENTATION_STYLE_AND_TEAM_DATA
info.preferredPresentationStyle = pasteboardItemPresentationStyle(itemProvider.preferredPresentationStyle);
#endif
if (!CGSizeEqualToSize(itemProvider.preferredPresentationSize, CGSizeZero)) {
auto adjustedPreferredPresentationHeight = [](auto height) -> std::optional<double> {
if (!IOSApplication::isMobileMail() && !IOSApplication::isMailCompositionService())
return { height };
// Mail's max-width: 100%; default style is in conflict with the preferred presentation size and can lead to unexpectedly stretched images. Not setting the height forces layout to preserve the aspect ratio.
return { };
};
info.preferredPresentationSize = PresentationSize { itemProvider.preferredPresentationSize.width, adjustedPreferredPresentationHeight(itemProvider.preferredPresentationSize.height) };
}
info.containsFileURLAndFileUploadContent = itemProvider.web_containsFileURLAndFileUploadContent;
info.suggestedFileName = itemProvider.suggestedName;
NSArray<NSString *> *registeredTypeIdentifiers = itemProvider.registeredTypeIdentifiers;
info.platformTypesByFidelity.reserveInitialCapacity(registeredTypeIdentifiers.count);
for (NSString *typeIdentifier in registeredTypeIdentifiers) {
info.platformTypesByFidelity.uncheckedAppend(typeIdentifier);
CFStringRef cfTypeIdentifier = (CFStringRef)typeIdentifier;
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (!UTTypeIsDeclared(cfTypeIdentifier))
continue;
if (UTTypeConformsTo(cfTypeIdentifier, kUTTypeText))
continue;
if (UTTypeConformsTo(cfTypeIdentifier, kUTTypeURL))
continue;
if (UTTypeConformsTo(cfTypeIdentifier, kUTTypeRTFD))
continue;
if (UTTypeConformsTo(cfTypeIdentifier, kUTTypeFlatRTFD))
continue;
ALLOW_DEPRECATED_DECLARATIONS_END
info.isNonTextType = true;
}
info.webSafeTypesByFidelity = webSafeTypes(registeredTypeIdentifiers, IncludeImageTypes::Yes, [&] {
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
return shouldTreatAtLeastOneTypeAsFile(registeredTypeIdentifiers) && !Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(readString(index, kUTTypeURL));
ALLOW_DEPRECATED_DECLARATIONS_END
});
return info;
}
#else
std::optional<PasteboardItemInfo> PlatformPasteboard::informationForItemAtIndex(size_t, int64_t)
{
return std::nullopt;
}
#endif
static bool pasteboardMayContainFilePaths(id<AbstractPasteboard> pasteboard)
{
#if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS
if ([pasteboard isKindOfClass:[WebItemProviderPasteboard class]])
return false;
#endif
return shouldTreatAtLeastOneTypeAsFile(pasteboard.pasteboardTypes);
}
String PlatformPasteboard::stringForType(const String& type) const
{
auto result = readString(0, type);
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (pasteboardMayContainFilePaths(m_pasteboard.get()) && type == String { kUTTypeURL }) {
if (!Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(result))
result = { };
}
ALLOW_DEPRECATED_DECLARATIONS_END
return result;
}
Color PlatformPasteboard::color()
{
NSData *data = [m_pasteboard dataForPasteboardType:UIColorPboardType];
UIColor *uiColor = [NSKeyedUnarchiver unarchivedObjectOfClass:PAL::getUIColorClass() fromData:data error:nil];
return roundAndClampToSRGBALossy(uiColor.CGColor);
}
URL PlatformPasteboard::url()
{
return URL();
}
int64_t PlatformPasteboard::copy(const String&)
{
return 0;
}
int64_t PlatformPasteboard::addTypes(const Vector<String>&)
{
return 0;
}
int64_t PlatformPasteboard::setTypes(const Vector<String>&)
{
return 0;
}
int64_t PlatformPasteboard::setBufferForType(SharedBuffer*, const String&)
{
return 0;
}
int64_t PlatformPasteboard::setURL(const PasteboardURL&)
{
return 0;
}
int64_t PlatformPasteboard::setStringForType(const String&, const String&)
{
return 0;
}
int64_t PlatformPasteboard::changeCount() const
{
return [m_pasteboard changeCount];
}
String PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(const String& domType, IncludeImageTypes includeImageTypes)
{
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (domType == "text/plain"_s)
return kUTTypePlainText;
if (domType == "text/html"_s)
return kUTTypeHTML;
if (domType == "text/uri-list"_s)
return kUTTypeURL;
if (includeImageTypes == IncludeImageTypes::Yes && domType == "image/png"_s)
return kUTTypePNG;
ALLOW_DEPRECATED_DECLARATIONS_END
return { };
}
#if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS
static NSString *webIOSPastePboardType = @"iOS rich content paste pasteboard type";
static void registerItemsToPasteboard(NSArray<WebItemProviderRegistrationInfoList *> *itemLists, id <AbstractPasteboard> pasteboard)
{
#if PLATFORM(MACCATALYST)
// In macCatalyst, -[UIPasteboard setItemProviders:] is not yet supported, so we fall back to setting an item dictionary when
// populating the pasteboard upon copy.
if ([pasteboard isKindOfClass:PAL::getUIPasteboardClass()]) {
auto itemDictionaries = adoptNS([[NSMutableArray alloc] initWithCapacity:itemLists.count]);
for (WebItemProviderRegistrationInfoList *representationsToRegister in itemLists) {
auto itemDictionary = adoptNS([[NSMutableDictionary alloc] initWithCapacity:representationsToRegister.numberOfItems]);
[representationsToRegister enumerateItems:[itemDictionary] (id <WebItemProviderRegistrar> item, NSUInteger) {
if ([item respondsToSelector:@selector(typeIdentifierForClient)] && [item respondsToSelector:@selector(dataForClient)])
[itemDictionary setObject:item.dataForClient forKey:item.typeIdentifierForClient];
}];
[itemDictionaries addObject:itemDictionary.get()];
}
[pasteboard setItems:itemDictionaries.get()];
return;
}
#endif // PLATFORM(MACCATALYST)
auto itemProviders = adoptNS([[NSMutableArray alloc] initWithCapacity:itemLists.count]);
for (WebItemProviderRegistrationInfoList *representationsToRegister in itemLists) {
if (auto *itemProvider = representationsToRegister.itemProvider)
[itemProviders addObject:itemProvider];
}
[pasteboard setItemProviders:itemProviders.get()];
if ([pasteboard respondsToSelector:@selector(stageRegistrationLists:)])
[pasteboard stageRegistrationLists:itemLists];
}
static void registerItemToPasteboard(WebItemProviderRegistrationInfoList *representationsToRegister, id <AbstractPasteboard> pasteboard)
{
registerItemsToPasteboard(@[ representationsToRegister ], pasteboard);
}
int64_t PlatformPasteboard::setColor(const Color& color)
{
auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
[representationsToRegister addData:[NSKeyedArchiver archivedDataWithRootObject:cocoaColor(color).get() requiringSecureCoding:NO error:nil] forType:UIColorPboardType];
registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
return 0;
}
static void addRepresentationsForPlainText(WebItemProviderRegistrationInfoList *itemsToRegister, const String& plainText)
{
if (plainText.isEmpty())
return;
NSURL *platformURL = [NSURL URLWithString:plainText];
if (URL(platformURL).isValid())
[itemsToRegister addRepresentingObject:platformURL];
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
[itemsToRegister addData:[(NSString *)plainText dataUsingEncoding:NSUTF8StringEncoding] forType:(NSString *)kUTTypeUTF8PlainText];
ALLOW_DEPRECATED_DECLARATIONS_END
}
bool PlatformPasteboard::allowReadingURLAtIndex(const URL& url, int index) const
{
NSItemProvider *itemProvider = (NSUInteger)index < [m_pasteboard itemProviders].count ? [[m_pasteboard itemProviders] objectAtIndex:index] : nil;
for (NSString *type in itemProvider.registeredTypeIdentifiers) {
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (UTTypeConformsTo((CFStringRef)type, kUTTypeURL))
return true;
ALLOW_DEPRECATED_DECLARATIONS_END
}
return url.isValid();
}
void PlatformPasteboard::write(const PasteboardWebContent& content)
{
auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
#if !PLATFORM(MACCATALYST)
[representationsToRegister addData:[webIOSPastePboardType dataUsingEncoding:NSUTF8StringEncoding] forType:webIOSPastePboardType];
#endif
ASSERT(content.clientTypes.size() == content.clientData.size());
for (size_t i = 0, size = content.clientTypes.size(); i < size; ++i)
[representationsToRegister addData:content.clientData[i]->makeContiguous()->createNSData().get() forType:content.clientTypes[i]];
if (content.dataInWebArchiveFormat) {
auto webArchiveData = content.dataInWebArchiveFormat->createNSData();
#if PLATFORM(MACCATALYST)
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
NSString *webArchiveType = (__bridge NSString *)kUTTypeWebArchive;
ALLOW_DEPRECATED_DECLARATIONS_END
#else
// FIXME: We should additionally register "com.apple.webarchive" once <rdar://problem/46830277> is fixed.
NSString *webArchiveType = WebArchivePboardType;
#endif
[representationsToRegister addData:webArchiveData.get() forType:webArchiveType];
}
if (content.dataInAttributedStringFormat) {
if (NSAttributedString *attributedString = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObject:NSAttributedString.class] fromData:content.dataInAttributedStringFormat->makeContiguous()->createNSData().get() error:nullptr])
[representationsToRegister addRepresentingObject:attributedString];
}
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (content.dataInRTFDFormat)
[representationsToRegister addData:content.dataInRTFDFormat->createNSData().get() forType:(NSString *)kUTTypeFlatRTFD];
if (content.dataInRTFFormat)
[representationsToRegister addData:content.dataInRTFFormat->createNSData().get() forType:(NSString *)kUTTypeRTF];
if (!content.dataInHTMLFormat.isEmpty()) {
NSData *htmlAsData = [(NSString *)content.dataInHTMLFormat dataUsingEncoding:NSUTF8StringEncoding];
[representationsToRegister addData:htmlAsData forType:(NSString *)kUTTypeHTML];
}
ALLOW_DEPRECATED_DECLARATIONS_END
if (!content.dataInStringFormat.isEmpty())
addRepresentationsForPlainText(representationsToRegister.get(), content.dataInStringFormat);
PasteboardCustomData customData;
customData.setOrigin(content.contentOrigin);
[representationsToRegister addData:customData.createSharedBuffer()->createNSData().get() forType:@(PasteboardCustomData::cocoaType().characters())];
registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
}
void PlatformPasteboard::write(const PasteboardImage& pasteboardImage)
{
auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
auto& types = pasteboardImage.clientTypes;
auto& data = pasteboardImage.clientData;
ASSERT(types.size() == data.size());
for (size_t i = 0, size = types.size(); i < size; ++i)
[representationsToRegister addData:data[i]->createNSData().get() forType:types[i]];
if (pasteboardImage.resourceData && !pasteboardImage.resourceMIMEType.isEmpty()) {
auto utiOrMIMEType = pasteboardImage.resourceMIMEType;
if (!isDeclaredUTI(utiOrMIMEType))
utiOrMIMEType = UTIFromMIMEType(utiOrMIMEType);
auto imageData = pasteboardImage.resourceData->makeContiguous()->createNSData();
[representationsToRegister addData:imageData.get() forType:(NSString *)utiOrMIMEType];
[representationsToRegister setPreferredPresentationSize:pasteboardImage.imageSize];
[representationsToRegister setSuggestedName:pasteboardImage.suggestedName];
}
// FIXME: When writing a PasteboardImage, we currently always place the image data at a higer fidelity than the
// associated image URL. However, in the case of an image enclosed by an anchor, we might want to consider the
// the URL (i.e. the anchor's href attribute) to be a higher fidelity representation.
auto& pasteboardURL = pasteboardImage.url;
if (NSURL *nsURL = pasteboardURL.url) {
#if NSURL_SUPPORTS_TITLE
nsURL._title = pasteboardURL.title.isEmpty() ? WTF::userVisibleString(pasteboardURL.url) : (NSString *)pasteboardURL.title;
#endif
[representationsToRegister addRepresentingObject:nsURL];
}
registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
}
void PlatformPasteboard::write(const String& pasteboardType, const String& text)
{
auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
[representationsToRegister setPreferredPresentationStyle:WebPreferredPresentationStyleInline];
NSString *pasteboardTypeAsNSString = pasteboardType;
if (!text.isEmpty() && pasteboardTypeAsNSString.length) {
auto pasteboardTypeAsCFString = (CFStringRef)pasteboardTypeAsNSString;
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (UTTypeConformsTo(pasteboardTypeAsCFString, kUTTypeURL) || UTTypeConformsTo(pasteboardTypeAsCFString, kUTTypeText))
addRepresentationsForPlainText(representationsToRegister.get(), text);
else
[representationsToRegister addData:[pasteboardTypeAsNSString dataUsingEncoding:NSUTF8StringEncoding] forType:pasteboardType];
ALLOW_DEPRECATED_DECLARATIONS_END
}
registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
}
void PlatformPasteboard::write(const PasteboardURL& url)
{
auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
[representationsToRegister setPreferredPresentationStyle:WebPreferredPresentationStyleInline];
if (NSURL *nsURL = url.url) {
#if NSURL_SUPPORTS_TITLE
if (!url.title.isEmpty())
nsURL._title = url.title;
#endif
[representationsToRegister addRepresentingObject:nsURL];
[representationsToRegister addRepresentingObject:(NSString *)url.url.string()];
}
registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
}
static const char originKeyForTeamData[] = "com.apple.WebKit.drag-and-drop-team-data.origin";
static const char customTypesKeyForTeamData[] = "com.apple.WebKit.drag-and-drop-team-data.custom-types";
Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String& origin) const
{
ListHashSet<String> domPasteboardTypes;
#if PASTEBOARD_SUPPORTS_PRESENTATION_STYLE_AND_TEAM_DATA
for (NSItemProvider *provider in [m_pasteboard itemProviders]) {
if (!provider.teamData.length)
continue;
NSSet *classes = [NSSet setWithObjects:NSDictionary.class, NSString.class, NSArray.class, nil];
id teamDataObject = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:provider.teamData error:nullptr];
if (![teamDataObject isKindOfClass:NSDictionary.class])
continue;
id originInTeamData = [teamDataObject objectForKey:@(originKeyForTeamData)];
if (![originInTeamData isKindOfClass:NSString.class])
continue;
if (String((NSString *)originInTeamData) != origin)
continue;
id customTypes = [teamDataObject objectForKey:@(customTypesKeyForTeamData)];
if (![customTypes isKindOfClass:NSArray.class])
continue;
for (NSString *type in customTypes)
domPasteboardTypes.add(type);
}
#endif // PASTEBOARD_SUPPORTS_PRESENTATION_STYLE_AND_TEAM_DATA
if (NSData *serializedCustomData = [m_pasteboard dataForPasteboardType:@(PasteboardCustomData::cocoaType().characters())]) {
auto data = PasteboardCustomData::fromSharedBuffer(SharedBuffer::create(serializedCustomData).get());
if (data.origin() == origin) {
for (auto& type : data.orderedTypes())
domPasteboardTypes.add(type);
}
}
auto webSafePasteboardTypes = webSafeTypes([m_pasteboard pasteboardTypes], IncludeImageTypes::No, [&] {
BOOL ableToDetermineProtocolOfPasteboardURL = ![m_pasteboard isKindOfClass:[WebItemProviderPasteboard class]];
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
return ableToDetermineProtocolOfPasteboardURL && stringForType(kUTTypeURL).isEmpty();
ALLOW_DEPRECATED_DECLARATIONS_END
});
for (auto& type : webSafePasteboardTypes)
domPasteboardTypes.add(type);
return copyToVector(domPasteboardTypes);
}
static RetainPtr<WebItemProviderRegistrationInfoList> createItemProviderRegistrationList(const PasteboardCustomData& data)
{
auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
[representationsToRegister setPreferredPresentationStyle:WebPreferredPresentationStyleInline];
if (data.hasSameOriginCustomData() || !data.origin().isEmpty()) {
if (auto serializedSharedBuffer = data.createSharedBuffer()->createNSData()) {
// We stash the list of supplied pasteboard types in teamData here for compatibility with drag and drop.
// Since the contents of item providers cannot be loaded prior to drop, but the pasteboard types are
// contained within the custom data blob and we need to vend them to the page when firing `dragover`
// events, we need an additional in-memory representation of the pasteboard types array that contains
// all of the custom types. We use the teamData property, available on NSItemProvider on iOS, to store
// this information, since the contents of teamData are immediately available prior to the drop.
NSDictionary *teamDataDictionary = @{ @(originKeyForTeamData) : data.origin(), @(customTypesKeyForTeamData) : createNSArray(data.orderedTypes()).get() };
if (NSData *teamData = [NSKeyedArchiver archivedDataWithRootObject:teamDataDictionary requiringSecureCoding:YES error:nullptr]) {
[representationsToRegister setTeamData:teamData];
[representationsToRegister addData:serializedSharedBuffer.get() forType:@(PasteboardCustomData::cocoaType().characters())];
}
}
}
data.forEachPlatformStringOrBuffer([&] (auto& type, auto& value) {
auto cocoaType = PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type, PlatformPasteboard::IncludeImageTypes::Yes);
if (cocoaType.isEmpty())
return;
if (std::holds_alternative<String>(value)) {
if (std::get<String>(value).isNull())
return;
NSString *nsStringValue = std::get<String>(value);
auto cfType = cocoaType.createCFString();
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (UTTypeConformsTo(cfType.get(), kUTTypeURL))
[representationsToRegister addRepresentingObject:[NSURL URLWithString:nsStringValue]];
else if (UTTypeConformsTo(cfType.get(), kUTTypePlainText))
[representationsToRegister addRepresentingObject:nsStringValue];
else
[representationsToRegister addData:[nsStringValue dataUsingEncoding:NSUTF8StringEncoding] forType:(NSString *)cocoaType];
ALLOW_DEPRECATED_DECLARATIONS_END
return;
}
auto buffer = std::get<Ref<SharedBuffer>>(value);
[representationsToRegister addData:buffer->createNSData().get() forType:(NSString *)cocoaType];
});
return representationsToRegister;
}
int64_t PlatformPasteboard::write(const Vector<PasteboardCustomData>& itemData)
{
auto registrationLists = createNSArray(itemData, [] (auto& data) {
return createItemProviderRegistrationList(data);
});
registerItemsToPasteboard(registrationLists.get(), m_pasteboard.get());
return [m_pasteboard changeCount];
}
#else
int64_t PlatformPasteboard::setColor(const Color&)
{
return 0;
}
bool PlatformPasteboard::allowReadingURLAtIndex(const URL&, int) const
{
return false;
}
void PlatformPasteboard::write(const PasteboardWebContent&)
{
}
void PlatformPasteboard::write(const PasteboardImage&)
{
}
void PlatformPasteboard::write(const String&, const String&)
{
}
void PlatformPasteboard::write(const PasteboardURL&)
{
}
Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String&) const
{
return { };
}
int64_t PlatformPasteboard::write(const Vector<PasteboardCustomData>&)
{
return 0;
}
#endif
int PlatformPasteboard::count() const
{
return [m_pasteboard numberOfItems];
}
Vector<String> PlatformPasteboard::allStringsForType(const String& type) const
{
auto numberOfItems = count();
Vector<String> strings;
strings.reserveInitialCapacity(numberOfItems);
for (int index = 0; index < numberOfItems; ++index) {
String value = readString(index, type);
if (!value.isEmpty())
strings.uncheckedAppend(WTFMove(value));
}
return strings;
}
RefPtr<SharedBuffer> PlatformPasteboard::readBuffer(std::optional<size_t> index, const String& type) const
{
if (!index)
return bufferForType(type);
NSInteger integerIndex = *index;
if (integerIndex < 0 || integerIndex >= [m_pasteboard numberOfItems])
return nullptr;
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:integerIndex];
RetainPtr<NSArray> pasteboardItem = [m_pasteboard dataForPasteboardType:type inItemSet:indexSet];
if (![pasteboardItem count])
return nullptr;
return SharedBuffer::create([pasteboardItem objectAtIndex:0]);
}
String PlatformPasteboard::readString(size_t index, const String& type) const
{
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (type == String(kUTTypeURL)) {
String title;
return [(NSURL *)readURL(index, title) absoluteString];
}
if ((NSInteger)index < 0 || (NSInteger)index >= [m_pasteboard numberOfItems])
return { };
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:index];
auto value = retainPtr([m_pasteboard valuesForPasteboardType:type inItemSet:indexSet].firstObject ?: [m_pasteboard dataForPasteboardType:type inItemSet:indexSet].firstObject);
if (!value)
return { };
if ([value isKindOfClass:[NSData class]])
value = adoptNS([[NSString alloc] initWithData:(NSData *)value.get() encoding:NSUTF8StringEncoding]);
if (type == String(kUTTypePlainText) || type == String(kUTTypeHTML)) {
ASSERT([value isKindOfClass:[NSString class]]);
return [value isKindOfClass:[NSString class]] ? value.get() : nil;
}
if (type == String(kUTTypeText)) {
ASSERT([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSAttributedString class]]);
if ([value isKindOfClass:[NSString class]])
return value.get();
if ([value isKindOfClass:[NSAttributedString class]])
return [(NSAttributedString *)value string];
}
ALLOW_DEPRECATED_DECLARATIONS_END
return String();
}
URL PlatformPasteboard::readURL(size_t index, String& title) const
{
if ((NSInteger)index < 0 || (NSInteger)index >= [m_pasteboard numberOfItems])
return { };
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
id value = [m_pasteboard valuesForPasteboardType:(__bridge NSString *)kUTTypeURL inItemSet:[NSIndexSet indexSetWithIndex:index]].firstObject;
ALLOW_DEPRECATED_DECLARATIONS_END
if (!value)
return { };
ASSERT([value isKindOfClass:[NSURL class]]);
if (![value isKindOfClass:[NSURL class]])
return { };
NSURL *url = (NSURL *)value;
if (!allowReadingURLAtIndex(url, index))
return { };
#if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS && NSURL_SUPPORTS_TITLE
title = [url _title];
#else
UNUSED_PARAM(title);
#endif
return url;
}
void PlatformPasteboard::updateSupportedTypeIdentifiers(const Vector<String>& types)
{
if (![m_pasteboard respondsToSelector:@selector(updateSupportedTypeIdentifiers:)])
return;
[m_pasteboard updateSupportedTypeIdentifiers:createNSArray(types).get()];
}
int64_t PlatformPasteboard::write(const PasteboardCustomData& data)
{
return write(Vector<PasteboardCustomData> { data });
}
bool PlatformPasteboard::containsURLStringSuitableForLoading()
{
Vector<String> types;
getTypes(types);
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (!types.contains(String(kUTTypeURL)))
return false;
auto urlString = stringForType(kUTTypeURL);
ALLOW_DEPRECATED_DECLARATIONS_END
if (urlString.isEmpty()) {
// On iOS, we don't get access to the contents of NSItemProviders until we perform the drag operation.
// Thus, we consider DragData to contain an URL if it contains the `public.url` UTI type. Later down the
// road, when we perform the drag operation, we can then check if the URL's protocol is http or https,
// and if it isn't, we bail out of page navigation.
return true;
}
return URL { [NSURL URLWithString:urlString] }.protocolIsInHTTPFamily();
}
}
#endif // PLATFORM(IOS_FAMILY)