| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "components/open_from_clipboard/clipboard_recent_content_ios.h" |
| |
| #import <CommonCrypto/CommonDigest.h> |
| #import <UIKit/UIKit.h> |
| #import <stddef.h> |
| #import <stdint.h> |
| |
| #import "base/metrics/user_metrics.h" |
| #import "base/notimplemented.h" |
| #import "base/notreached.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/system/sys_info.h" |
| #import "base/task/sequenced_task_runner.h" |
| #import "components/open_from_clipboard/clipboard_recent_content_impl_ios.h" |
| #import "net/base/apple/url_conversions.h" |
| #import "url/gurl.h" |
| #import "url/url_constants.h" |
| |
| namespace { |
| |
| // Schemes accepted by the ClipboardRecentContentIOS. |
| constexpr std::array<const char*, 4> kAuthorizedSchemes = { |
| url::kHttpScheme, |
| url::kHttpsScheme, |
| url::kDataScheme, |
| url::kAboutScheme, |
| }; |
| |
| // Get the list of authorized schemes. |
| NSSet<NSString*>* GetAuthorizedSchemeList(std::string_view application_scheme) { |
| NSMutableSet<NSString*>* schemes = [NSMutableSet set]; |
| for (const char* scheme : kAuthorizedSchemes) { |
| [schemes addObject:base::SysUTF8ToNSString(scheme)]; |
| } |
| if (!application_scheme.empty()) { |
| [schemes addObject:base::SysUTF8ToNSString(application_scheme)]; |
| } |
| |
| return [schemes copy]; |
| } |
| |
| ContentType ContentTypeFromClipboardContentType(ClipboardContentType type) { |
| switch (type) { |
| case ClipboardContentType::URL: |
| return ContentTypeURL; |
| case ClipboardContentType::Text: |
| return ContentTypeText; |
| case ClipboardContentType::Image: |
| return ContentTypeImage; |
| } |
| } |
| |
| ClipboardContentType ClipboardContentTypeFromContentType(ContentType type) { |
| if ([type isEqualToString:ContentTypeURL]) { |
| return ClipboardContentType::URL; |
| } else if ([type isEqualToString:ContentTypeText]) { |
| return ClipboardContentType::Text; |
| } else if ([type isEqualToString:ContentTypeImage]) { |
| return ClipboardContentType::Image; |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace |
| |
| @interface ClipboardRecentContentDelegateImpl |
| : NSObject<ClipboardRecentContentDelegate> |
| @end |
| |
| @implementation ClipboardRecentContentDelegateImpl |
| |
| - (void)onClipboardChanged { |
| base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged")); |
| } |
| |
| @end |
| |
| ClipboardRecentContentIOS::ClipboardRecentContentIOS( |
| std::string_view application_scheme, |
| NSUserDefaults* group_user_defaults) |
| : ClipboardRecentContentIOS([[ClipboardRecentContentImplIOS alloc] |
| initWithMaxAge:MaximumAgeOfClipboard().InSecondsF() |
| authorizedSchemes:GetAuthorizedSchemeList(application_scheme) |
| userDefaults:group_user_defaults |
| delegate:[[ClipboardRecentContentDelegateImpl alloc] |
| init]]) {} |
| |
| ClipboardRecentContentIOS::ClipboardRecentContentIOS( |
| ClipboardRecentContentImplIOS* implementation) { |
| implementation_ = implementation; |
| } |
| |
| void ClipboardRecentContentIOS::HasRecentContentFromClipboard( |
| std::set<ClipboardContentType> types, |
| HasDataCallback callback) { |
| __block HasDataCallback callback_for_block = std::move(callback); |
| NSMutableSet<ContentType>* ios_types = [NSMutableSet set]; |
| for (ClipboardContentType type : types) { |
| [ios_types addObject:ContentTypeFromClipboardContentType(type)]; |
| } |
| // The iOS methods for checking clipboard content call their callbacks on an |
| // arbitrary thread. As Objective-C doesn't have very good thread-management |
| // techniques, make sure this method calls its callback on the same thread |
| // that it was called on. |
| scoped_refptr<base::SequencedTaskRunner> task_runner = |
| base::SequencedTaskRunner::GetCurrentDefault(); |
| [implementation_ |
| hasContentMatchingTypes:ios_types |
| completionHandler:^(NSSet<ContentType>* results) { |
| std::set<ClipboardContentType> matching_types; |
| for (ContentType type in results) { |
| matching_types.insert( |
| ClipboardContentTypeFromContentType(type)); |
| } |
| task_runner->PostTask( |
| FROM_HERE, base::BindOnce(^{ |
| std::move(callback_for_block).Run(matching_types); |
| })); |
| }]; |
| } |
| |
| // This value will be nullopt during the brief period |
| // when the clipboard is updating its cache, which is triggered by a |
| // pasteboardDidChange notification. It may also be nullopt if the app decides |
| // it should not return the value of the clipboard, for example if the current |
| // clipboard contents are too old. |
| std::optional<std::set<ClipboardContentType>> |
| ClipboardRecentContentIOS::GetCachedClipboardContentTypes() { |
| NSSet<ContentType>* current_content_types = |
| [implementation_ cachedClipboardContentTypes]; |
| if (!current_content_types) { |
| return std::nullopt; |
| } |
| std::set<ClipboardContentType> current_content_types_ios; |
| |
| for (ContentType type in current_content_types) { |
| current_content_types_ios.insert(ClipboardContentTypeFromContentType(type)); |
| } |
| |
| return current_content_types_ios; |
| } |
| |
| void ClipboardRecentContentIOS::GetRecentURLFromClipboard( |
| GetRecentURLCallback callback) { |
| __block GetRecentURLCallback callback_for_block = std::move(callback); |
| // The iOS methods for checking clipboard content call their callbacks on an |
| // arbitrary thread. As Objective-C doesn't have very good thread-management |
| // techniques, make sure this method calls its callback on the same thread |
| // that it was called on. |
| scoped_refptr<base::SequencedTaskRunner> task_runner = |
| base::SequencedTaskRunner::GetCurrentDefault(); |
| [implementation_ recentURLFromClipboard:^(NSURL* url) { |
| GURL converted_url = net::GURLWithNSURL(url); |
| if (!converted_url.is_valid()) { |
| task_runner->PostTask(FROM_HERE, base::BindOnce(^{ |
| std::move(callback_for_block).Run(std::nullopt); |
| })); |
| return; |
| } |
| task_runner->PostTask(FROM_HERE, base::BindOnce(^{ |
| std::move(callback_for_block).Run(converted_url); |
| })); |
| }]; |
| } |
| |
| void ClipboardRecentContentIOS::GetRecentTextFromClipboard( |
| GetRecentTextCallback callback) { |
| __block GetRecentTextCallback callback_for_block = std::move(callback); |
| // The iOS methods for checking clipboard content call their callbacks on an |
| // arbitrary thread. As Objective-C doesn't have very good thread-management |
| // techniques, make sure this method calls its callback on the same thread |
| // that it was called on. |
| scoped_refptr<base::SequencedTaskRunner> task_runner = |
| base::SequencedTaskRunner::GetCurrentDefault(); |
| [implementation_ recentTextFromClipboard:^(NSString* text) { |
| if (!text) { |
| task_runner->PostTask(FROM_HERE, base::BindOnce(^{ |
| std::move(callback_for_block).Run(std::nullopt); |
| })); |
| return; |
| } |
| task_runner->PostTask( |
| FROM_HERE, base::BindOnce(^{ |
| std::move(callback_for_block).Run(base::SysNSStringToUTF16(text)); |
| })); |
| }]; |
| } |
| |
| void ClipboardRecentContentIOS::GetRecentImageFromClipboard( |
| GetRecentImageCallback callback) { |
| __block GetRecentImageCallback callback_for_block = std::move(callback); |
| // The iOS methods for checking clipboard content call their callbacks on an |
| // arbitrary thread. As Objective-C doesn't have very good thread-management |
| // techniques, make sure this method calls its callback on the same thread |
| // that it was called on. |
| scoped_refptr<base::SequencedTaskRunner> task_runner = |
| base::SequencedTaskRunner::GetCurrentDefault(); |
| [implementation_ recentImageFromClipboard:^(UIImage* image) { |
| if (!image) { |
| task_runner->PostTask(FROM_HERE, base::BindOnce(^{ |
| std::move(callback_for_block).Run(std::nullopt); |
| })); |
| return; |
| } |
| task_runner->PostTask( |
| FROM_HERE, base::BindOnce(^{ |
| std::move(callback_for_block).Run(gfx::Image(image)); |
| })); |
| }]; |
| } |
| |
| ClipboardRecentContentIOS::~ClipboardRecentContentIOS() = default; |
| |
| base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const { |
| return base::Seconds( |
| static_cast<int64_t>([implementation_ clipboardContentAge])); |
| } |
| |
| void ClipboardRecentContentIOS::SuppressClipboardContent() { |
| [implementation_ suppressClipboardContent]; |
| } |
| |
| void ClipboardRecentContentIOS::ClearClipboardContent() { |
| NOTIMPLEMENTED(); |
| return; |
| } |