1// Copyright 2016 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "ui/base/clipboard/clipboard_util_mac.h" 6 7#include "base/mac/foundation_util.h" 8#import "base/mac/mac_util.h" 9#include "base/mac/scoped_cftyperef.h" 10 11namespace ui { 12 13NSString* const kUTTypeURLName = @"public.url-name"; 14 15namespace { 16 17NSString* const kWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType"; 18 19// It's much more convenient to return an NSString than a 20// base::ScopedCFTypeRef<CFStringRef>, since the methods on NSPasteboardItem 21// require an NSString*. 22NSString* UTIFromPboardType(NSString* type) { 23 return [base::mac::CFToNSCast(UTTypeCreatePreferredIdentifierForTag( 24 kUTTagClassNSPboardType, base::mac::NSToCFCast(type), kUTTypeData)) 25 autorelease]; 26} 27 28bool ReadWebURLsWithTitlesPboardType(NSPasteboard* pboard, 29 NSArray** urls, 30 NSArray** titles) { 31 NSArray* bookmarkPairs = base::mac::ObjCCast<NSArray>([pboard 32 propertyListForType:UTIFromPboardType(kWebURLsWithTitlesPboardType)]); 33 if (!bookmarkPairs) 34 return false; 35 36 if ([bookmarkPairs count] != 2) 37 return false; 38 39 NSArray* urlsArr = base::mac::ObjCCast<NSArray>(bookmarkPairs[0]); 40 NSArray* titlesArr = base::mac::ObjCCast<NSArray>(bookmarkPairs[1]); 41 42 if (!urlsArr || !titlesArr) 43 return false; 44 if ([urlsArr count] < 1) 45 return false; 46 if ([urlsArr count] != [titlesArr count]) 47 return false; 48 49 for (id obj in urlsArr) { 50 if (![obj isKindOfClass:[NSString class]]) 51 return false; 52 } 53 54 for (id obj in titlesArr) { 55 if (![obj isKindOfClass:[NSString class]]) 56 return false; 57 } 58 59 *urls = urlsArr; 60 *titles = titlesArr; 61 return true; 62} 63 64bool ReadURLItemsWithTitles(NSPasteboard* pboard, 65 NSArray** urls, 66 NSArray** titles) { 67 NSMutableArray* urlsArr = [NSMutableArray array]; 68 NSMutableArray* titlesArr = [NSMutableArray array]; 69 70 NSArray* items = [pboard pasteboardItems]; 71 for (NSPasteboardItem* item : items) { 72 NSString* url = [item stringForType:base::mac::CFToNSCast(kUTTypeURL)]; 73 NSString* title = [item stringForType:kUTTypeURLName]; 74 75 if (url) { 76 [urlsArr addObject:url]; 77 if (title) 78 [titlesArr addObject:title]; 79 else 80 [titlesArr addObject:@""]; 81 } 82 } 83 84 if ([urlsArr count]) { 85 *urls = urlsArr; 86 *titles = titlesArr; 87 return true; 88 } else { 89 return false; 90 } 91} 92 93} // namespace 94 95UniquePasteboard::UniquePasteboard() 96 : pasteboard_([[NSPasteboard pasteboardWithUniqueName] retain]) {} 97 98UniquePasteboard::~UniquePasteboard() { 99 [pasteboard_ releaseGlobally]; 100 101 if (base::mac::IsOS10_12()) { 102 // On 10.12, move ownership to the autorelease pool rather than possibly 103 // triggering -[NSPasteboard dealloc] here. This is a speculative workaround 104 // for https://crbug.com/877979 where a call to __CFPasteboardDeallocate 105 // from here is triggering "Semaphore object deallocated while in use". 106 pasteboard_.autorelease(); 107 } 108} 109 110// static 111base::scoped_nsobject<NSPasteboardItem> ClipboardUtil::PasteboardItemFromUrl( 112 NSString* urlString, 113 NSString* title) { 114 DCHECK(urlString); 115 if (!title) 116 title = urlString; 117 118 base::scoped_nsobject<NSPasteboardItem> item([[NSPasteboardItem alloc] init]); 119 120 NSURL* url = [NSURL URLWithString:urlString]; 121 if ([url isFileURL] && 122 [[NSFileManager defaultManager] fileExistsAtPath:[url path]]) { 123 [item setPropertyList:@[ [url path] ] 124 forType:UTIFromPboardType(NSFilenamesPboardType)]; 125 } 126 127 // Set Safari's URL + title arrays Pboard type. 128 NSArray* urlsAndTitles = @[ @[ urlString ], @[ title ] ]; 129 [item setPropertyList:urlsAndTitles 130 forType:UTIFromPboardType(kWebURLsWithTitlesPboardType)]; 131 132 // Set NSURLPboardType. The format of the property list is divined from 133 // Webkit's function PlatformPasteboard::setStringForType. 134 // https://github.com/WebKit/webkit/blob/master/Source/WebCore/platform/mac/PlatformPasteboardMac.mm 135 NSURL* base = [url baseURL]; 136 if (base) { 137 [item setPropertyList:@[ [url relativeString], [base absoluteString] ] 138 forType:UTIFromPboardType(NSURLPboardType)]; 139 } else if (url) { 140 [item setPropertyList:@[ [url absoluteString], @"" ] 141 forType:UTIFromPboardType(NSURLPboardType)]; 142 } 143 144 [item setString:urlString forType:NSPasteboardTypeString]; 145 [item setString:urlString forType:base::mac::CFToNSCast(kUTTypeURL)]; 146 [item setString:title forType:kUTTypeURLName]; 147 return item; 148} 149 150// static 151base::scoped_nsobject<NSPasteboardItem> ClipboardUtil::PasteboardItemFromUrls( 152 NSArray* urls, 153 NSArray* titles) { 154 base::scoped_nsobject<NSPasteboardItem> item([[NSPasteboardItem alloc] init]); 155 156 // Set Safari's URL + title arrays Pboard type. 157 NSArray* urlsAndTitles = @[ urls, titles ]; 158 [item setPropertyList:urlsAndTitles 159 forType:UTIFromPboardType(kWebURLsWithTitlesPboardType)]; 160 161 return item; 162} 163 164// static 165base::scoped_nsobject<NSPasteboardItem> ClipboardUtil::PasteboardItemFromString( 166 NSString* string) { 167 base::scoped_nsobject<NSPasteboardItem> item([[NSPasteboardItem alloc] init]); 168 [item setString:string forType:NSPasteboardTypeString]; 169 return item; 170} 171 172//static 173NSString* ClipboardUtil::GetTitleFromPasteboardURL(NSPasteboard* pboard) { 174 return [pboard stringForType:kUTTypeURLName]; 175} 176 177//static 178NSString* ClipboardUtil::GetURLFromPasteboardURL(NSPasteboard* pboard) { 179 return [pboard stringForType:base::mac::CFToNSCast(kUTTypeURL)]; 180} 181 182// static 183NSString* ClipboardUtil::UTIForPasteboardType(NSString* type) { 184 return UTIFromPboardType(type); 185} 186 187// static 188NSString* ClipboardUtil::UTIForWebURLsAndTitles() { 189 return UTIFromPboardType(kWebURLsWithTitlesPboardType); 190} 191 192// static 193void ClipboardUtil::AddDataToPasteboard(NSPasteboard* pboard, 194 NSPasteboardItem* item) { 195 NSSet* oldTypes = [NSSet setWithArray:[pboard types]]; 196 NSMutableSet* newTypes = [NSMutableSet setWithArray:[item types]]; 197 [newTypes minusSet:oldTypes]; 198 199 [pboard addTypes:[newTypes allObjects] owner:nil]; 200 for (NSString* type in newTypes) { 201 // Technically, the object associated with |type| might be an NSString or a 202 // property list. It doesn't matter though, since the type gets pulled from 203 // and shoved into an NSDictionary. 204 [pboard setData:[item dataForType:type] forType:type]; 205 } 206} 207 208// static 209bool ClipboardUtil::URLsAndTitlesFromPasteboard(NSPasteboard* pboard, 210 NSArray** urls, 211 NSArray** titles) { 212 return ReadWebURLsWithTitlesPboardType(pboard, urls, titles) || 213 ReadURLItemsWithTitles(pboard, urls, titles); 214} 215 216// static 217NSPasteboard* ClipboardUtil::PasteboardFromBuffer(ClipboardBuffer buffer) { 218 NSString* buffer_type = nil; 219 switch (buffer) { 220 case ClipboardBuffer::kCopyPaste: 221 buffer_type = NSGeneralPboard; 222 break; 223 case ClipboardBuffer::kDrag: 224 buffer_type = NSDragPboard; 225 break; 226 case ClipboardBuffer::kSelection: 227 NOTREACHED(); 228 break; 229 } 230 231 return [NSPasteboard pasteboardWithName:buffer_type]; 232} 233 234// static 235NSString* ClipboardUtil::GetHTMLFromRTFOnPasteboard(NSPasteboard* pboard) { 236 NSData* rtfData = [pboard dataForType:NSRTFPboardType]; 237 if (!rtfData) 238 return nil; 239 240 NSAttributedString* attributed = 241 [[[NSAttributedString alloc] initWithRTF:rtfData 242 documentAttributes:nil] autorelease]; 243 NSData* htmlData = 244 [attributed dataFromRange:NSMakeRange(0, [attributed length]) 245 documentAttributes:@{ 246 NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType 247 } 248 error:nil]; 249 250 // According to the docs, NSHTMLTextDocumentType is UTF8. 251 return [[[NSString alloc] initWithData:htmlData 252 encoding:NSUTF8StringEncoding] autorelease]; 253} 254 255} // namespace ui 256