1 // Copyright 2014 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 "components/bookmarks/browser/bookmark_pasteboard_helper_mac.h" 6 7 #import <Cocoa/Cocoa.h> 8 9 #include "base/files/file_path.h" 10 #include "base/strings/sys_string_conversions.h" 11 #include "components/bookmarks/browser/bookmark_node.h" 12 #include "ui/base/clipboard/clipboard.h" 13 14 using bookmarks::BookmarkNodeData; 15 16 NSString* const kBookmarkDictionaryListPboardType = 17 @"BookmarkDictionaryListPboardType"; 18 19 namespace { 20 21 // An unofficial standard pasteboard title type to be provided alongside the 22 // |NSURLPboardType|. 23 NSString* const kNSURLTitlePboardType = @"public.url-name"; 24 25 // Pasteboard type used to store profile path to determine which profile 26 // a set of bookmarks came from. 27 NSString* const kChromiumProfilePathPboardType = 28 @"ChromiumProfilePathPboardType"; 29 30 // Internal bookmark ID for a bookmark node. Used only when moving inside 31 // of one profile. 32 NSString* const kChromiumBookmarkId = @"ChromiumBookmarkId"; 33 34 // Internal bookmark meta info dictionary for a bookmark node. 35 NSString* const kChromiumBookmarkMetaInfo = @"ChromiumBookmarkMetaInfo"; 36 37 // Mac WebKit uses this type, declared in 38 // WebKit/mac/History/WebURLsWithTitles.h. 39 NSString* const kCrWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType"; 40 41 // Keys for the type of node in BookmarkDictionaryListPboardType. 42 NSString* const kWebBookmarkType = @"WebBookmarkType"; 43 44 NSString* const kWebBookmarkTypeList = @"WebBookmarkTypeList"; 45 46 NSString* const kWebBookmarkTypeLeaf = @"WebBookmarkTypeLeaf"; 47 48 BookmarkNode::MetaInfoMap MetaInfoMapFromDictionary(NSDictionary* dictionary) { 49 BookmarkNode::MetaInfoMap meta_info_map; 50 51 for (NSString* key in dictionary) { 52 meta_info_map[base::SysNSStringToUTF8(key)] = 53 base::SysNSStringToUTF8([dictionary objectForKey:key]); 54 } 55 56 return meta_info_map; 57 } 58 59 void ConvertPlistToElements(NSArray* input, 60 std::vector<BookmarkNodeData::Element>& elements) { 61 NSUInteger len = [input count]; 62 for (NSUInteger i = 0; i < len; ++i) { 63 NSDictionary* pboardBookmark = [input objectAtIndex:i]; 64 scoped_ptr<BookmarkNode> new_node(new BookmarkNode(GURL())); 65 int64 node_id = 66 [[pboardBookmark objectForKey:kChromiumBookmarkId] longLongValue]; 67 new_node->set_id(node_id); 68 69 NSDictionary* metaInfoDictionary = 70 [pboardBookmark objectForKey:kChromiumBookmarkMetaInfo]; 71 if (metaInfoDictionary) 72 new_node->SetMetaInfoMap(MetaInfoMapFromDictionary(metaInfoDictionary)); 73 74 BOOL is_folder = [[pboardBookmark objectForKey:kWebBookmarkType] 75 isEqualToString:kWebBookmarkTypeList]; 76 if (is_folder) { 77 new_node->set_type(BookmarkNode::FOLDER); 78 NSString* title = [pboardBookmark objectForKey:@"Title"]; 79 new_node->SetTitle(base::SysNSStringToUTF16(title)); 80 } else { 81 new_node->set_type(BookmarkNode::URL); 82 NSDictionary* uriDictionary = 83 [pboardBookmark objectForKey:@"URIDictionary"]; 84 NSString* title = [uriDictionary objectForKey:@"title"]; 85 NSString* urlString = [pboardBookmark objectForKey:@"URLString"]; 86 new_node->SetTitle(base::SysNSStringToUTF16(title)); 87 new_node->set_url(GURL(base::SysNSStringToUTF8(urlString))); 88 } 89 BookmarkNodeData::Element e = BookmarkNodeData::Element(new_node.get()); 90 if (is_folder) { 91 ConvertPlistToElements([pboardBookmark objectForKey:@"Children"], 92 e.children); 93 } 94 elements.push_back(e); 95 } 96 } 97 98 bool ReadBookmarkDictionaryListPboardType( 99 NSPasteboard* pb, 100 std::vector<BookmarkNodeData::Element>& elements) { 101 NSArray* bookmarks = 102 [pb propertyListForType:kBookmarkDictionaryListPboardType]; 103 if (!bookmarks) 104 return false; 105 ConvertPlistToElements(bookmarks, elements); 106 return true; 107 } 108 109 bool ReadWebURLsWithTitlesPboardType( 110 NSPasteboard* pb, 111 std::vector<BookmarkNodeData::Element>& elements) { 112 NSArray* bookmarkPairs = 113 [pb propertyListForType:kCrWebURLsWithTitlesPboardType]; 114 if (![bookmarkPairs isKindOfClass:[NSArray class]]) 115 return false; 116 117 NSArray* urlsArr = [bookmarkPairs objectAtIndex:0]; 118 NSArray* titlesArr = [bookmarkPairs objectAtIndex:1]; 119 if ([urlsArr count] < 1) 120 return false; 121 if ([urlsArr count] != [titlesArr count]) 122 return false; 123 124 NSUInteger len = [titlesArr count]; 125 for (NSUInteger i = 0; i < len; ++i) { 126 base::string16 title = 127 base::SysNSStringToUTF16([titlesArr objectAtIndex:i]); 128 std::string url = base::SysNSStringToUTF8([urlsArr objectAtIndex:i]); 129 if (!url.empty()) { 130 BookmarkNodeData::Element element; 131 element.is_url = true; 132 element.url = GURL(url); 133 element.title = title; 134 elements.push_back(element); 135 } 136 } 137 return true; 138 } 139 140 bool ReadNSURLPboardType(NSPasteboard* pb, 141 std::vector<BookmarkNodeData::Element>& elements) { 142 NSURL* url = [NSURL URLFromPasteboard:pb]; 143 if (url == nil) 144 return false; 145 146 std::string urlString = base::SysNSStringToUTF8([url absoluteString]); 147 NSString* title = [pb stringForType:kNSURLTitlePboardType]; 148 if (!title) 149 title = [pb stringForType:NSStringPboardType]; 150 151 BookmarkNodeData::Element element; 152 element.is_url = true; 153 element.url = GURL(urlString); 154 element.title = base::SysNSStringToUTF16(title); 155 elements.push_back(element); 156 return true; 157 } 158 159 NSDictionary* DictionaryFromBookmarkMetaInfo( 160 const BookmarkNode::MetaInfoMap& meta_info_map) { 161 NSMutableDictionary* dictionary = [NSMutableDictionary dictionary]; 162 163 for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map.begin(); 164 it != meta_info_map.end(); ++it) { 165 [dictionary setObject:base::SysUTF8ToNSString(it->second) 166 forKey:base::SysUTF8ToNSString(it->first)]; 167 } 168 169 return dictionary; 170 } 171 172 NSArray* GetPlistForBookmarkList( 173 const std::vector<BookmarkNodeData::Element>& elements) { 174 NSMutableArray* plist = [NSMutableArray array]; 175 for (size_t i = 0; i < elements.size(); ++i) { 176 BookmarkNodeData::Element element = elements[i]; 177 NSDictionary* metaInfoDictionary = 178 DictionaryFromBookmarkMetaInfo(element.meta_info_map); 179 if (element.is_url) { 180 NSString* title = base::SysUTF16ToNSString(element.title); 181 NSString* url = base::SysUTF8ToNSString(element.url.spec()); 182 int64 elementId = element.id(); 183 NSNumber* idNum = [NSNumber numberWithLongLong:elementId]; 184 NSDictionary* uriDictionary = [NSDictionary dictionaryWithObjectsAndKeys: 185 title, @"title", nil]; 186 NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys: 187 uriDictionary, @"URIDictionary", 188 url, @"URLString", 189 kWebBookmarkTypeLeaf, kWebBookmarkType, 190 idNum, kChromiumBookmarkId, 191 metaInfoDictionary, kChromiumBookmarkMetaInfo, 192 nil]; 193 [plist addObject:object]; 194 } else { 195 NSString* title = base::SysUTF16ToNSString(element.title); 196 NSArray* children = GetPlistForBookmarkList(element.children); 197 int64 elementId = element.id(); 198 NSNumber* idNum = [NSNumber numberWithLongLong:elementId]; 199 NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys: 200 title, @"Title", 201 children, @"Children", 202 kWebBookmarkTypeList, kWebBookmarkType, 203 idNum, kChromiumBookmarkId, 204 metaInfoDictionary, kChromiumBookmarkMetaInfo, 205 nil]; 206 [plist addObject:object]; 207 } 208 } 209 return plist; 210 } 211 212 void WriteBookmarkDictionaryListPboardType( 213 NSPasteboard* pb, 214 const std::vector<BookmarkNodeData::Element>& elements) { 215 NSArray* plist = GetPlistForBookmarkList(elements); 216 [pb setPropertyList:plist forType:kBookmarkDictionaryListPboardType]; 217 } 218 219 void FillFlattenedArraysForBookmarks( 220 const std::vector<BookmarkNodeData::Element>& elements, 221 NSMutableArray* titles, 222 NSMutableArray* urls) { 223 for (size_t i = 0; i < elements.size(); ++i) { 224 BookmarkNodeData::Element element = elements[i]; 225 if (element.is_url) { 226 NSString* title = base::SysUTF16ToNSString(element.title); 227 NSString* url = base::SysUTF8ToNSString(element.url.spec()); 228 [titles addObject:title]; 229 [urls addObject:url]; 230 } else { 231 FillFlattenedArraysForBookmarks(element.children, titles, urls); 232 } 233 } 234 } 235 236 void WriteSimplifiedBookmarkTypes( 237 NSPasteboard* pb, 238 const std::vector<BookmarkNodeData::Element>& elements) { 239 NSMutableArray* titles = [NSMutableArray array]; 240 NSMutableArray* urls = [NSMutableArray array]; 241 FillFlattenedArraysForBookmarks(elements, titles, urls); 242 243 // These bookmark types only act on urls, not folders. 244 if ([urls count] < 1) 245 return; 246 247 // Write WebURLsWithTitlesPboardType. 248 [pb setPropertyList:[NSArray arrayWithObjects:urls, titles, nil] 249 forType:kCrWebURLsWithTitlesPboardType]; 250 251 // Write NSStringPboardType. 252 [pb setString:[urls componentsJoinedByString:@"\n"] 253 forType:NSStringPboardType]; 254 255 // Write NSURLPboardType (with title). 256 NSURL* url = [NSURL URLWithString:[urls objectAtIndex:0]]; 257 [url writeToPasteboard:pb]; 258 NSString* titleString = [titles objectAtIndex:0]; 259 [pb setString:titleString forType:kNSURLTitlePboardType]; 260 } 261 262 NSPasteboard* PasteboardFromType(ui::ClipboardType type) { 263 NSString* type_string = nil; 264 switch (type) { 265 case ui::CLIPBOARD_TYPE_COPY_PASTE: 266 type_string = NSGeneralPboard; 267 break; 268 case ui::CLIPBOARD_TYPE_DRAG: 269 type_string = NSDragPboard; 270 break; 271 case ui::CLIPBOARD_TYPE_SELECTION: 272 NOTREACHED(); 273 break; 274 } 275 276 return [NSPasteboard pasteboardWithName:type_string]; 277 } 278 279 } // namespace 280 281 void WriteBookmarksToPasteboard( 282 ui::ClipboardType type, 283 const std::vector<BookmarkNodeData::Element>& elements, 284 const base::FilePath& profile_path) { 285 if (elements.empty()) 286 return; 287 288 NSPasteboard* pb = PasteboardFromType(type); 289 290 NSArray* types = [NSArray arrayWithObjects:kBookmarkDictionaryListPboardType, 291 kCrWebURLsWithTitlesPboardType, 292 NSStringPboardType, 293 NSURLPboardType, 294 kNSURLTitlePboardType, 295 kChromiumProfilePathPboardType, 296 nil]; 297 [pb declareTypes:types owner:nil]; 298 [pb setString:base::SysUTF8ToNSString(profile_path.value()) 299 forType:kChromiumProfilePathPboardType]; 300 WriteBookmarkDictionaryListPboardType(pb, elements); 301 WriteSimplifiedBookmarkTypes(pb, elements); 302 } 303 304 bool ReadBookmarksFromPasteboard( 305 ui::ClipboardType type, 306 std::vector<BookmarkNodeData::Element>& elements, 307 base::FilePath* profile_path) { 308 NSPasteboard* pb = PasteboardFromType(type); 309 310 elements.clear(); 311 NSString* profile = [pb stringForType:kChromiumProfilePathPboardType]; 312 *profile_path = base::FilePath(base::SysNSStringToUTF8(profile)); 313 return ReadBookmarkDictionaryListPboardType(pb, elements) || 314 ReadWebURLsWithTitlesPboardType(pb, elements) || 315 ReadNSURLPboardType(pb, elements); 316 } 317 318 bool PasteboardContainsBookmarks(ui::ClipboardType type) { 319 NSPasteboard* pb = PasteboardFromType(type); 320 321 NSArray* availableTypes = 322 [NSArray arrayWithObjects:kBookmarkDictionaryListPboardType, 323 kCrWebURLsWithTitlesPboardType, 324 NSURLPboardType, 325 nil]; 326 return [pb availableTypeFromArray:availableTypes] != nil; 327 } 328