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 "ui/base/dragdrop/os_exchange_data_provider_mac.h" 6 7#import <Cocoa/Cocoa.h> 8 9#include "base/logging.h" 10#include "base/memory/ptr_util.h" 11#include "base/pickle.h" 12#include "base/strings/sys_string_conversions.h" 13#include "base/strings/utf_string_conversions.h" 14#import "third_party/mozilla/NSPasteboard+Utils.h" 15#include "ui/base/clipboard/clipboard_constants.h" 16#include "ui/base/clipboard/clipboard_format_type.h" 17#import "ui/base/clipboard/clipboard_util_mac.h" 18#include "ui/base/clipboard/custom_data_helper.h" 19#import "ui/base/dragdrop/cocoa_dnd_util.h" 20#include "ui/base/dragdrop/file_info/file_info.h" 21#include "url/gurl.h" 22 23@interface CrPasteboardItemWrapper : NSObject <NSPasteboardWriting> 24- (instancetype)initWithPasteboardItem:(NSPasteboardItem*)pasteboardItem; 25@end 26 27@implementation CrPasteboardItemWrapper { 28 base::scoped_nsobject<NSPasteboardItem> _pasteboardItem; 29} 30 31- (instancetype)initWithPasteboardItem:(NSPasteboardItem*)pasteboardItem { 32 if ((self = [super init])) { 33 _pasteboardItem.reset([pasteboardItem retain]); 34 } 35 36 return self; 37} 38 39- (NSArray<NSString*>*)writableTypesForPasteboard:(NSPasteboard*)pasteboard { 40 // If the NSPasteboardItem hasn't been added to an NSPasteboard, then the 41 // -[NSPasteboardItem writableTypesForPasteboard:] will return -types. But if 42 // it has been added to a pasteboard, it will return nil. This pasteboard item 43 // was added implicitly by adding flavors to the owned pasteboard of 44 // OwningProvider, so call -types to actually get data. 45 // 46 // Merge in the ui::kChromeDragDummyPboardType type, so that all of Chromium 47 // is marked to receive the drags. TODO(avi): Wire up MacViews so that 48 // BridgedContentView properly registers the result of View::GetDropFormats() 49 // rather than OSExchangeDataProviderMac::SupportedPasteboardTypes(). 50 return [[_pasteboardItem types] 51 arrayByAddingObject:ui::kChromeDragDummyPboardType]; 52} 53 54- (NSPasteboardWritingOptions)writingOptionsForType:(NSString*)type 55 pasteboard:(NSPasteboard*)pasteboard { 56 // It is critical to return 0 here. If any flavors are promised, then when the 57 // app quits, AppKit will call in the promises, and the backing pasteboard 58 // will likely be long-deallocated. Yes, AppKit will call in promises for 59 // *all* promised flavors on *all* pasteboards, not just those pasteboards 60 // used for copy/paste. 61 return 0; 62} 63 64- (id)pasteboardPropertyListForType:(NSString*)type { 65 if ([type isEqual:ui::kChromeDragDummyPboardType]) 66 return [NSData data]; 67 68 // Like above, an NSPasteboardItem added to a pasteboard will return nil from 69 // -pasteboardPropertyListForType:, so call -dataForType: instead. 70 return [_pasteboardItem dataForType:type]; 71} 72 73@end 74 75namespace ui { 76 77namespace { 78 79class OwningProvider : public OSExchangeDataProviderMac { 80 public: 81 OwningProvider() 82 : OSExchangeDataProviderMac(), 83 owned_pasteboard_(new ui::UniquePasteboard) {} 84 OwningProvider(const OwningProvider& provider) = default; 85 86 std::unique_ptr<OSExchangeData::Provider> Clone() const override { 87 return std::make_unique<OwningProvider>(*this); 88 } 89 90 NSPasteboard* GetPasteboard() const override { 91 return owned_pasteboard_->get(); 92 } 93 94 private: 95 scoped_refptr<ui::UniquePasteboard> owned_pasteboard_; 96}; 97 98class WrappingProvider : public OSExchangeDataProviderMac { 99 public: 100 WrappingProvider(NSPasteboard* pasteboard) 101 : OSExchangeDataProviderMac(), wrapped_pasteboard_([pasteboard retain]) {} 102 WrappingProvider(const WrappingProvider& provider) = default; 103 104 std::unique_ptr<OSExchangeData::Provider> Clone() const override { 105 return std::make_unique<WrappingProvider>(*this); 106 } 107 108 NSPasteboard* GetPasteboard() const override { return wrapped_pasteboard_; } 109 110 private: 111 base::scoped_nsobject<NSPasteboard> wrapped_pasteboard_; 112}; 113 114} // namespace 115 116OSExchangeDataProviderMac::OSExchangeDataProviderMac() = default; 117OSExchangeDataProviderMac::OSExchangeDataProviderMac( 118 const OSExchangeDataProviderMac&) = default; 119OSExchangeDataProviderMac& OSExchangeDataProviderMac::operator=( 120 const OSExchangeDataProviderMac&) = default; 121 122OSExchangeDataProviderMac::~OSExchangeDataProviderMac() = default; 123 124// static 125std::unique_ptr<OSExchangeDataProviderMac> 126OSExchangeDataProviderMac::CreateProvider() { 127 return std::make_unique<OwningProvider>(); 128} 129 130// static 131std::unique_ptr<OSExchangeDataProviderMac> 132OSExchangeDataProviderMac::CreateProviderWrappingPasteboard( 133 NSPasteboard* pasteboard) { 134 return std::make_unique<WrappingProvider>(pasteboard); 135} 136 137void OSExchangeDataProviderMac::MarkOriginatedFromRenderer() { 138 NOTIMPLEMENTED(); 139} 140 141bool OSExchangeDataProviderMac::DidOriginateFromRenderer() const { 142 NOTIMPLEMENTED(); 143 return false; 144} 145 146void OSExchangeDataProviderMac::SetString(const base::string16& string) { 147 [GetPasteboard() setString:base::SysUTF16ToNSString(string) 148 forType:NSPasteboardTypeString]; 149} 150 151void OSExchangeDataProviderMac::SetURL(const GURL& url, 152 const base::string16& title) { 153 base::scoped_nsobject<NSPasteboardItem> item = 154 ClipboardUtil::PasteboardItemFromUrl(base::SysUTF8ToNSString(url.spec()), 155 base::SysUTF16ToNSString(title)); 156 ui::ClipboardUtil::AddDataToPasteboard(GetPasteboard(), item); 157} 158 159void OSExchangeDataProviderMac::SetFilename(const base::FilePath& path) { 160 [GetPasteboard() setPropertyList:@[ base::SysUTF8ToNSString(path.value()) ] 161 forType:NSFilenamesPboardType]; 162} 163 164void OSExchangeDataProviderMac::SetFilenames( 165 const std::vector<FileInfo>& filenames) { 166 if (filenames.empty()) 167 return; 168 169 NSMutableArray* paths = [NSMutableArray arrayWithCapacity:filenames.size()]; 170 171 for (const auto& filename : filenames) { 172 NSString* path = base::SysUTF8ToNSString(filename.path.value()); 173 [paths addObject:path]; 174 } 175 [GetPasteboard() setPropertyList:paths forType:NSFilenamesPboardType]; 176} 177 178void OSExchangeDataProviderMac::SetPickledData( 179 const ClipboardFormatType& format, 180 const base::Pickle& data) { 181 NSData* ns_data = [NSData dataWithBytes:data.data() length:data.size()]; 182 [GetPasteboard() setData:ns_data forType:format.ToNSString()]; 183} 184 185bool OSExchangeDataProviderMac::GetString(base::string16* data) const { 186 DCHECK(data); 187 NSString* item = [GetPasteboard() stringForType:NSPasteboardTypeString]; 188 if (item) { 189 *data = base::SysNSStringToUTF16(item); 190 return true; 191 } 192 193 // There was no NSString, check for an NSURL. 194 GURL url; 195 base::string16 title; 196 bool result = 197 GetURLAndTitle(OSExchangeData::DO_NOT_CONVERT_FILENAMES, &url, &title); 198 if (result) 199 *data = base::UTF8ToUTF16(url.spec()); 200 201 return result; 202} 203 204bool OSExchangeDataProviderMac::GetURLAndTitle( 205 OSExchangeData::FilenameToURLPolicy policy, 206 GURL* url, 207 base::string16* title) const { 208 DCHECK(url); 209 DCHECK(title); 210 211 if (ui::PopulateURLAndTitleFromPasteboard(url, title, GetPasteboard(), 212 false)) { 213 return true; 214 } 215 216 // If there are no URLs, try to convert a filename to a URL if the policy 217 // allows it. The title remains blank. 218 // 219 // This could be done in the call to PopulateURLAndTitleFromPasteboard above 220 // if |true| were passed in as the last parameter, but that function strips 221 // the trailing slashes off of paths and always returns the last path element 222 // as the title whereas no path conversion nor title is wanted. 223 base::FilePath path; 224 if (policy != OSExchangeData::DO_NOT_CONVERT_FILENAMES && 225 GetFilename(&path)) { 226 NSURL* fileUrl = 227 [NSURL fileURLWithPath:base::SysUTF8ToNSString(path.value())]; 228 *url = 229 GURL([[fileUrl absoluteString] stringByStandardizingPath].UTF8String); 230 return true; 231 } 232 233 return false; 234} 235 236bool OSExchangeDataProviderMac::GetFilename(base::FilePath* path) const { 237 NSArray* paths = [GetPasteboard() propertyListForType:NSFilenamesPboardType]; 238 if ([paths count] == 0) 239 return false; 240 241 *path = base::FilePath(base::SysNSStringToUTF8(paths[0])); 242 return true; 243} 244 245bool OSExchangeDataProviderMac::GetFilenames( 246 std::vector<FileInfo>* filenames) const { 247 NSArray* paths = [GetPasteboard() propertyListForType:NSFilenamesPboardType]; 248 if ([paths count] == 0) 249 return false; 250 251 for (NSString* path in paths) 252 filenames->push_back( 253 {base::FilePath(base::SysNSStringToUTF8(path)), base::FilePath()}); 254 255 return true; 256} 257 258bool OSExchangeDataProviderMac::GetPickledData( 259 const ClipboardFormatType& format, 260 base::Pickle* data) const { 261 DCHECK(data); 262 NSData* ns_data = [GetPasteboard() dataForType:format.ToNSString()]; 263 if (!ns_data) 264 return false; 265 266 *data = 267 base::Pickle(static_cast<const char*>([ns_data bytes]), [ns_data length]); 268 return true; 269} 270 271bool OSExchangeDataProviderMac::HasString() const { 272 base::string16 string; 273 return GetString(&string); 274} 275 276bool OSExchangeDataProviderMac::HasURL( 277 OSExchangeData::FilenameToURLPolicy policy) const { 278 GURL url; 279 base::string16 title; 280 return GetURLAndTitle(policy, &url, &title); 281} 282 283bool OSExchangeDataProviderMac::HasFile() const { 284 return [[GetPasteboard() types] containsObject:NSFilenamesPboardType]; 285} 286 287bool OSExchangeDataProviderMac::HasCustomFormat( 288 const ClipboardFormatType& format) const { 289 return [[GetPasteboard() types] containsObject:format.ToNSString()]; 290} 291 292void OSExchangeDataProviderMac::SetDragImage( 293 const gfx::ImageSkia& image, 294 const gfx::Vector2d& cursor_offset) { 295 drag_image_ = image; 296 cursor_offset_ = cursor_offset; 297} 298 299gfx::ImageSkia OSExchangeDataProviderMac::GetDragImage() const { 300 return drag_image_; 301} 302 303gfx::Vector2d OSExchangeDataProviderMac::GetDragImageOffset() const { 304 return cursor_offset_; 305} 306 307NSDraggingItem* OSExchangeDataProviderMac::GetDraggingItem() const { 308 // What's going on here is that initiating a drag (-[NSView 309 // beginDraggingSessionWithItems...]) requires a dragging item. Even though 310 // pasteboard items are NSPasteboardWriters, they are locked to their 311 // pasteboard and cannot be used to initiate a drag with another pasteboard 312 // (hello https://crbug.com/928684). Therefore, wrap them. 313 // 314 // OSExchangeDataProviderMac was written to the old NSPasteboard APIs that 315 // didn't account for more than one item. This kinda matches Views which also 316 // assumes that only one drag item can exist at a time. TODO(avi): Fix all of 317 // Views to be able to handle drags of more than one item. Then rewrite 318 // OSExchangeDataProviderMac to the new NSPasteboard item API. 319 320 NSArray* pasteboardItems = [GetPasteboard() pasteboardItems]; 321 DCHECK(pasteboardItems); 322 DCHECK_EQ(1u, [pasteboardItems count]); 323 324 CrPasteboardItemWrapper* wrapper = [[[CrPasteboardItemWrapper alloc] 325 initWithPasteboardItem:[pasteboardItems firstObject]] autorelease]; 326 327 NSDraggingItem* drag_item = 328 [[[NSDraggingItem alloc] initWithPasteboardWriter:wrapper] autorelease]; 329 330 return drag_item; 331} 332 333// static 334NSArray* OSExchangeDataProviderMac::SupportedPasteboardTypes() { 335 return @[ 336 kWebCustomDataPboardType, ui::ClipboardUtil::UTIForWebURLsAndTitles(), 337 NSURLPboardType, NSFilenamesPboardType, ui::kChromeDragDummyPboardType, 338 NSStringPboardType, NSHTMLPboardType, NSRTFPboardType, 339 NSFilenamesPboardType, ui::kWebCustomDataPboardType, NSPasteboardTypeString 340 ]; 341} 342 343} // namespace ui 344