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