| /* |
| * 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) |