blob: 8b682036359a84328b3822c3a305b9737f4dc98f [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 "ui/base/dragdrop/os_exchange_data_provider_mac.h"
#import <Cocoa/Cocoa.h>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/pickle.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#import "third_party/mozilla/NSPasteboard+Utils.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/clipboard/clipboard_format_type.h"
#import "ui/base/clipboard/clipboard_util_mac.h"
#include "ui/base/clipboard/custom_data_helper.h"
#import "ui/base/dragdrop/cocoa_dnd_util.h"
#include "ui/base/dragdrop/file_info.h"
#include "url/gurl.h"
@interface CrPasteboardItemWrapper : NSObject <NSPasteboardWriting>
- (instancetype)initWithPasteboardItem:(NSPasteboardItem*)pasteboardItem;
@end
@implementation CrPasteboardItemWrapper {
base::scoped_nsobject<NSPasteboardItem> pasteboardItem_;
}
- (instancetype)initWithPasteboardItem:(NSPasteboardItem*)pasteboardItem {
if ((self = [super init])) {
pasteboardItem_.reset([pasteboardItem retain]);
}
return self;
}
- (NSArray<NSString*>*)writableTypesForPasteboard:(NSPasteboard*)pasteboard {
// If the NSPasteboardItem hasn't been added to an NSPasteboard, then the
// -[NSPasteboardItem writableTypesForPasteboard:] will return -types. But if
// it has been added to a pasteboard, it will return nil. This pasteboard item
// was added implicitly by adding flavors to the owned pasteboard of
// OwningProvider, so call -types to actually get data.
//
// Merge in the ui::kChromeDragDummyPboardType type, so that all of Chromium
// is marked to receive the drags. TODO(avi): Wire up MacViews so that
// BridgedContentView properly registers the result of View::GetDropFormats()
// rather than OSExchangeDataProviderMac::SupportedPasteboardTypes().
return [[pasteboardItem_ types]
arrayByAddingObject:ui::kChromeDragDummyPboardType];
}
- (NSPasteboardWritingOptions)writingOptionsForType:(NSString*)type
pasteboard:(NSPasteboard*)pasteboard {
// It is critical to return 0 here. If any flavors are promised, then when the
// app quits, AppKit will call in the promises, and the backing pasteboard
// will likely be long-deallocated. Yes, AppKit will call in promises for
// *all* promised flavors on *all* pasteboards, not just those pasteboards
// used for copy/paste.
return 0;
}
- (id)pasteboardPropertyListForType:(NSString*)type {
if ([type isEqual:ui::kChromeDragDummyPboardType])
return [NSData data];
// Like above, an NSPasteboardItem added to a pasteboard will return nil from
// -pasteboardPropertyListForType:, so call -dataForType: instead.
return [pasteboardItem_ dataForType:type];
}
@end
namespace ui {
namespace {
class OwningProvider : public OSExchangeDataProviderMac {
public:
OwningProvider()
: OSExchangeDataProviderMac(),
owned_pasteboard_(new ui::UniquePasteboard) {}
OwningProvider(const OwningProvider& provider) = default;
std::unique_ptr<OSExchangeData::Provider> Clone() const override {
return std::make_unique<OwningProvider>(*this);
}
NSPasteboard* GetPasteboard() const override {
return owned_pasteboard_->get();
}
private:
scoped_refptr<ui::UniquePasteboard> owned_pasteboard_;
};
class WrappingProvider : public OSExchangeDataProviderMac {
public:
WrappingProvider(NSPasteboard* pasteboard)
: OSExchangeDataProviderMac(), wrapped_pasteboard_([pasteboard retain]) {}
WrappingProvider(const WrappingProvider& provider) = default;
std::unique_ptr<OSExchangeData::Provider> Clone() const override {
return std::make_unique<WrappingProvider>(*this);
}
NSPasteboard* GetPasteboard() const override { return wrapped_pasteboard_; }
private:
base::scoped_nsobject<NSPasteboard> wrapped_pasteboard_;
};
} // namespace
OSExchangeDataProviderMac::OSExchangeDataProviderMac() = default;
OSExchangeDataProviderMac::OSExchangeDataProviderMac(
const OSExchangeDataProviderMac&) = default;
OSExchangeDataProviderMac& OSExchangeDataProviderMac::operator=(
const OSExchangeDataProviderMac&) = default;
OSExchangeDataProviderMac::~OSExchangeDataProviderMac() = default;
// static
std::unique_ptr<OSExchangeDataProviderMac>
OSExchangeDataProviderMac::CreateProvider() {
return std::make_unique<OwningProvider>();
}
// static
std::unique_ptr<OSExchangeDataProviderMac>
OSExchangeDataProviderMac::CreateProviderWrappingPasteboard(
NSPasteboard* pasteboard) {
return std::make_unique<WrappingProvider>(pasteboard);
}
void OSExchangeDataProviderMac::MarkOriginatedFromRenderer() {
NOTIMPLEMENTED();
}
bool OSExchangeDataProviderMac::DidOriginateFromRenderer() const {
NOTIMPLEMENTED();
return false;
}
void OSExchangeDataProviderMac::SetString(const base::string16& string) {
[GetPasteboard() setString:base::SysUTF16ToNSString(string)
forType:NSPasteboardTypeString];
}
void OSExchangeDataProviderMac::SetURL(const GURL& url,
const base::string16& title) {
base::scoped_nsobject<NSPasteboardItem> item =
ClipboardUtil::PasteboardItemFromUrl(base::SysUTF8ToNSString(url.spec()),
base::SysUTF16ToNSString(title));
ui::ClipboardUtil::AddDataToPasteboard(GetPasteboard(), item);
}
void OSExchangeDataProviderMac::SetFilename(const base::FilePath& path) {
[GetPasteboard() setPropertyList:@[ base::SysUTF8ToNSString(path.value()) ]
forType:NSFilenamesPboardType];
}
void OSExchangeDataProviderMac::SetFilenames(
const std::vector<FileInfo>& filenames) {
if (filenames.empty())
return;
NSMutableArray* paths = [NSMutableArray arrayWithCapacity:filenames.size()];
for (const auto& filename : filenames) {
NSString* path = base::SysUTF8ToNSString(filename.path.value());
[paths addObject:path];
}
[GetPasteboard() setPropertyList:paths forType:NSFilenamesPboardType];
}
void OSExchangeDataProviderMac::SetPickledData(
const ClipboardFormatType& format,
const base::Pickle& data) {
NSData* ns_data = [NSData dataWithBytes:data.data() length:data.size()];
[GetPasteboard() setData:ns_data forType:format.ToNSString()];
}
bool OSExchangeDataProviderMac::GetString(base::string16* data) const {
DCHECK(data);
NSString* item = [GetPasteboard() stringForType:NSPasteboardTypeString];
if (item) {
*data = base::SysNSStringToUTF16(item);
return true;
}
// There was no NSString, check for an NSURL.
GURL url;
base::string16 title;
bool result =
GetURLAndTitle(OSExchangeData::DO_NOT_CONVERT_FILENAMES, &url, &title);
if (result)
*data = base::UTF8ToUTF16(url.spec());
return result;
}
bool OSExchangeDataProviderMac::GetURLAndTitle(
OSExchangeData::FilenameToURLPolicy policy,
GURL* url,
base::string16* title) const {
DCHECK(url);
DCHECK(title);
if (ui::PopulateURLAndTitleFromPasteboard(url, title, GetPasteboard(),
false)) {
return true;
}
// If there are no URLs, try to convert a filename to a URL if the policy
// allows it. The title remains blank.
//
// This could be done in the call to PopulateURLAndTitleFromPasteboard above
// if |true| were passed in as the last parameter, but that function strips
// the trailing slashes off of paths and always returns the last path element
// as the title whereas no path conversion nor title is wanted.
base::FilePath path;
if (policy != OSExchangeData::DO_NOT_CONVERT_FILENAMES &&
GetFilename(&path)) {
NSURL* fileUrl =
[NSURL fileURLWithPath:base::SysUTF8ToNSString(path.value())];
*url =
GURL([[fileUrl absoluteString] stringByStandardizingPath].UTF8String);
return true;
}
return false;
}
bool OSExchangeDataProviderMac::GetFilename(base::FilePath* path) const {
NSArray* paths = [GetPasteboard() propertyListForType:NSFilenamesPboardType];
if ([paths count] == 0)
return false;
*path = base::FilePath(base::SysNSStringToUTF8(paths[0]));
return true;
}
bool OSExchangeDataProviderMac::GetFilenames(
std::vector<FileInfo>* filenames) const {
NSArray* paths = [GetPasteboard() propertyListForType:NSFilenamesPboardType];
if ([paths count] == 0)
return false;
for (NSString* path in paths)
filenames->push_back(
{base::FilePath(base::SysNSStringToUTF8(path)), base::FilePath()});
return true;
}
bool OSExchangeDataProviderMac::GetPickledData(
const ClipboardFormatType& format,
base::Pickle* data) const {
DCHECK(data);
NSData* ns_data = [GetPasteboard() dataForType:format.ToNSString()];
if (!ns_data)
return false;
*data =
base::Pickle(static_cast<const char*>([ns_data bytes]), [ns_data length]);
return true;
}
bool OSExchangeDataProviderMac::HasString() const {
base::string16 string;
return GetString(&string);
}
bool OSExchangeDataProviderMac::HasURL(
OSExchangeData::FilenameToURLPolicy policy) const {
GURL url;
base::string16 title;
return GetURLAndTitle(policy, &url, &title);
}
bool OSExchangeDataProviderMac::HasFile() const {
return [[GetPasteboard() types] containsObject:NSFilenamesPboardType];
}
bool OSExchangeDataProviderMac::HasCustomFormat(
const ClipboardFormatType& format) const {
return [[GetPasteboard() types] containsObject:format.ToNSString()];
}
void OSExchangeDataProviderMac::SetDragImage(
const gfx::ImageSkia& image,
const gfx::Vector2d& cursor_offset) {
drag_image_ = image;
cursor_offset_ = cursor_offset;
}
gfx::ImageSkia OSExchangeDataProviderMac::GetDragImage() const {
return drag_image_;
}
gfx::Vector2d OSExchangeDataProviderMac::GetDragImageOffset() const {
return cursor_offset_;
}
NSDraggingItem* OSExchangeDataProviderMac::GetDraggingItem() const {
// What's going on here is that initiating a drag (-[NSView
// beginDraggingSessionWithItems...]) requires a dragging item. Even though
// pasteboard items are NSPasteboardWriters, they are locked to their
// pasteboard and cannot be used to initiate a drag with another pasteboard
// (hello https://crbug.com/928684). Therefore, wrap them.
//
// OSExchangeDataProviderMac was written to the old NSPasteboard APIs that
// didn't account for more than one item. This kinda matches Views which also
// assumes that only one drag item can exist at a time. TODO(avi): Fix all of
// Views to be able to handle drags of more than one item. Then rewrite
// OSExchangeDataProviderMac to the new NSPasteboard item API.
NSArray* pasteboardItems = [GetPasteboard() pasteboardItems];
DCHECK(pasteboardItems);
DCHECK_EQ(1u, [pasteboardItems count]);
CrPasteboardItemWrapper* wrapper = [[[CrPasteboardItemWrapper alloc]
initWithPasteboardItem:[pasteboardItems firstObject]] autorelease];
NSDraggingItem* drag_item =
[[[NSDraggingItem alloc] initWithPasteboardWriter:wrapper] autorelease];
return drag_item;
}
// static
NSArray* OSExchangeDataProviderMac::SupportedPasteboardTypes() {
return @[
kWebCustomDataPboardType, ui::ClipboardUtil::UTIForWebURLsAndTitles(),
NSURLPboardType, NSFilenamesPboardType, ui::kChromeDragDummyPboardType,
NSStringPboardType, NSHTMLPboardType, NSRTFPboardType,
NSFilenamesPboardType, ui::kWebCustomDataPboardType, NSPasteboardTypeString
];
}
} // namespace ui