/* * Copyright (C) 2005, 2006, 2007 Apple, Inc. All rights reserved. * (C) 2007 Graham Dennis (graham.dennis@gmail.com) * (C) 2007 Eric Seidel * * 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. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "DumpRenderTreePasteboard.h" #if PLATFORM(MAC) #import "DumpRenderTreeMac.h" #import "NSPasteboardAdditions.h" #import #import #import #import #import #import @interface LocalPasteboard : NSPasteboard { RetainPtr _owner; RetainPtr _pasteboardName; RetainPtr> _cachedPasteboardItems; NSInteger _changeCount; ListHashSet, WTF::RetainPtrObjectHash> _types; ListHashSet, WTF::RetainPtrObjectHash> _originalTypes; HashMap, RetainPtr, WTF::RetainPtrObjectHash, WTF::RetainPtrObjectHashTraits> _data; } -(id)initWithName:(NSString *)name; @end static Lock localPasteboardsLock; static RetainPtr localPasteboards WTF_GUARDED_BY_LOCK(localPasteboardsLock); @implementation DumpRenderTreePasteboard // Return a local pasteboard so we don't disturb the real pasteboards when running tests. + (NSPasteboard *)_pasteboardWithName:(NSString *)name { Locker locker { localPasteboardsLock }; static uint64_t number WTF_GUARDED_BY_LOCK(localPasteboardsLock) = 0; if (!name) name = [NSString stringWithFormat:@"LocalPasteboard%llu", ++number]; if (!localPasteboards) localPasteboards = adoptNS([[NSMutableDictionary alloc] init]); if (LocalPasteboard *pasteboard = [localPasteboards objectForKey:name]) return pasteboard; auto pasteboard = adoptNS([[LocalPasteboard alloc] initWithName:name]); [localPasteboards setObject:pasteboard.get() forKey:name]; return pasteboard.get(); } + (void)releaseLocalPasteboards { Locker locker { localPasteboardsLock }; localPasteboards = nil; } // Convenience method for JS so that it doesn't have to try and create a NSArray on the objc side instead // of the usual WebScriptObject that is passed around - (NSInteger)declareType:(NSString *)type owner:(id)newOwner { return [self declareTypes:@[type] owner:newOwner]; } @end @implementation LocalPasteboard + (id)alloc { // Need to skip NSPasteboard's alloc, which does not allocate an object. return class_createInstance(self, 0); } - (id)initWithName:(NSString *)name { self = [super init]; if (!self) return nil; _pasteboardName = adoptNS([name copy]); return self; } - (NSString *)name { return _pasteboardName.get(); } - (void)releaseGlobally { } - (NSInteger)declareTypes:(NSArray *)newTypes owner:(id)newOwner { [self _clearContentsWithoutUpdatingChangeCount]; [self _addTypesWithoutUpdatingChangeCount:newTypes owner:newOwner]; return ++_changeCount; } static bool isUTI(NSString *type) { return UTTypeIsDynamic((__bridge CFStringRef)type) || UTTypeIsDeclared((__bridge CFStringRef)type); } static RetainPtr toUTI(NSString *type) { if (isUTI(type)) { // This is already a UTI. return adoptCF(CFStringCreateCopy(nullptr, (__bridge CFStringRef)type)); } return adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassNSPboardType, (__bridge CFStringRef)type, nullptr)); } - (void)_clearContentsWithoutUpdatingChangeCount { _cachedPasteboardItems = nil; _types.clear(); _originalTypes.clear(); _data.clear(); } - (NSInteger)clearContents { [self _clearContentsWithoutUpdatingChangeCount]; return ++_changeCount; } - (NSInteger)addTypes:(NSArray *)newTypes owner:(id)newOwner { auto previousOwner = _owner; [self _addTypesWithoutUpdatingChangeCount:newTypes owner:newOwner]; if (previousOwner != newOwner) ++_changeCount; return _changeCount; } - (void)_addTypesWithoutUpdatingChangeCount:(NSArray *)newTypes owner:(id)newOwner { _owner = newOwner; for (NSString *type in newTypes) { _types.add(toUTI(type)); _originalTypes.add((__bridge CFStringRef)type); } } - (NSInteger)changeCount { return _changeCount; } - (NSArray *)types { auto types = adoptNS([[NSMutableArray alloc] init]); for (const auto& type : _types) { [types addObject:(__bridge NSString *)type.get()]; // Include the pasteboard type as well. if (auto pasteboardType = adoptNS((__bridge NSString *)UTTypeCopyPreferredTagWithClass(type.get(), kUTTagClassNSPboardType))) [types addObject:pasteboardType.get()]; } return types.autorelease(); } - (NSString *)availableTypeFromArray:(NSArray *)types { for (NSString *type in types) { if (_types.contains((__bridge CFStringRef)type)) return type; } return nil; } - (BOOL)setData:(NSData *)data forType:(NSString *)dataType { auto uti = toUTI(dataType); if (!_types.contains(uti)) return NO; _data.set(WTFMove(uti), (__bridge CFDataRef)(data ?: [NSData data])); return YES; } - (NSData *)dataForType:(NSString *)dataType { if (NSData *data = (__bridge NSData *)_data.get(toUTI(dataType).get()).get()) return data; if (_owner && [_owner respondsToSelector:@selector(pasteboard:provideDataForType:)]) [_owner pasteboard:self provideDataForType:dataType]; return (__bridge NSData *)_data.get(toUTI(dataType).get()).get(); } - (BOOL)setPropertyList:(id)propertyList forType:(NSString *)dataType { NSData *data = nil; if (propertyList) data = [NSPropertyListSerialization dataWithPropertyList:propertyList format:NSPropertyListXMLFormat_v1_0 options:0 error:nullptr]; return [self setData:data forType:dataType]; } - (BOOL)setString:(NSString *)string forType:(NSString *)dataType { return [self setData:[string dataUsingEncoding:NSUTF8StringEncoding] forType:dataType]; } - (BOOL)writeObjects:(NSArray> *)objects { auto items = adoptNS([[NSMutableArray alloc] initWithCapacity:objects.count]); for (id object in objects) { ASSERT([object isKindOfClass:NSPasteboardItem.class]); [items addObject:(NSPasteboardItem *)object]; for (NSString *type in [object writableTypesForPasteboard:self]) { [self addTypes:@[ type ] owner:self]; id propertyList = [object pasteboardPropertyListForType:type]; if ([propertyList isKindOfClass:NSData.class]) [self setData:propertyList forType:type]; else ASSERT_NOT_REACHED(); } } _cachedPasteboardItems = items.get(); return YES; } - (NSArray *)pasteboardItems { if (_cachedPasteboardItems) return _cachedPasteboardItems.get(); auto item = adoptNS([[NSPasteboardItem alloc] init]); for (auto& type : _originalTypes) { if (!_data.contains(toUTI((__bridge NSString *)type.get()))) [_owner pasteboard:self provideDataForType:(__bridge NSString *)type.get()]; } for (const auto& typeAndData : _data) { NSData *data = (__bridge NSData *)typeAndData.value.get(); NSString *type = (__bridge NSString *)typeAndData.key.get(); [item setData:data forType:[NSPasteboard _modernPasteboardType:type]]; } _cachedPasteboardItems = @[ item.get() ]; return _cachedPasteboardItems.get(); } @end #endif // PLATFORM(MAC)