// Copyright 2015 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. #import "components/open_from_clipboard/clipboard_recent_content_ios.h" #import #import #include #include #include "base/metrics/user_metrics.h" #include "base/notreached.h" #include "base/strings/sys_string_conversions.h" #include "base/system/sys_info.h" #include "base/threading/sequenced_task_runner_handle.h" #import "components/open_from_clipboard/clipboard_recent_content_impl_ios.h" #import "net/base/mac/url_conversions.h" #include "url/gurl.h" #include "url/url_constants.h" #if !defined(__has_feature) || !__has_feature(objc_arc) #error "This file requires ARC support." #endif namespace { // Schemes accepted by the ClipboardRecentContentIOS. const char* kAuthorizedSchemes[] = { url::kHttpScheme, url::kHttpsScheme, url::kDataScheme, url::kAboutScheme, }; // Get the list of authorized schemes. NSSet* getAuthorizedSchemeList( const std::string& application_scheme) { NSMutableSet* schemes = [NSMutableSet set]; for (size_t i = 0; i < std::size(kAuthorizedSchemes); ++i) { [schemes addObject:base::SysUTF8ToNSString(kAuthorizedSchemes[i])]; } 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(); return ClipboardContentType::Text; } } // namespace @interface ClipboardRecentContentDelegateImpl : NSObject @end @implementation ClipboardRecentContentDelegateImpl - (void)onClipboardChanged { base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged")); } @end ClipboardRecentContentIOS::ClipboardRecentContentIOS( const std::string& 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; } absl::optional ClipboardRecentContentIOS::GetRecentURLFromClipboard() { NSURL* url_from_pasteboard = [implementation_ recentURLFromClipboard]; GURL converted_url = net::GURLWithNSURL(url_from_pasteboard); if (!converted_url.is_valid()) { return absl::nullopt; } return converted_url; } absl::optional ClipboardRecentContentIOS::GetRecentTextFromClipboard() { NSString* text_from_pasteboard = [implementation_ recentTextFromClipboard]; if (!text_from_pasteboard) { return absl::nullopt; } return base::SysNSStringToUTF16(text_from_pasteboard); } bool ClipboardRecentContentIOS::HasRecentImageFromClipboard() { return GetRecentImageFromClipboardInternal().has_value(); } void ClipboardRecentContentIOS::HasRecentContentFromClipboard( std::set types, HasDataCallback callback) { __block HasDataCallback callback_for_block = std::move(callback); NSMutableSet* 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 task_runner = base::SequencedTaskRunnerHandle::Get(); [implementation_ hasContentMatchingTypes:ios_types completionHandler:^(NSSet* results) { std::set 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. absl::optional> ClipboardRecentContentIOS::GetCachedClipboardContentTypes() { NSSet* current_content_types = [implementation_ cachedClipboardContentTypes]; if (!current_content_types) { return absl::nullopt; } std::set 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 task_runner = base::SequencedTaskRunnerHandle::Get(); [implementation_ recentURLFromClipboardAsync:^(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(absl::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 task_runner = base::SequencedTaskRunnerHandle::Get(); [implementation_ recentTextFromClipboardAsync:^(NSString* text) { if (!text) { task_runner->PostTask(FROM_HERE, base::BindOnce(^{ std::move(callback_for_block).Run(absl::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 task_runner = base::SequencedTaskRunnerHandle::Get(); [implementation_ recentImageFromClipboardAsync:^(UIImage* image) { if (!image) { task_runner->PostTask(FROM_HERE, base::BindOnce(^{ std::move(callback_for_block).Run(absl::nullopt); })); return; } task_runner->PostTask( FROM_HERE, base::BindOnce(^{ std::move(callback_for_block).Run(gfx::Image(image)); })); }]; } ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {} base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const { return base::Seconds( static_cast([implementation_ clipboardContentAge])); } void ClipboardRecentContentIOS::SuppressClipboardContent() { [implementation_ suppressClipboardContent]; } void ClipboardRecentContentIOS::ClearClipboardContent() { NOTIMPLEMENTED(); return; } absl::optional ClipboardRecentContentIOS::GetRecentImageFromClipboardInternal() { UIImage* image_from_pasteboard = [implementation_ recentImageFromClipboard]; if (!image_from_pasteboard) { return absl::nullopt; } return gfx::Image(image_from_pasteboard); }