| // Copyright 2014 The Chromium Authors |
| // 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 <algorithm> |
| #include <optional> |
| #include <string_view> |
| |
| #include "base/apple/foundation_util.h" |
| #include "base/check_op.h" |
| #include "base/containers/span.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/notimplemented.h" |
| #include "base/pickle.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "net/base/filename_util.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" |
| #include "ui/base/clipboard/file_info.h" |
| #include "ui/base/data_transfer_policy/data_transfer_policy_controller.h" |
| #include "url/gurl.h" |
| |
| @interface CrPasteboardItemWrapper : NSObject <NSPasteboardWriting> |
| - (instancetype)initWithPasteboardItem:(NSPasteboardItem*)pasteboardItem; |
| @end |
| |
| @implementation CrPasteboardItemWrapper { |
| NSPasteboardItem* __strong _pasteboardItem; |
| } |
| |
| - (instancetype)initWithPasteboardItem:(NSPasteboardItem*)pasteboardItem { |
| if ((self = [super init])) { |
| _pasteboardItem = pasteboardItem; |
| } |
| |
| 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::kUTTypeChromiumInitiatedDrag 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::kUTTypeChromiumInitiatedDrag]; |
| } |
| |
| - (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::kUTTypeChromiumInitiatedDrag]) |
| 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() : owned_pasteboard_(new UniquePasteboard) {} |
| OwningProvider(const OwningProvider& provider) = default; |
| |
| std::unique_ptr<OSExchangeDataProvider> Clone() const override { |
| return std::make_unique<OwningProvider>(*this); |
| } |
| |
| NSPasteboard* GetPasteboard() const override { |
| return owned_pasteboard_->get(); |
| } |
| |
| private: |
| scoped_refptr<UniquePasteboard> owned_pasteboard_; |
| }; |
| |
| class WrappingProvider : public OSExchangeDataProviderMac { |
| public: |
| explicit WrappingProvider(NSPasteboard* pasteboard) |
| : wrapped_pasteboard_(pasteboard) {} |
| WrappingProvider(const WrappingProvider& provider) = default; |
| |
| std::unique_ptr<OSExchangeDataProvider> Clone() const override { |
| return std::make_unique<WrappingProvider>(*this); |
| } |
| |
| NSPasteboard* GetPasteboard() const override { return wrapped_pasteboard_; } |
| |
| private: |
| __strong 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::MarkRendererTaintedFromOrigin( |
| const url::Origin& origin) { |
| NSString* string = origin.opaque() |
| ? [NSString string] |
| : base::SysUTF8ToNSString(origin.Serialize()); |
| [GetPasteboard() setString:string |
| forType:kUTTypeChromiumRendererInitiatedDrag]; |
| } |
| |
| bool OSExchangeDataProviderMac::IsRendererTainted() const { |
| return [GetPasteboard().types |
| containsObject:kUTTypeChromiumRendererInitiatedDrag]; |
| } |
| |
| std::optional<url::Origin> OSExchangeDataProviderMac::GetRendererTaintedOrigin() |
| const { |
| NSString* item = |
| [GetPasteboard() stringForType:kUTTypeChromiumRendererInitiatedDrag]; |
| if (!item) { |
| return std::nullopt; |
| } |
| |
| if (0 == [item length]) { |
| return url::Origin(); |
| } |
| |
| return url::Origin::Create(GURL(base::SysNSStringToUTF8(item))); |
| } |
| |
| void OSExchangeDataProviderMac::MarkAsFromPrivileged() { |
| [GetPasteboard() setData:[NSData data] |
| forType:kUTTypeChromiumPrivilegedInitiatedDrag]; |
| } |
| |
| bool OSExchangeDataProviderMac::IsFromPrivileged() const { |
| return [GetPasteboard().types |
| containsObject:kUTTypeChromiumPrivilegedInitiatedDrag]; |
| } |
| |
| void OSExchangeDataProviderMac::SetString(std::u16string_view string) { |
| [GetPasteboard() setString:base::SysUTF16ToNSString(string) |
| forType:NSPasteboardTypeString]; |
| } |
| |
| void OSExchangeDataProviderMac::SetURL(const GURL& url, |
| std::u16string_view title) { |
| NSArray<NSPasteboardItem*>* items = clipboard_util::PasteboardItemsFromUrls( |
| @[ base::SysUTF8ToNSString(url.spec()) ], |
| @[ base::SysUTF16ToNSString(title) ]); |
| clipboard_util::AddDataToPasteboard(GetPasteboard(), items.firstObject); |
| } |
| |
| void OSExchangeDataProviderMac::SetFilename(const base::FilePath& path) { |
| std::vector<FileInfo> filenames(1, FileInfo(path, base::FilePath())); |
| clipboard_util::WriteFilesToPasteboard(GetPasteboard(), filenames); |
| } |
| |
| void OSExchangeDataProviderMac::SetFilenames( |
| const std::vector<FileInfo>& filenames) { |
| clipboard_util::WriteFilesToPasteboard(GetPasteboard(), filenames); |
| } |
| |
| 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()]; |
| } |
| |
| std::optional<std::u16string> OSExchangeDataProviderMac::GetString() const { |
| NSString* item = [GetPasteboard() stringForType:NSPasteboardTypeString]; |
| if (item) { |
| return base::SysNSStringToUTF16(item); |
| } |
| |
| // There was no NSString, check for an NSURL. |
| if (std::optional<UrlInfo> url_info = |
| GetURLAndTitle(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES); |
| url_info.has_value()) { |
| return base::UTF8ToUTF16(url_info->url.spec()); |
| } |
| |
| return std::nullopt; |
| } |
| |
| std::optional<OSExchangeDataProvider::UrlInfo> |
| OSExchangeDataProviderMac::GetURLAndTitle(FilenameToURLPolicy policy) const { |
| NSArray<URLAndTitle*>* urls_and_titles = |
| clipboard_util::URLsAndTitlesFromPasteboard( |
| GetPasteboard(), policy == FilenameToURLPolicy::CONVERT_FILENAMES); |
| if (!urls_and_titles.count) { |
| return std::nullopt; |
| } |
| |
| GURL url(base::SysNSStringToUTF8(urls_and_titles.firstObject.URL)); |
| return UrlInfo{std::move(url), |
| base::SysNSStringToUTF16(urls_and_titles.firstObject.title)}; |
| } |
| |
| std::optional<std::vector<GURL>> OSExchangeDataProviderMac::GetURLs( |
| FilenameToURLPolicy policy) const { |
| NSArray<URLAndTitle*>* urls_and_titles = |
| clipboard_util::URLsAndTitlesFromPasteboard( |
| GetPasteboard(), policy == FilenameToURLPolicy::CONVERT_FILENAMES); |
| if (!urls_and_titles.count) { |
| return std::nullopt; |
| } |
| |
| std::vector<GURL> local_urls; |
| for (URLAndTitle* url_and_title in urls_and_titles) { |
| local_urls.emplace_back(base::SysNSStringToUTF8(url_and_title.URL)); |
| } |
| return local_urls; |
| } |
| |
| std::optional<std::vector<FileInfo>> OSExchangeDataProviderMac::GetFilenames() |
| const { |
| std::vector<FileInfo> files = |
| clipboard_util::FilesFromPasteboard(GetPasteboard()); |
| if (files.empty()) { |
| return std::nullopt; |
| } |
| |
| return files; |
| } |
| |
| std::optional<base::Pickle> OSExchangeDataProviderMac::GetPickledData( |
| const ClipboardFormatType& format) const { |
| NSData* ns_data = [GetPasteboard() dataForType:format.ToNSString()]; |
| if (!ns_data) { |
| return std::nullopt; |
| } |
| |
| return base::Pickle::WithData(base::apple::NSDataToSpan(ns_data)); |
| } |
| |
| bool OSExchangeDataProviderMac::HasString() const { |
| return GetString().has_value(); |
| } |
| |
| bool OSExchangeDataProviderMac::HasURL(FilenameToURLPolicy policy) const { |
| return GetURLAndTitle(policy).has_value(); |
| } |
| |
| bool OSExchangeDataProviderMac::HasFile() const { |
| return [GetPasteboard().types containsObject:NSPasteboardTypeFileURL]; |
| } |
| |
| bool OSExchangeDataProviderMac::HasCustomFormat( |
| const ClipboardFormatType& format) const { |
| return [GetPasteboard().types containsObject:format.ToNSString()]; |
| } |
| |
| void OSExchangeDataProviderMac::SetFileContents( |
| const base::FilePath& filename, |
| const std::string& file_contents) { |
| NOTIMPLEMENTED(); |
| } |
| |
| std::optional<OSExchangeDataProvider::FileContentsInfo> |
| OSExchangeDataProviderMac::GetFileContents() const { |
| NOTIMPLEMENTED(); |
| return std::nullopt; |
| } |
| |
| bool OSExchangeDataProviderMac::HasFileContents() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| 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_; |
| } |
| |
| NSArray<NSDraggingItem*>* OSExchangeDataProviderMac::GetDraggingItems() 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. |
| |
| NSArray<NSPasteboardItem*>* pasteboard_items = |
| GetPasteboard().pasteboardItems; |
| if (!pasteboard_items) { |
| return nil; |
| } |
| |
| NSMutableArray<NSDraggingItem*>* drag_items = [NSMutableArray array]; |
| for (NSPasteboardItem* item in pasteboard_items) { |
| CrPasteboardItemWrapper* wrapper = |
| [[CrPasteboardItemWrapper alloc] initWithPasteboardItem:item]; |
| NSDraggingItem* drag_item = |
| [[NSDraggingItem alloc] initWithPasteboardWriter:wrapper]; |
| |
| [drag_items addObject:drag_item]; |
| } |
| |
| return drag_items; |
| } |
| |
| // static |
| NSArray* OSExchangeDataProviderMac::SupportedPasteboardTypes() { |
| return @[ |
| kUTTypeChromiumInitiatedDrag, kUTTypeChromiumPrivilegedInitiatedDrag, |
| kUTTypeChromiumRendererInitiatedDrag, kUTTypeChromiumDataTransferCustomData, |
| kUTTypeWebKitWebUrlsWithTitles, kUTTypeChromiumSourceUrl, |
| NSPasteboardTypeFileURL, NSPasteboardTypeHTML, NSPasteboardTypeRTF, |
| NSPasteboardTypeString, NSPasteboardTypeURL |
| ]; |
| } |
| |
| void OSExchangeDataProviderMac::SetSource( |
| std::unique_ptr<DataTransferEndpoint> data_source) {} |
| |
| DataTransferEndpoint* OSExchangeDataProviderMac::GetSource() const { |
| return nullptr; |
| } |
| |
| } // namespace ui |