Commit b22dddd5 authored by Robbie Gibson's avatar Robbie Gibson Committed by Commit Bot

Add async methods to ClipboardRecentContent for urls/text/images

Bug: 1098722
Change-Id: I70e40b221b228e120ca02e3d2ae0e47b936cb0b5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2315033
Commit-Queue: Robbie Gibson <rkgibson@google.com>
Reviewed-by: default avatarOlivier Robin <olivierrobin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#792632}
parent 0320db93
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
#include "url/gurl.h" #include "url/gurl.h"
enum class ClipboardContentType { URL, Text, Image };
// Helper class returning an URL if the content of the clipboard can be turned // Helper class returning an URL if the content of the clipboard can be turned
// into an URL, and if it estimates that the content of the clipboard is not too // into an URL, and if it estimates that the content of the clipboard is not too
// old. // old.
...@@ -48,6 +50,28 @@ class ClipboardRecentContent { ...@@ -48,6 +50,28 @@ class ClipboardRecentContent {
// Return if system's clipboard contains an image. // Return if system's clipboard contains an image.
virtual bool HasRecentImageFromClipboard() = 0; virtual bool HasRecentImageFromClipboard() = 0;
/*
On iOS, iOS 14 introduces new clipboard APIs that are async. The asynchronous
forms of clipboard access below should be preferred.
*/
using HasDataCallback =
base::OnceCallback<void(std::set<ClipboardContentType>)>;
using GetRecentURLCallback = base::OnceCallback<void(base::Optional<GURL>)>;
using GetRecentTextCallback =
base::OnceCallback<void(base::Optional<base::string16>)>;
// Returns whether the clipboard contains a URL to |HasDataCallback| if it
// is recent enough and has not been suppressed.
virtual void HasRecentContentFromClipboard(
std::set<ClipboardContentType> types,
HasDataCallback callback) = 0;
// Returns clipboard content as URL to |GetRecentURLCallback|, if it has a
// compatible type, is recent enough and has not been suppressed.
virtual void GetRecentURLFromClipboard(GetRecentURLCallback callback) = 0;
// Returns clipboard content as a string to |GetRecentTextCallback|, if it has
// a compatible type, is recent enough and has not been suppressed.
virtual void GetRecentTextFromClipboard(GetRecentTextCallback callback) = 0;
// Returns how old the content of the clipboard is. // Returns how old the content of the clipboard is.
virtual base::TimeDelta GetClipboardContentAge() const = 0; virtual base::TimeDelta GetClipboardContentAge() const = 0;
......
...@@ -118,6 +118,42 @@ bool ClipboardRecentContentGeneric::HasRecentImageFromClipboard() { ...@@ -118,6 +118,42 @@ bool ClipboardRecentContentGeneric::HasRecentImageFromClipboard() {
/* data_dst = */ nullptr); /* data_dst = */ nullptr);
} }
void ClipboardRecentContentGeneric::HasRecentContentFromClipboard(
std::set<ClipboardContentType> types,
HasDataCallback callback) {
std::set<ClipboardContentType> matching_types;
for (ClipboardContentType type : types) {
switch (type) {
case ClipboardContentType::URL:
if (GetRecentURLFromClipboard()) {
matching_types.insert(ClipboardContentType::URL);
}
break;
case ClipboardContentType::Text:
if (GetRecentTextFromClipboard()) {
matching_types.insert(ClipboardContentType::Text);
}
break;
case ClipboardContentType::Image:
if (HasRecentImageFromClipboard()) {
matching_types.insert(ClipboardContentType::Image);
}
break;
}
}
std::move(callback).Run(matching_types);
}
void ClipboardRecentContentGeneric::GetRecentURLFromClipboard(
GetRecentURLCallback callback) {
std::move(callback).Run(GetRecentURLFromClipboard());
}
void ClipboardRecentContentGeneric::GetRecentTextFromClipboard(
GetRecentTextCallback callback) {
std::move(callback).Run(GetRecentTextFromClipboard());
}
base::TimeDelta ClipboardRecentContentGeneric::GetClipboardContentAge() const { base::TimeDelta ClipboardRecentContentGeneric::GetClipboardContentAge() const {
const base::Time last_modified_time = const base::Time last_modified_time =
ui::Clipboard::GetForCurrentThread()->GetLastModifiedTime(); ui::Clipboard::GetForCurrentThread()->GetLastModifiedTime();
......
...@@ -28,6 +28,10 @@ class ClipboardRecentContentGeneric : public ClipboardRecentContent { ...@@ -28,6 +28,10 @@ class ClipboardRecentContentGeneric : public ClipboardRecentContent {
base::Optional<base::string16> GetRecentTextFromClipboard() override; base::Optional<base::string16> GetRecentTextFromClipboard() override;
void GetRecentImageFromClipboard(GetRecentImageCallback callback) override; void GetRecentImageFromClipboard(GetRecentImageCallback callback) override;
bool HasRecentImageFromClipboard() override; bool HasRecentImageFromClipboard() override;
void HasRecentContentFromClipboard(std::set<ClipboardContentType> types,
HasDataCallback callback) override;
void GetRecentURLFromClipboard(GetRecentURLCallback callback) override;
void GetRecentTextFromClipboard(GetRecentTextCallback callback) override;
base::TimeDelta GetClipboardContentAge() const override; base::TimeDelta GetClipboardContentAge() const override;
void SuppressClipboardContent() override; void SuppressClipboardContent() override;
void ClearClipboardContent() override; void ClearClipboardContent() override;
......
...@@ -8,6 +8,12 @@ ...@@ -8,6 +8,12 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
typedef NSString* ContentType NS_TYPED_ENUM;
extern ContentType const ContentTypeURL;
extern ContentType const ContentTypeText;
extern ContentType const ContentTypeImage;
// A protocol implemented by delegates to handle clipboard changes. // A protocol implemented by delegates to handle clipboard changes.
@protocol ClipboardRecentContentDelegate<NSObject> @protocol ClipboardRecentContentDelegate<NSObject>
...@@ -44,6 +50,25 @@ ...@@ -44,6 +50,25 @@
// not been suppressed. Otherwise, returns nil. // not been suppressed. Otherwise, returns nil.
- (UIImage*)recentImageFromClipboard; - (UIImage*)recentImageFromClipboard;
// Uses the new iOS 14 pasteboard detection pattern API to asynchronously detect
// if the clipboard contains content (that has not been suppressed) of the
// requested types without actually getting the contents.
- (void)hasContentMatchingTypes:(NSSet<ContentType>*)types
completionHandler:
(void (^)(NSSet<ContentType>*))completionHandler;
// Uses the new iOS 14 pasteboard detection pattern API to asynchronously get a
// copied URL from the clipboard if it has not been suppressed. Passes nil to
// the callback otherwise.
- (void)recentURLFromClipboardAsync:(void (^)(NSURL*))callback;
// Uses the new iOS 14 pasteboard detection pattern API to asynchronously get a
// copied string from the clipboard if it has not been suppressed. Passes nil to
// the callback otherwise.
- (void)recentTextFromClipboardAsync:(void (^)(NSString*))callback;
// Asynchronously gets an image from the clipboard if is has not been
// suppressed. Passes nil to the callback otherwise. This does not actually use
// any iOS 14 APIs and could be done synchronously, but is here for consistency.
- (void)recentImageFromClipboardAsync:(void (^)(UIImage*))callback;
// Returns how old the content of the clipboard is. // Returns how old the content of the clipboard is.
- (NSTimeInterval)clipboardContentAge; - (NSTimeInterval)clipboardContentAge;
......
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
ContentType const ContentTypeURL = @"ContentTypeURL";
ContentType const ContentTypeText = @"ContentTypeString";
ContentType const ContentTypeImage = @"ContentTypeImage";
namespace { namespace {
// Key used to store the pasteboard's current change count. If when resuming // Key used to store the pasteboard's current change count. If when resuming
// chrome the pasteboard's change count is different from the stored one, then // chrome the pasteboard's change count is different from the stored one, then
...@@ -158,6 +162,220 @@ NSString* const kPasteboardChangeDateKey = @"PasteboardChangeDate"; ...@@ -158,6 +162,220 @@ NSString* const kPasteboardChangeDateKey = @"PasteboardChangeDate";
return self.cachedImage; return self.cachedImage;
} }
- (void)hasContentMatchingTypes:(NSSet<ContentType>*)types
completionHandler:
(void (^)(NSSet<ContentType>*))completionHandler {
[self updateIfNeeded];
if (![self shouldReturnValueOfClipboard]) {
completionHandler([NSSet set]);
return;
}
__block NSMutableDictionary<ContentType, NSNumber*>* results =
[[NSMutableDictionary alloc] init];
void (^checkResults)() = ^{
NSMutableSet<ContentType>* matchingTypes = [NSMutableSet set];
if ([results count] != [types count]) {
return;
}
for (ContentType type in results) {
if ([results[type] boolValue]) {
[matchingTypes addObject:type];
}
}
completionHandler(matchingTypes);
};
for (ContentType type in types) {
if ([type isEqualToString:ContentTypeURL]) {
[self hasRecentURLFromClipboardInternal:^(BOOL hasURL) {
results[ContentTypeURL] = [NSNumber numberWithBool:hasURL];
checkResults();
}];
} else if ([type isEqualToString:ContentTypeText]) {
[self hasRecentTextFromClipboardInternal:^(BOOL hasText) {
results[ContentTypeText] = [NSNumber numberWithBool:hasText];
checkResults();
}];
} else if ([type isEqualToString:ContentTypeImage]) {
[self hasRecentImageFromClipboardInternal:^(BOOL hasImage) {
results[ContentTypeImage] = [NSNumber numberWithBool:hasImage];
checkResults();
}];
}
}
}
- (void)hasRecentURLFromClipboardInternal:(void (^)(BOOL))callback {
DCHECK(callback);
if (@available(iOS 14, *)) {
// Use cached value if it exists
if (self.cachedURL) {
callback(YES);
return;
}
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
NSSet<UIPasteboardDetectionPattern>* urlPattern =
[NSSet setWithObject:UIPasteboardDetectionPatternProbableWebURL];
[UIPasteboard.generalPasteboard
detectPatternsForPatterns:urlPattern
completionHandler:^(
NSSet<UIPasteboardDetectionPattern>* patterns,
NSError* error) {
callback([patterns
containsObject:
UIPasteboardDetectionPatternProbableWebURL]);
}];
#else
// To prevent clipboard notification from appearing on iOS 14 with iOS 13
// SDK, use the -hasURLs property to check for URL existence. This will
// cause crbug.com/1033935 to reappear in code using this method (also see
// the comments in -URLFromPasteboard in this file), but that is preferable
// to the notificatio appearing when it shouldn't.
callback(UIPasteboard.generalPasteboard.hasURLs);
#endif
} else {
callback([self recentURLFromClipboard] != nil);
}
}
- (void)hasRecentTextFromClipboardInternal:(void (^)(BOOL))callback {
DCHECK(callback);
if (@available(iOS 14, *)) {
// Use cached value if it exists
if (self.cachedText) {
callback(YES);
return;
}
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
NSSet<UIPasteboardDetectionPattern>* textPattern =
[NSSet setWithObject:UIPasteboardDetectionPatternProbableWebSearch];
[UIPasteboard.generalPasteboard
detectPatternsForPatterns:textPattern
completionHandler:^(
NSSet<UIPasteboardDetectionPattern>* patterns,
NSError* error) {
callback([patterns
containsObject:
UIPasteboardDetectionPatternProbableWebSearch]);
}];
#else
callback(UIPasteboard.generalPasteboard.hasStrings);
#endif
} else {
callback([self recentTextFromClipboard] != nil);
}
}
- (void)hasRecentImageFromClipboardInternal:(void (^)(BOOL))callback {
DCHECK(callback);
if (@available(iOS 14, *)) {
// Use cached value if it exists
if (self.cachedImage) {
callback(YES);
return;
}
callback(UIPasteboard.generalPasteboard.hasImages);
} else {
callback([self recentImageFromClipboard] != nil);
}
}
- (void)recentURLFromClipboardAsync:(void (^)(NSURL*))callback {
DCHECK(callback);
if (@available(iOS 14, *)) {
[self updateIfNeeded];
if (![self shouldReturnValueOfClipboard]) {
callback(nil);
return;
}
// Use cached value if it exists.
if (self.cachedURL) {
callback(self.cachedURL);
return;
}
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
__weak __typeof(self) weakSelf = self;
NSSet<UIPasteboardDetectionPattern>* urlPattern =
[NSSet setWithObject:UIPasteboardDetectionPatternProbableWebURL];
[UIPasteboard.generalPasteboard
detectValuesForPatterns:urlPattern
completionHandler:^(
NSDictionary<UIPasteboardDetectionPattern, id>* values,
NSError* error) {
NSURL* url = [NSURL
URLWithString:
values[UIPasteboardDetectionPatternProbableWebURL]];
weakSelf.cachedURL = url;
callback(url);
}];
#else
callback([self recentURLFromClipboard]);
#endif
} else {
callback([self recentURLFromClipboard]);
}
}
- (void)recentTextFromClipboardAsync:(void (^)(NSString*))callback {
DCHECK(callback);
if (@available(iOS 14, *)) {
[self updateIfNeeded];
if (![self shouldReturnValueOfClipboard]) {
callback(nil);
return;
}
// Use cached value if it exists.
if (self.cachedText) {
callback(self.cachedText);
return;
}
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
__weak __typeof(self) weakSelf = self;
NSSet<UIPasteboardDetectionPattern>* textPattern =
[NSSet setWithObject:UIPasteboardDetectionPatternProbableWebSearch];
[UIPasteboard.generalPasteboard
detectValuesForPatterns:textPattern
completionHandler:^(
NSDictionary<UIPasteboardDetectionPattern, id>* values,
NSError* error) {
NSString* text =
values[UIPasteboardDetectionPatternProbableWebSearch];
weakSelf.cachedText = text;
callback(text);
}];
#else
callback([self recentTextFromClipboard]);
#endif
} else {
callback([self recentTextFromClipboard]);
}
}
- (void)recentImageFromClipboardAsync:(void (^)(UIImage*))callback {
DCHECK(callback);
[self updateIfNeeded];
if (![self shouldReturnValueOfClipboard]) {
callback(nil);
return;
}
if (!self.cachedImage) {
self.cachedImage = UIPasteboard.generalPasteboard.image;
}
callback(self.cachedImage);
}
- (NSTimeInterval)clipboardContentAge { - (NSTimeInterval)clipboardContentAge {
return -[self.lastPasteboardChangeDate timeIntervalSinceNow]; return -[self.lastPasteboardChangeDate timeIntervalSinceNow];
} }
......
...@@ -44,6 +44,10 @@ class ClipboardRecentContentIOS : public ClipboardRecentContent { ...@@ -44,6 +44,10 @@ class ClipboardRecentContentIOS : public ClipboardRecentContent {
base::Optional<base::string16> GetRecentTextFromClipboard() override; base::Optional<base::string16> GetRecentTextFromClipboard() override;
void GetRecentImageFromClipboard(GetRecentImageCallback callback) override; void GetRecentImageFromClipboard(GetRecentImageCallback callback) override;
bool HasRecentImageFromClipboard() override; bool HasRecentImageFromClipboard() override;
void HasRecentContentFromClipboard(std::set<ClipboardContentType> types,
HasDataCallback callback) override;
void GetRecentURLFromClipboard(GetRecentURLCallback callback) override;
void GetRecentTextFromClipboard(GetRecentTextCallback callback) override;
base::TimeDelta GetClipboardContentAge() const override; base::TimeDelta GetClipboardContentAge() const override;
void SuppressClipboardContent() override; void SuppressClipboardContent() override;
void ClearClipboardContent() override; void ClearClipboardContent() override;
......
...@@ -44,6 +44,29 @@ NSSet<NSString*>* getAuthorizedSchemeList( ...@@ -44,6 +44,29 @@ NSSet<NSString*>* getAuthorizedSchemeList(
return [schemes copy]; 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 } // namespace
@interface ClipboardRecentContentDelegateImpl @interface ClipboardRecentContentDelegateImpl
...@@ -95,13 +118,65 @@ ClipboardRecentContentIOS::GetRecentTextFromClipboard() { ...@@ -95,13 +118,65 @@ ClipboardRecentContentIOS::GetRecentTextFromClipboard() {
void ClipboardRecentContentIOS::GetRecentImageFromClipboard( void ClipboardRecentContentIOS::GetRecentImageFromClipboard(
GetRecentImageCallback callback) { GetRecentImageCallback callback) {
std::move(callback).Run(GetRecentImageFromClipboardInternal()); __block GetRecentImageCallback callback_for_block = std::move(callback);
[implementation_ recentImageFromClipboardAsync:^(UIImage* image) {
if (!image) {
std::move(callback_for_block).Run(base::nullopt);
return;
}
std::move(callback_for_block).Run(gfx::Image(image));
}];
} }
bool ClipboardRecentContentIOS::HasRecentImageFromClipboard() { bool ClipboardRecentContentIOS::HasRecentImageFromClipboard() {
return GetRecentImageFromClipboardInternal().has_value(); 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)];
}
[implementation_ hasContentMatchingTypes:ios_types
completionHandler:^(NSSet<ContentType>* results) {
std::set<ClipboardContentType> matching_types;
for (ContentType type in results) {
matching_types.insert(
ClipboardContentTypeFromContentType(type));
}
std::move(callback_for_block).Run(matching_types);
}];
}
void ClipboardRecentContentIOS::GetRecentURLFromClipboard(
GetRecentURLCallback callback) {
__block GetRecentURLCallback callback_for_block = std::move(callback);
[implementation_ recentURLFromClipboardAsync:^(NSURL* url) {
GURL converted_url = net::GURLWithNSURL(url);
if (!converted_url.is_valid()) {
std::move(callback_for_block).Run(base::nullopt);
return;
}
std::move(callback_for_block).Run(converted_url);
}];
}
void ClipboardRecentContentIOS::GetRecentTextFromClipboard(
GetRecentTextCallback callback) {
__block GetRecentTextCallback callback_for_block = std::move(callback);
[implementation_ recentTextFromClipboardAsync:^(NSString* text) {
if (!text) {
std::move(callback_for_block).Run(base::nullopt);
return;
}
std::move(callback_for_block).Run(base::SysNSStringToUTF16(text));
}];
}
ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {} ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {}
base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const { base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
......
...@@ -40,6 +40,42 @@ bool FakeClipboardRecentContent::HasRecentImageFromClipboard() { ...@@ -40,6 +40,42 @@ bool FakeClipboardRecentContent::HasRecentImageFromClipboard() {
return clipboard_image_content_.has_value(); return clipboard_image_content_.has_value();
} }
void FakeClipboardRecentContent::HasRecentContentFromClipboard(
std::set<ClipboardContentType> types,
HasDataCallback callback) {
std::set<ClipboardContentType> matching_types;
for (ClipboardContentType type : types) {
switch (type) {
case ClipboardContentType::URL:
if (GetRecentURLFromClipboard()) {
matching_types.insert(ClipboardContentType::URL);
}
break;
case ClipboardContentType::Text:
if (GetRecentTextFromClipboard()) {
matching_types.insert(ClipboardContentType::Text);
}
break;
case ClipboardContentType::Image:
if (HasRecentImageFromClipboard()) {
matching_types.insert(ClipboardContentType::Image);
}
break;
}
}
std::move(callback).Run(matching_types);
}
void FakeClipboardRecentContent::GetRecentURLFromClipboard(
GetRecentURLCallback callback) {
std::move(callback).Run(GetRecentURLFromClipboard());
}
void FakeClipboardRecentContent::GetRecentTextFromClipboard(
GetRecentTextCallback callback) {
std::move(callback).Run(GetRecentTextFromClipboard());
}
base::TimeDelta FakeClipboardRecentContent::GetClipboardContentAge() const { base::TimeDelta FakeClipboardRecentContent::GetClipboardContentAge() const {
return content_age_; return content_age_;
} }
......
...@@ -23,6 +23,10 @@ class FakeClipboardRecentContent : public ClipboardRecentContent { ...@@ -23,6 +23,10 @@ class FakeClipboardRecentContent : public ClipboardRecentContent {
base::Optional<base::string16> GetRecentTextFromClipboard() override; base::Optional<base::string16> GetRecentTextFromClipboard() override;
void GetRecentImageFromClipboard(GetRecentImageCallback callback) override; void GetRecentImageFromClipboard(GetRecentImageCallback callback) override;
bool HasRecentImageFromClipboard() override; bool HasRecentImageFromClipboard() override;
void HasRecentContentFromClipboard(std::set<ClipboardContentType> types,
HasDataCallback callback) override;
void GetRecentURLFromClipboard(GetRecentURLCallback callback) override;
void GetRecentTextFromClipboard(GetRecentTextCallback callback) override;
base::TimeDelta GetClipboardContentAge() const override; base::TimeDelta GetClipboardContentAge() const override;
void SuppressClipboardContent() override; void SuppressClipboardContent() override;
void ClearClipboardContent() override; void ClearClipboardContent() override;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment