// 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>
#include <stddef.h>
#include <stdint.h>
#include "base/metrics/user_metrics.h"
#include "base/notreached.h"
#include "base/strings/sys_string_conversions.h"
#include "base/system/sys_info.h"
#import "base/task/sequenced_task_runner.h"
#include "base/task/sequenced_task_runner.h"
#import "components/open_from_clipboard/clipboard_recent_content_impl_ios.h"
#import "net/base/apple/url_conversions.h"
#include "url/gurl.h"
#include "url/url_constants.h"
namespace {
// Schemes accepted by the ClipboardRecentContentIOS.
const char* kAuthorizedSchemes[] = {
url::kHttpScheme, url::kHttpsScheme, url::kDataScheme, url::kAboutScheme,
};
// Get the list of authorized schemes.
NSSet<NSString*>* getAuthorizedSchemeList(
const std::string& application_scheme) {
NSMutableSet<NSString*>* 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_IN_MIGRATION();
return ClipboardContentType::Text;
}
} // namespace
@interface ClipboardRecentContentDelegateImpl
: NSObject<ClipboardRecentContentDelegate>
@end
@implementation ClipboardRecentContentDelegateImpl
- (void)onClipboardChanged {
base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged"));
}
@end
ClipboardRecentContentIOS::ClipboardRecentContentIOS(
const std::string& application_scheme,
NSUserDefaults* group_user_defaults,
bool only_use_clipboard_async)
: ClipboardRecentContentIOS([[ClipboardRecentContentImplIOS alloc]
initWithMaxAge:MaximumAgeOfClipboard().InSecondsF()
authorizedSchemes:getAuthorizedSchemeList(application_scheme)
userDefaults:group_user_defaults
onlyUseClipboardAsync:only_use_clipboard_async
delegate:[[ClipboardRecentContentDelegateImpl alloc]
init]]) {}
ClipboardRecentContentIOS::ClipboardRecentContentIOS(
ClipboardRecentContentImplIOS* implementation) {
implementation_ = implementation;
}
std::optional<GURL> ClipboardRecentContentIOS::GetRecentURLFromClipboard() {
NSURL* url_from_pasteboard = [implementation_ recentURLFromClipboard];
GURL converted_url = net::GURLWithNSURL(url_from_pasteboard);
if (!converted_url.is_valid()) {
return std::nullopt;
}
return converted_url;
}
std::optional<std::u16string>
ClipboardRecentContentIOS::GetRecentTextFromClipboard() {
NSString* text_from_pasteboard = [implementation_ recentTextFromClipboard];
if (!text_from_pasteboard) {
return std::nullopt;
}
return base::SysNSStringToUTF16(text_from_pasteboard);
}
bool ClipboardRecentContentIOS::HasRecentImageFromClipboard() {
return GetRecentImageFromClipboardInternal().has_value();
}
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_ 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(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_ recentTextFromClipboardAsync:^(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_ recentImageFromClipboardAsync:^(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() {}
base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
return base::Seconds(
static_cast<int64_t>([implementation_ clipboardContentAge]));
}
void ClipboardRecentContentIOS::SuppressClipboardContent() {
[implementation_ suppressClipboardContent];
}
void ClipboardRecentContentIOS::ClearClipboardContent() {
NOTIMPLEMENTED();
return;
}
std::optional<gfx::Image>
ClipboardRecentContentIOS::GetRecentImageFromClipboardInternal() {
UIImage* image_from_pasteboard = [implementation_ recentImageFromClipboard];
if (!image_from_pasteboard) {
return std::nullopt;
}
return gfx::Image(image_from_pasteboard);
}