Popular sites on the NTP: Try to keep the ordering constant
[chromium-blink-merge.git] / components / bookmarks / browser / bookmark_pasteboard_helper_mac.mm
blob7f390604a31769bfb6f4e9dde309eddadb7a903d
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.
5 #include "components/bookmarks/browser/bookmark_pasteboard_helper_mac.h"
7 #import <Cocoa/Cocoa.h>
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"
14 NSString* const kBookmarkDictionaryListPboardType =
15     @"BookmarkDictionaryListPboardType";
17 namespace bookmarks {
19 namespace {
21 // An unofficial standard pasteboard title type to be provided alongside the
22 // |NSURLPboardType|.
23 NSString* const kNSURLTitlePboardType = @"public.url-name";
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";
30 // Internal bookmark ID for a bookmark node.  Used only when moving inside
31 // of one profile.
32 NSString* const kChromiumBookmarkId = @"ChromiumBookmarkId";
34 // Internal bookmark meta info dictionary for a bookmark node.
35 NSString* const kChromiumBookmarkMetaInfo = @"ChromiumBookmarkMetaInfo";
37 // Mac WebKit uses this type, declared in
38 // WebKit/mac/History/WebURLsWithTitles.h.
39 NSString* const kCrWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType";
41 // Keys for the type of node in BookmarkDictionaryListPboardType.
42 NSString* const kWebBookmarkType = @"WebBookmarkType";
44 NSString* const kWebBookmarkTypeList = @"WebBookmarkTypeList";
46 NSString* const kWebBookmarkTypeLeaf = @"WebBookmarkTypeLeaf";
48 BookmarkNode::MetaInfoMap MetaInfoMapFromDictionary(NSDictionary* dictionary) {
49   BookmarkNode::MetaInfoMap meta_info_map;
51   for (NSString* key in dictionary) {
52     meta_info_map[base::SysNSStringToUTF8(key)] =
53         base::SysNSStringToUTF8([dictionary objectForKey:key]);
54   }
56   return meta_info_map;
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);
69     NSDictionary* metaInfoDictionary =
70         [pboardBookmark objectForKey:kChromiumBookmarkMetaInfo];
71     if (metaInfoDictionary)
72       new_node->SetMetaInfoMap(MetaInfoMapFromDictionary(metaInfoDictionary));
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   }
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;
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;
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;
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;
140 bool ReadNSURLPboardType(NSPasteboard* pb,
141                          std::vector<BookmarkNodeData::Element>& elements) {
142   NSURL* url = [NSURL URLFromPasteboard:pb];
143   if (url == nil)
144     return false;
146   std::string urlString = base::SysNSStringToUTF8([url absoluteString]);
147   NSString* title = [pb stringForType:kNSURLTitlePboardType];
148   if (!title)
149     title = [pb stringForType:NSStringPboardType];
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;
159 NSDictionary* DictionaryFromBookmarkMetaInfo(
160     const BookmarkNode::MetaInfoMap& meta_info_map) {
161   NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
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   }
169   return dictionary;
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;
212 void WriteBookmarkDictionaryListPboardType(
213     NSPasteboard* pb,
214     const std::vector<BookmarkNodeData::Element>& elements) {
215   NSArray* plist = GetPlistForBookmarkList(elements);
216   [pb setPropertyList:plist forType:kBookmarkDictionaryListPboardType];
219 void FillFlattenedArraysForBookmarks(
220     const std::vector<BookmarkNodeData::Element>& elements,
221     NSMutableArray* url_titles,
222     NSMutableArray* urls,
223     NSMutableArray* toplevel_string_data) {
224   for (const BookmarkNodeData::Element& element : elements) {
225     NSString* title = base::SysUTF16ToNSString(element.title);
226     if (element.is_url) {
227       NSString* url = base::SysUTF8ToNSString(element.url.spec());
228       [url_titles addObject:title];
229       [urls addObject:url];
230       if (toplevel_string_data)
231         [toplevel_string_data addObject:url];
232     } else {
233       if (toplevel_string_data)
234         [toplevel_string_data addObject:title];
235       FillFlattenedArraysForBookmarks(element.children, url_titles, urls, nil);
236     }
237   }
240 void WriteSimplifiedBookmarkTypes(
241     NSPasteboard* pb,
242     const std::vector<BookmarkNodeData::Element>& elements) {
243   NSMutableArray* url_titles = [NSMutableArray array];
244   NSMutableArray* urls = [NSMutableArray array];
245   NSMutableArray* toplevel_string_data = [NSMutableArray array];
246   FillFlattenedArraysForBookmarks(
247       elements, url_titles, urls, toplevel_string_data);
249   // Write NSStringPboardType.
250   [pb setString:[toplevel_string_data componentsJoinedByString:@"\n"]
251         forType:NSStringPboardType];
253   // The following pasteboard types only act on urls, not folders.
254   if ([urls count] < 1)
255     return;
257   // Write WebURLsWithTitlesPboardType.
258   [pb setPropertyList:[NSArray arrayWithObjects:urls, url_titles, nil]
259               forType:kCrWebURLsWithTitlesPboardType];
261   // Write NSURLPboardType (with title).
262   NSURL* url = [NSURL URLWithString:[urls objectAtIndex:0]];
263   [url writeToPasteboard:pb];
264   NSString* titleString = [url_titles objectAtIndex:0];
265   [pb setString:titleString forType:kNSURLTitlePboardType];
268 NSPasteboard* PasteboardFromType(ui::ClipboardType type) {
269   NSString* type_string = nil;
270   switch (type) {
271     case ui::CLIPBOARD_TYPE_COPY_PASTE:
272       type_string = NSGeneralPboard;
273       break;
274     case ui::CLIPBOARD_TYPE_DRAG:
275       type_string = NSDragPboard;
276       break;
277     case ui::CLIPBOARD_TYPE_SELECTION:
278       NOTREACHED();
279       break;
280   }
282   return [NSPasteboard pasteboardWithName:type_string];
285 }  // namespace
287 void WriteBookmarksToPasteboard(
288     ui::ClipboardType type,
289     const std::vector<BookmarkNodeData::Element>& elements,
290     const base::FilePath& profile_path) {
291   if (elements.empty())
292     return;
294   NSPasteboard* pb = PasteboardFromType(type);
296   NSArray* types = [NSArray arrayWithObjects:kBookmarkDictionaryListPboardType,
297                                              kCrWebURLsWithTitlesPboardType,
298                                              NSStringPboardType,
299                                              NSURLPboardType,
300                                              kNSURLTitlePboardType,
301                                              kChromiumProfilePathPboardType,
302                                              nil];
303   [pb declareTypes:types owner:nil];
304   [pb setString:base::SysUTF8ToNSString(profile_path.value())
305         forType:kChromiumProfilePathPboardType];
306   WriteBookmarkDictionaryListPboardType(pb, elements);
307   WriteSimplifiedBookmarkTypes(pb, elements);
310 bool ReadBookmarksFromPasteboard(
311     ui::ClipboardType type,
312     std::vector<BookmarkNodeData::Element>& elements,
313     base::FilePath* profile_path) {
314   NSPasteboard* pb = PasteboardFromType(type);
316   elements.clear();
317   NSString* profile = [pb stringForType:kChromiumProfilePathPboardType];
318   *profile_path = base::FilePath(base::SysNSStringToUTF8(profile));
319   return ReadBookmarkDictionaryListPboardType(pb, elements) ||
320          ReadWebURLsWithTitlesPboardType(pb, elements) ||
321          ReadNSURLPboardType(pb, elements);
324 bool PasteboardContainsBookmarks(ui::ClipboardType type) {
325   NSPasteboard* pb = PasteboardFromType(type);
327   NSArray* availableTypes =
328       [NSArray arrayWithObjects:kBookmarkDictionaryListPboardType,
329                                 kCrWebURLsWithTitlesPboardType,
330                                 NSURLPboardType,
331                                 nil];
332   return [pb availableTypeFromArray:availableTypes] != nil;
335 }  // namespace bookmarks