• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
14NSString* const kBookmarkDictionaryListPboardType =
15    @"BookmarkDictionaryListPboardType";
16
17namespace {
18
19// An unofficial standard pasteboard title type to be provided alongside the
20// |NSURLPboardType|.
21NSString* 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.
25NSString* const kChromiumProfilePathPboardType =
26    @"ChromiumProfilePathPboardType";
27
28// Internal bookmark ID for a bookmark node.  Used only when moving inside
29// of one profile.
30NSString* const kChromiumBookmarkId = @"ChromiumBookmarkId";
31
32// Mac WebKit uses this type, declared in
33// WebKit/mac/History/WebURLsWithTitles.h.
34NSString* const kCrWebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType";
35
36// Keys for the type of node in BookmarkDictionaryListPboardType.
37NSString* const kWebBookmarkType = @"WebBookmarkType";
38
39NSString* const kWebBookmarkTypeList = @"WebBookmarkTypeList";
40
41NSString* const kWebBookmarkTypeLeaf = @"WebBookmarkTypeLeaf";
42
43void 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
75bool 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
86bool 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
117bool 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
136NSArray* 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
172void WriteBookmarkDictionaryListPboardType(
173    NSPasteboard* pb,
174    const std::vector<BookmarkNodeData::Element>& elements) {
175  NSArray* plist = GetPlistForBookmarkList(elements);
176  [pb setPropertyList:plist forType:kBookmarkDictionaryListPboardType];
177}
178
179void 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
196void 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
222NSPasteboard* 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
241void 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
264bool 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
278bool 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