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;
@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]
- (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];
namespace ui {
namespace {
class OwningProvider : public OSExchangeDataProviderMac {
: 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();
scoped_refptr<ui::UniquePasteboard> owned_pasteboard_;
class WrappingProvider : public OSExchangeDataProviderMac {
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_; }
base::scoped_nsobject<NSPasteboard> wrapped_pasteboard_;
} // namespace
OSExchangeDataProviderMac::OSExchangeDataProviderMac() = default;
const OSExchangeDataProviderMac&) = default;
OSExchangeDataProviderMac& OSExchangeDataProviderMac::operator=(
const OSExchangeDataProviderMac&) = default;
OSExchangeDataProviderMac::~OSExchangeDataProviderMac() = default;
// static
OSExchangeDataProviderMac::CreateProvider() {
return std::make_unique<OwningProvider>();
// static
NSPasteboard* pasteboard) {
return std::make_unique<WrappingProvider>(pasteboard);
void OSExchangeDataProviderMac::MarkOriginatedFromRenderer() {
bool OSExchangeDataProviderMac::DidOriginateFromRenderer() const {
return false;
void OSExchangeDataProviderMac::SetString(const base::string16& string) {
[GetPasteboard() setString:base::SysUTF16ToNSString(string)
void OSExchangeDataProviderMac::SetURL(const GURL& url,
const base::string16& title) {
base::scoped_nsobject<NSPasteboardItem> item =
ui::ClipboardUtil::AddDataToPasteboard(GetPasteboard(), item);
void OSExchangeDataProviderMac::SetFilename(const base::FilePath& path) {
[GetPasteboard() setPropertyList:@[ base::SysUTF8ToNSString(path.value()) ]
void OSExchangeDataProviderMac::SetFilenames(
const std::vector<FileInfo>& filenames) {
if (filenames.empty())
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 length:data.size()];
[GetPasteboard() setData:ns_data forType:format.ToNSString()];
bool OSExchangeDataProviderMac::GetString(base::string16* data) const {
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 {
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)
{base::FilePath(base::SysNSStringToUTF8(path)), base::FilePath()});
return true;
bool OSExchangeDataProviderMac::GetPickledData(
const ClipboardFormatType& format,
base::Pickle* data) const {
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 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_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