bookmark_pasteboard_helper_mac.mm 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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. #include "components/bookmarks/browser/bookmark_pasteboard_helper_mac.h"
  5. #import <Cocoa/Cocoa.h>
  6. #include <stddef.h>
  7. #include <stdint.h>
  8. #include <memory>
  9. #include "base/files/file_path.h"
  10. #include "base/guid.h"
  11. #include "base/mac/foundation_util.h"
  12. #include "base/strings/sys_string_conversions.h"
  13. #include "components/bookmarks/browser/bookmark_node.h"
  14. #include "ui/base/clipboard/clipboard.h"
  15. #include "ui/base/clipboard/clipboard_util_mac.h"
  16. namespace bookmarks {
  17. NSString* const kUTTypeChromiumBookmarkDictionaryList =
  18. @"org.chromium.bookmark-dictionary-list";
  19. namespace {
  20. // UTI used to store profile path to determine which profile a set of bookmarks
  21. // came from.
  22. NSString* const kUTTypeChromiumProfilePath = @"org.chromium.profile-path";
  23. // Internal bookmark ID for a bookmark node. Used only when moving inside of
  24. // one profile.
  25. NSString* const kChromiumBookmarkIdKey = @"ChromiumBookmarkId";
  26. // Internal bookmark meta info dictionary for a bookmark node.
  27. NSString* const kChromiumBookmarkMetaInfoKey = @"ChromiumBookmarkMetaInfo";
  28. // Keys for the type of node in kUTTypeChromiumBookmarkDictionaryList.
  29. NSString* const kWebBookmarkTypeKey = @"WebBookmarkType";
  30. NSString* const kWebBookmarkTypeList = @"WebBookmarkTypeList";
  31. NSString* const kWebBookmarkTypeLeaf = @"WebBookmarkTypeLeaf";
  32. // Property keys.
  33. NSString* const kTitleKey = @"Title";
  34. NSString* const kURLStringKey = @"URLString";
  35. NSString* const kChildrenKey = @"Children";
  36. BookmarkNode::MetaInfoMap MetaInfoMapFromDictionary(NSDictionary* dictionary) {
  37. __block BookmarkNode::MetaInfoMap meta_info_map;
  38. [dictionary
  39. enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL* stop) {
  40. NSString* key_ns = base::mac::ObjCCast<NSString>(key);
  41. NSString* value_ns = base::mac::ObjCCast<NSString>(value);
  42. if (key_ns && value_ns) {
  43. meta_info_map[base::SysNSStringToUTF8(key_ns)] =
  44. base::SysNSStringToUTF8(value_ns);
  45. }
  46. }];
  47. return meta_info_map;
  48. }
  49. NSDictionary* DictionaryFromBookmarkMetaInfo(
  50. const BookmarkNode::MetaInfoMap& meta_info_map) {
  51. NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
  52. for (const auto& item : meta_info_map) {
  53. dictionary[base::SysUTF8ToNSString(item.first)] =
  54. base::SysUTF8ToNSString(item.second);
  55. }
  56. return dictionary;
  57. }
  58. void ConvertNSArrayToElements(
  59. NSArray* input,
  60. std::vector<BookmarkNodeData::Element>* elements) {
  61. for (NSDictionary* bookmark_dict in input) {
  62. NSString* type =
  63. base::mac::ObjCCast<NSString>(bookmark_dict[kWebBookmarkTypeKey]);
  64. if (!type)
  65. continue;
  66. BOOL is_folder = [type isEqualToString:kWebBookmarkTypeList];
  67. GURL url = GURL();
  68. if (!is_folder) {
  69. NSString* url_string =
  70. base::mac::ObjCCast<NSString>(bookmark_dict[kURLStringKey]);
  71. if (!url_string)
  72. continue;
  73. url = GURL(base::SysNSStringToUTF8(url_string));
  74. }
  75. auto new_node = std::make_unique<BookmarkNode>(
  76. /*id=*/0, base::GUID::GenerateRandomV4(), url);
  77. NSNumber* node_id =
  78. base::mac::ObjCCast<NSNumber>(bookmark_dict[kChromiumBookmarkIdKey]);
  79. if (node_id)
  80. new_node->set_id([node_id longLongValue]);
  81. NSDictionary* meta_info = base::mac::ObjCCast<NSDictionary>(
  82. bookmark_dict[kChromiumBookmarkMetaInfoKey]);
  83. if (meta_info)
  84. new_node->SetMetaInfoMap(MetaInfoMapFromDictionary(meta_info));
  85. NSString* title = base::mac::ObjCCast<NSString>(bookmark_dict[kTitleKey]);
  86. new_node->SetTitle(base::SysNSStringToUTF16(title));
  87. BookmarkNodeData::Element e = BookmarkNodeData::Element(new_node.get());
  88. // BookmarkNodeData::Element::ReadFromPickle explicitly zeroes out the two
  89. // date fields so do so too. TODO(avi): Refactor this code to be a member
  90. // function of BookmarkNodeData::Element so that it can write the id_ field
  91. // directly and avoid the round-trip through BookmarkNode.
  92. e.date_added = base::Time();
  93. e.date_folder_modified = base::Time();
  94. if (is_folder) {
  95. ConvertNSArrayToElements(bookmark_dict[kChildrenKey], &e.children);
  96. }
  97. elements->push_back(e);
  98. }
  99. }
  100. bool ReadBookmarkDictionaryListType(
  101. NSPasteboard* pb,
  102. std::vector<BookmarkNodeData::Element>* elements) {
  103. id bookmarks = [pb propertyListForType:kUTTypeChromiumBookmarkDictionaryList];
  104. if (!bookmarks)
  105. return false;
  106. NSArray* bookmarks_array = base::mac::ObjCCast<NSArray>(bookmarks);
  107. if (!bookmarks_array)
  108. return false;
  109. ConvertNSArrayToElements(bookmarks_array, elements);
  110. return true;
  111. }
  112. bool ReadWebURLsWithTitlesPboardType(
  113. NSPasteboard* pb,
  114. std::vector<BookmarkNodeData::Element>* elements) {
  115. NSArray* urls = nil;
  116. NSArray* titles = nil;
  117. if (!ui::ClipboardUtil::URLsAndTitlesFromPasteboard(pb, &urls, &titles))
  118. return false;
  119. NSUInteger len = [titles count];
  120. for (NSUInteger i = 0; i < len; ++i) {
  121. std::u16string title = base::SysNSStringToUTF16(titles[i]);
  122. std::string url = base::SysNSStringToUTF8(urls[i]);
  123. if (!url.empty()) {
  124. BookmarkNodeData::Element element;
  125. element.is_url = true;
  126. element.url = GURL(url);
  127. element.title = title;
  128. elements->push_back(element);
  129. }
  130. }
  131. return true;
  132. }
  133. NSArray* GetNSArrayForBookmarkList(
  134. const std::vector<BookmarkNodeData::Element>& elements) {
  135. NSMutableArray* array = [NSMutableArray array];
  136. for (const auto& element : elements) {
  137. NSDictionary* meta_info =
  138. DictionaryFromBookmarkMetaInfo(element.meta_info_map);
  139. NSString* title = base::SysUTF16ToNSString(element.title);
  140. NSNumber* element_id = @(element.id());
  141. NSDictionary* object;
  142. if (element.is_url) {
  143. NSString* url = base::SysUTF8ToNSString(element.url.spec());
  144. object = @{
  145. kTitleKey : title,
  146. kURLStringKey : url,
  147. kWebBookmarkTypeKey : kWebBookmarkTypeLeaf,
  148. kChromiumBookmarkIdKey : element_id,
  149. kChromiumBookmarkMetaInfoKey : meta_info
  150. };
  151. } else {
  152. NSArray* children = GetNSArrayForBookmarkList(element.children);
  153. object = @{
  154. kTitleKey : title,
  155. kChildrenKey : children,
  156. kWebBookmarkTypeKey : kWebBookmarkTypeList,
  157. kChromiumBookmarkIdKey : element_id,
  158. kChromiumBookmarkMetaInfoKey : meta_info
  159. };
  160. }
  161. [array addObject:object];
  162. }
  163. return array;
  164. }
  165. void WriteBookmarkDictionaryListType(
  166. NSPasteboardItem* item,
  167. const std::vector<BookmarkNodeData::Element>& elements) {
  168. NSArray* array = GetNSArrayForBookmarkList(elements);
  169. [item setPropertyList:array forType:kUTTypeChromiumBookmarkDictionaryList];
  170. }
  171. void FillFlattenedArraysForBookmarks(
  172. const std::vector<BookmarkNodeData::Element>& elements,
  173. NSMutableArray* url_titles,
  174. NSMutableArray* urls,
  175. NSMutableArray* toplevel_string_data) {
  176. for (const auto& element : elements) {
  177. NSString* title = base::SysUTF16ToNSString(element.title);
  178. if (element.is_url) {
  179. NSString* url = base::SysUTF8ToNSString(element.url.spec());
  180. [url_titles addObject:title];
  181. [urls addObject:url];
  182. if (toplevel_string_data)
  183. [toplevel_string_data addObject:url];
  184. } else {
  185. if (toplevel_string_data)
  186. [toplevel_string_data addObject:title];
  187. FillFlattenedArraysForBookmarks(element.children, url_titles, urls, nil);
  188. }
  189. }
  190. }
  191. base::scoped_nsobject<NSPasteboardItem> WriteSimplifiedBookmarkTypes(
  192. const std::vector<BookmarkNodeData::Element>& elements) {
  193. NSMutableArray* url_titles = [NSMutableArray array];
  194. NSMutableArray* urls = [NSMutableArray array];
  195. NSMutableArray* toplevel_string_data = [NSMutableArray array];
  196. FillFlattenedArraysForBookmarks(
  197. elements, url_titles, urls, toplevel_string_data);
  198. base::scoped_nsobject<NSPasteboardItem> item;
  199. if ([urls count] > 0) {
  200. if ([urls count] == 1) {
  201. item = ui::ClipboardUtil::PasteboardItemFromUrl([urls firstObject],
  202. [url_titles firstObject]);
  203. } else {
  204. item = ui::ClipboardUtil::PasteboardItemFromUrls(urls, url_titles);
  205. }
  206. }
  207. if (!item) {
  208. item.reset([[NSPasteboardItem alloc] init]);
  209. }
  210. [item setString:[toplevel_string_data componentsJoinedByString:@"\n"]
  211. forType:base::mac::CFToNSCast(kUTTypeUTF8PlainText)];
  212. return item;
  213. }
  214. NSPasteboardItem* PasteboardItemFromBookmarks(
  215. const std::vector<BookmarkNodeData::Element>& elements,
  216. const base::FilePath& profile_path) {
  217. base::scoped_nsobject<NSPasteboardItem> item =
  218. WriteSimplifiedBookmarkTypes(elements);
  219. WriteBookmarkDictionaryListType(item, elements);
  220. [item setString:base::SysUTF8ToNSString(profile_path.value())
  221. forType:kUTTypeChromiumProfilePath];
  222. return item.autorelease();
  223. }
  224. } // namespace
  225. void WriteBookmarksToPasteboard(
  226. NSPasteboard* pb,
  227. const std::vector<BookmarkNodeData::Element>& elements,
  228. const base::FilePath& profile_path) {
  229. if (elements.empty())
  230. return;
  231. NSPasteboardItem* item = PasteboardItemFromBookmarks(elements, profile_path);
  232. [pb clearContents];
  233. [pb writeObjects:@[ item ]];
  234. }
  235. bool ReadBookmarksFromPasteboard(
  236. NSPasteboard* pb,
  237. std::vector<BookmarkNodeData::Element>* elements,
  238. base::FilePath* profile_path) {
  239. elements->clear();
  240. NSString* profile = [pb stringForType:kUTTypeChromiumProfilePath];
  241. *profile_path = base::FilePath(base::SysNSStringToUTF8(profile));
  242. return ReadBookmarkDictionaryListType(pb, elements) ||
  243. ReadWebURLsWithTitlesPboardType(pb, elements);
  244. }
  245. bool PasteboardContainsBookmarks(NSPasteboard* pb) {
  246. NSArray* availableTypes = @[
  247. ui::ClipboardUtil::UTIForWebURLsAndTitles(),
  248. kUTTypeChromiumBookmarkDictionaryList
  249. ];
  250. return [pb availableTypeFromArray:availableTypes] != nil;
  251. }
  252. } // namespace bookmarks