blob: 5e97f18e5264c7dc64283d3ea0108bab4700bc87 [file]
// 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;
}