Home | History | Annotate | Download | only in browser
      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