blob: 75a5e7f7fa74cc03e6e7f6ad2a3d8d10ff3c114e [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/bookmarks/browser/bookmark_pasteboard_helper_mac.h"
#import <Cocoa/Cocoa.h>
#include <stddef.h>
#include <stdint.h>
#include "base/files/file_path.h"
#include "base/strings/sys_string_conversions.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_util_mac.h"
NSString* const kBookmarkDictionaryListPboardType =
@"com.google.chrome.BookmarkDictionaryListPboardType";
namespace bookmarks {
namespace {
// Pasteboard type used to store profile path to determine which profile
// a set of bookmarks came from.
NSString* const kChromiumProfilePathPboardType =
@"com.google.chrome.ChromiumProfilePathPboardType";
// Internal bookmark ID for a bookmark node. Used only when moving inside
// of one profile.
NSString* const kChromiumBookmarkId = @"ChromiumBookmarkId";
// Internal bookmark meta info dictionary for a bookmark node.
NSString* const kChromiumBookmarkMetaInfo = @"ChromiumBookmarkMetaInfo";
// Keys for the type of node in BookmarkDictionaryListPboardType.
NSString* const kWebBookmarkType = @"WebBookmarkType";
NSString* const kWebBookmarkTypeList = @"WebBookmarkTypeList";
NSString* const kWebBookmarkTypeLeaf = @"WebBookmarkTypeLeaf";
BookmarkNode::MetaInfoMap MetaInfoMapFromDictionary(NSDictionary* dictionary) {
BookmarkNode::MetaInfoMap meta_info_map;
for (NSString* key in dictionary) {
meta_info_map[base::SysNSStringToUTF8(key)] =
base::SysNSStringToUTF8([dictionary objectForKey:key]);
}
return meta_info_map;
}
void ConvertPlistToElements(NSArray* input,
std::vector<BookmarkNodeData::Element>& elements) {
NSUInteger len = [input count];
for (NSUInteger i = 0; i < len; ++i) {
NSDictionary* pboardBookmark = [input objectAtIndex:i];
std::unique_ptr<BookmarkNode> new_node(new BookmarkNode(GURL()));
int64_t node_id =
[[pboardBookmark objectForKey:kChromiumBookmarkId] longLongValue];
new_node->set_id(node_id);
NSDictionary* metaInfoDictionary =
[pboardBookmark objectForKey:kChromiumBookmarkMetaInfo];
if (metaInfoDictionary)
new_node->SetMetaInfoMap(MetaInfoMapFromDictionary(metaInfoDictionary));
BOOL is_folder = [[pboardBookmark objectForKey:kWebBookmarkType]
isEqualToString:kWebBookmarkTypeList];
if (is_folder) {
new_node->set_type(BookmarkNode::FOLDER);
NSString* title = [pboardBookmark objectForKey:@"Title"];
new_node->SetTitle(base::SysNSStringToUTF16(title));
} else {
new_node->set_type(BookmarkNode::URL);
NSDictionary* uriDictionary =
[pboardBookmark objectForKey:@"URIDictionary"];
NSString* title = [uriDictionary objectForKey:@"title"];
NSString* urlString = [pboardBookmark objectForKey:@"URLString"];
new_node->SetTitle(base::SysNSStringToUTF16(title));
new_node->set_url(GURL(base::SysNSStringToUTF8(urlString)));
}
BookmarkNodeData::Element e = BookmarkNodeData::Element(new_node.get());
if (is_folder) {
ConvertPlistToElements([pboardBookmark objectForKey:@"Children"],
e.children);
}
elements.push_back(e);
}
}
bool ReadBookmarkDictionaryListPboardType(
NSPasteboard* pb,
std::vector<BookmarkNodeData::Element>& elements) {
NSString* uti = ui::ClipboardUtil::UTIForPasteboardType(
kBookmarkDictionaryListPboardType);
NSArray* bookmarks = [pb propertyListForType:uti];
if (!bookmarks)
return false;
ConvertPlistToElements(bookmarks, elements);
return true;
}
bool ReadWebURLsWithTitlesPboardType(
NSPasteboard* pb,
std::vector<BookmarkNodeData::Element>& elements) {
NSArray* urlsArr = nil;
NSArray* titlesArr = nil;
if (!ui::ClipboardUtil::URLsAndTitlesFromPasteboard(pb, &urlsArr, &titlesArr))
return false;
NSUInteger len = [titlesArr count];
for (NSUInteger i = 0; i < len; ++i) {
base::string16 title =
base::SysNSStringToUTF16([titlesArr objectAtIndex:i]);
std::string url = base::SysNSStringToUTF8([urlsArr objectAtIndex:i]);
if (!url.empty()) {
BookmarkNodeData::Element element;
element.is_url = true;
element.url = GURL(url);
element.title = title;
elements.push_back(element);
}
}
return true;
}
NSDictionary* DictionaryFromBookmarkMetaInfo(
const BookmarkNode::MetaInfoMap& meta_info_map) {
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map.begin();
it != meta_info_map.end(); ++it) {
[dictionary setObject:base::SysUTF8ToNSString(it->second)
forKey:base::SysUTF8ToNSString(it->first)];
}
return dictionary;
}
NSArray* GetPlistForBookmarkList(
const std::vector<BookmarkNodeData::Element>& elements) {
NSMutableArray* plist = [NSMutableArray array];
for (size_t i = 0; i < elements.size(); ++i) {
BookmarkNodeData::Element element = elements[i];
NSDictionary* metaInfoDictionary =
DictionaryFromBookmarkMetaInfo(element.meta_info_map);
if (element.is_url) {
NSString* title = base::SysUTF16ToNSString(element.title);
NSString* url = base::SysUTF8ToNSString(element.url.spec());
int64_t elementId = element.id();
NSNumber* idNum = [NSNumber numberWithLongLong:elementId];
NSDictionary* uriDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
title, @"title", nil];
NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys:
uriDictionary, @"URIDictionary",
url, @"URLString",
kWebBookmarkTypeLeaf, kWebBookmarkType,
idNum, kChromiumBookmarkId,
metaInfoDictionary, kChromiumBookmarkMetaInfo,
nil];
[plist addObject:object];
} else {
NSString* title = base::SysUTF16ToNSString(element.title);
NSArray* children = GetPlistForBookmarkList(element.children);
int64_t elementId = element.id();
NSNumber* idNum = [NSNumber numberWithLongLong:elementId];
NSDictionary* object = [NSDictionary dictionaryWithObjectsAndKeys:
title, @"Title",
children, @"Children",
kWebBookmarkTypeList, kWebBookmarkType,
idNum, kChromiumBookmarkId,
metaInfoDictionary, kChromiumBookmarkMetaInfo,
nil];
[plist addObject:object];
}
}
return plist;
}
void WriteBookmarkDictionaryListPboardType(
NSPasteboardItem* item,
const std::vector<BookmarkNodeData::Element>& elements) {
NSArray* plist = GetPlistForBookmarkList(elements);
NSString* uti = ui::ClipboardUtil::UTIForPasteboardType(
kBookmarkDictionaryListPboardType);
[item setPropertyList:plist forType:uti];
}
void FillFlattenedArraysForBookmarks(
const std::vector<BookmarkNodeData::Element>& elements,
NSMutableArray* url_titles,
NSMutableArray* urls,
NSMutableArray* toplevel_string_data) {
for (const BookmarkNodeData::Element& element : elements) {
NSString* title = base::SysUTF16ToNSString(element.title);
if (element.is_url) {
NSString* url = base::SysUTF8ToNSString(element.url.spec());
[url_titles addObject:title];
[urls addObject:url];
if (toplevel_string_data)
[toplevel_string_data addObject:url];
} else {
if (toplevel_string_data)
[toplevel_string_data addObject:title];
FillFlattenedArraysForBookmarks(element.children, url_titles, urls, nil);
}
}
}
base::scoped_nsobject<NSPasteboardItem> WriteSimplifiedBookmarkTypes(
const std::vector<BookmarkNodeData::Element>& elements) {
NSMutableArray* url_titles = [NSMutableArray array];
NSMutableArray* urls = [NSMutableArray array];
NSMutableArray* toplevel_string_data = [NSMutableArray array];
FillFlattenedArraysForBookmarks(
elements, url_titles, urls, toplevel_string_data);
base::scoped_nsobject<NSPasteboardItem> item;
if ([urls count] > 0) {
if ([urls count] == 1) {
item = ui::ClipboardUtil::PasteboardItemFromUrl([urls firstObject],
[url_titles firstObject]);
} else {
item = ui::ClipboardUtil::PasteboardItemFromUrls(urls, url_titles);
}
}
if (!item) {
item.reset([[NSPasteboardItem alloc] init]);
}
[item setString:[toplevel_string_data componentsJoinedByString:@"\n"]
forType:NSPasteboardTypeString];
return item;
}
NSPasteboard* PasteboardFromType(ui::ClipboardType type) {
NSString* type_string = nil;
switch (type) {
case ui::CLIPBOARD_TYPE_COPY_PASTE:
type_string = NSGeneralPboard;
break;
case ui::CLIPBOARD_TYPE_DRAG:
type_string = NSDragPboard;
break;
case ui::CLIPBOARD_TYPE_SELECTION:
NOTREACHED();
break;
}
return [NSPasteboard pasteboardWithName:type_string];
}
} // namespace
NSPasteboardItem* PasteboardItemFromBookmarks(
const std::vector<BookmarkNodeData::Element>& elements,
const base::FilePath& profile_path) {
base::scoped_nsobject<NSPasteboardItem> item =
WriteSimplifiedBookmarkTypes(elements);
WriteBookmarkDictionaryListPboardType(item, elements);
NSString* uti =
ui::ClipboardUtil::UTIForPasteboardType(kChromiumProfilePathPboardType);
[item setString:base::SysUTF8ToNSString(profile_path.value()) forType:uti];
return [[item retain] autorelease];
}
void WriteBookmarksToPasteboard(
ui::ClipboardType type,
const std::vector<BookmarkNodeData::Element>& elements,
const base::FilePath& profile_path) {
if (elements.empty())
return;
NSPasteboardItem* item = PasteboardItemFromBookmarks(elements, profile_path);
NSPasteboard* pb = PasteboardFromType(type);
[pb clearContents];
[pb writeObjects:@[ item ]];
}
bool ReadBookmarksFromPasteboard(
ui::ClipboardType type,
std::vector<BookmarkNodeData::Element>& elements,
base::FilePath* profile_path) {
NSPasteboard* pb = PasteboardFromType(type);
elements.clear();
NSString* uti =
ui::ClipboardUtil::UTIForPasteboardType(kChromiumProfilePathPboardType);
NSString* profile = [pb stringForType:uti];
*profile_path = base::FilePath(base::SysNSStringToUTF8(profile));
return ReadBookmarkDictionaryListPboardType(pb, elements) ||
ReadWebURLsWithTitlesPboardType(pb, elements);
}
bool PasteboardContainsBookmarks(ui::ClipboardType type) {
NSPasteboard* pb = PasteboardFromType(type);
NSArray* availableTypes = @[
ui::ClipboardUtil::UTIForWebURLsAndTitles(),
ui::ClipboardUtil::UTIForPasteboardType(kBookmarkDictionaryListPboardType)
];
return [pb availableTypeFromArray:availableTypes] != nil;
}
} // namespace bookmarks