summaryrefslogtreecommitdiff
path: root/chromium/components/open_from_clipboard
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/open_from_clipboard')
-rw-r--r--chromium/components/open_from_clipboard/BUILD.gn10
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content.cc14
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content.h24
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_features.cc11
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_features.h15
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_generic.cc36
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_generic.h4
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_generic_unittest.cc4
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.h25
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm389
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_ios.h4
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_ios.mm77
-rw-r--r--chromium/components/open_from_clipboard/clipboard_recent_content_ios_unittest.mm21
-rw-r--r--chromium/components/open_from_clipboard/fake_clipboard_recent_content.cc36
-rw-r--r--chromium/components/open_from_clipboard/fake_clipboard_recent_content.h4
15 files changed, 503 insertions, 171 deletions
diff --git a/chromium/components/open_from_clipboard/BUILD.gn b/chromium/components/open_from_clipboard/BUILD.gn
index 411035c2dfd..672fa62148b 100644
--- a/chromium/components/open_from_clipboard/BUILD.gn
+++ b/chromium/components/open_from_clipboard/BUILD.gn
@@ -15,7 +15,6 @@ static_library("open_from_clipboard") {
]
deps = [
- ":feature_flags",
":open_from_clipboard_impl",
"//base",
"//components/variations",
@@ -39,14 +38,6 @@ static_library("open_from_clipboard") {
}
}
-source_set("feature_flags") {
- sources = [
- "clipboard_recent_content_features.cc",
- "clipboard_recent_content_features.h",
- ]
- deps = [ "//base" ]
-}
-
# Helper classes used by "open_from_clipboard" target. These classes must have
# no dependencies on "//base:i18n".
source_set("open_from_clipboard_impl") {
@@ -94,7 +85,6 @@ source_set("unit_tests") {
if (!is_ios) {
sources += [ "clipboard_recent_content_generic_unittest.cc" ]
deps += [
- ":feature_flags",
"//base/test:test_support",
"//ui/base/clipboard:clipboard_test_support",
]
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content.cc b/chromium/components/open_from_clipboard/clipboard_recent_content.cc
index 5b2cb684719..2e59ce3afaa 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content.cc
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content.cc
@@ -7,7 +7,6 @@
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
-#include "components/open_from_clipboard/clipboard_recent_content_features.h"
#include "components/variations/variations_associated_data.h"
#include "url/url_constants.h"
@@ -35,16 +34,5 @@ void ClipboardRecentContent::SetInstance(
// static
base::TimeDelta ClipboardRecentContent::MaximumAgeOfClipboard() {
- // Identify the current setting for this parameter from the feature, using
- // 3600 seconds (1 hour) as a default if the parameter is not set.
- // On iOS, the default is 600 seconds (10 minutes).
- // TODO(gangwu) : Remove this feature flag after full launched in Android.
-#if defined(OS_IOS) || defined(OS_ANDROID)
- int default_maximum_age = 600;
-#else
- int default_maximum_age = 3600;
-#endif
- int value = variations::GetVariationParamByFeatureAsInt(
- kClipboardMaximumAge, kClipboardMaximumAgeParam, default_maximum_age);
- return base::TimeDelta::FromSeconds(value);
+ return base::TimeDelta::FromMinutes(10);
}
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content.h b/chromium/components/open_from_clipboard/clipboard_recent_content.h
index 5d6ed3c7b7a..6b383bda538 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content.h
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content.h
@@ -14,6 +14,8 @@
#include "ui/gfx/image/image.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
// into an URL, and if it estimates that the content of the clipboard is not too
// old.
@@ -48,6 +50,28 @@ class ClipboardRecentContent {
// Return if system's clipboard contains an image.
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.
virtual base::TimeDelta GetClipboardContentAge() const = 0;
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_features.cc b/chromium/components/open_from_clipboard/clipboard_recent_content_features.cc
deleted file mode 100644
index 5082125308a..00000000000
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_features.cc
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2019 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.
-
-#include "components/open_from_clipboard/clipboard_recent_content_features.h"
-
-const char kClipboardMaximumAgeParam[] = "UIClipboardMaximumAge";
-
-// Feature used to determine the maximum age of clipboard content.
-const base::Feature kClipboardMaximumAge{"ClipboardMaximumAge",
- base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_features.h b/chromium/components/open_from_clipboard/clipboard_recent_content_features.h
deleted file mode 100644
index 4b405155eee..00000000000
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_features.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2019 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.
-
-#ifndef COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_FEATURES_H_
-#define COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_FEATURES_H_
-
-#include "base/feature_list.h"
-
-// Parameter name for the ClipboardMaximumAge experiment.
-extern const char kClipboardMaximumAgeParam[];
-
-extern const base::Feature kClipboardMaximumAge;
-
-#endif // COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_FEATURES_H_
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_generic.cc b/chromium/components/open_from_clipboard/clipboard_recent_content_generic.cc
index 9d3f259696d..f3d94668bdf 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_generic.cc
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content_generic.cc
@@ -114,6 +114,42 @@ bool ClipboardRecentContentGeneric::HasRecentImageFromClipboard() {
ui::ClipboardBuffer::kCopyPaste);
}
+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 {
const base::Time last_modified_time =
ui::Clipboard::GetForCurrentThread()->GetLastModifiedTime();
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_generic.h b/chromium/components/open_from_clipboard/clipboard_recent_content_generic.h
index 218a411a093..2a4d2df24a9 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_generic.h
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content_generic.h
@@ -28,6 +28,10 @@ class ClipboardRecentContentGeneric : public ClipboardRecentContent {
base::Optional<base::string16> GetRecentTextFromClipboard() override;
void GetRecentImageFromClipboard(GetRecentImageCallback callback) 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;
void SuppressClipboardContent() override;
void ClearClipboardContent() override;
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_generic_unittest.cc b/chromium/components/open_from_clipboard/clipboard_recent_content_generic_unittest.cc
index bc9cffaa065..c7c81d47ce0 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_generic_unittest.cc
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content_generic_unittest.cc
@@ -13,7 +13,6 @@
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
-#include "components/open_from_clipboard/clipboard_recent_content_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/clipboard/test/test_clipboard.h"
#include "url/gurl.h"
@@ -77,9 +76,6 @@ TEST_F(ClipboardRecentContentGenericTest, RecognizesURLs) {
}
TEST_F(ClipboardRecentContentGenericTest, OlderURLsNotSuggested) {
- base::test::ScopedFeatureList scoped_feature_list;
- scoped_feature_list.InitAndEnableFeatureWithParameters(
- kClipboardMaximumAge, {{kClipboardMaximumAgeParam, "600"}});
ClipboardRecentContentGeneric recent_content;
base::Time now = base::Time::Now();
std::string text = "http://example.com/";
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.h b/chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.h
index 19bbf849be4..38f708b72d9 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.h
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.h
@@ -8,6 +8,12 @@
#import <Foundation/Foundation.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.
@protocol ClipboardRecentContentDelegate<NSObject>
@@ -44,6 +50,25 @@
// not been suppressed. Otherwise, returns nil.
- (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.
- (NSTimeInterval)clipboardContentAge;
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm b/chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm
index a568745bb02..20b1ce342d4 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm
@@ -4,7 +4,6 @@
#import "components/open_from_clipboard/clipboard_recent_content_impl_ios.h"
-#import <CommonCrypto/CommonDigest.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <UIKit/UIKit.h>
@@ -16,6 +15,10 @@
#error "This file requires ARC support."
#endif
+ContentType const ContentTypeURL = @"ContentTypeURL";
+ContentType const ContentTypeText = @"ContentTypeString";
+ContentType const ContentTypeImage = @"ContentTypeImage";
+
namespace {
// 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
@@ -24,45 +27,6 @@ NSString* const kPasteboardChangeCountKey = @"PasteboardChangeCount";
// Key used to store the last date at which it was detected that the pasteboard
// changed. It is used to evaluate the age of the pasteboard's content.
NSString* const kPasteboardChangeDateKey = @"PasteboardChangeDate";
-// Key used to store the hash of the content of the pasteboard. Whenever the
-// hash changed, the pasteboard content is considered to have changed.
-NSString* const kPasteboardEntryMD5Key = @"PasteboardEntryMD5";
-
-// Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|,
-// |image_data|, and |url|. This value is used to detect pasteboard content
-// change. Keeping only 4 bytes is a privacy requirement to introduce collision
-// and allow deniability of having copied a given string, image, or url.
-//
-// |image_data| is passed in as NSData instead of UIImage because converting
-// UIImage to NSData can be slow for large images and getting NSData directly
-// from the pasteboard is quicker.
-NSData* WeakMD5FromPasteboardData(NSString* string,
- NSData* image_data,
- NSURL* url) {
- CC_MD5_CTX ctx;
- CC_MD5_Init(&ctx);
-
- const std::string clipboard_string = base::SysNSStringToUTF8(string);
- const char* c_string = clipboard_string.c_str();
- CC_MD5_Update(&ctx, c_string, strlen(c_string));
-
- // This hash is used only to tell if the image has changed, so
- // limit the number of bytes to hash to prevent slowdown.
- NSUInteger bytes_to_hash = fmin([image_data length], 1000000);
- if (bytes_to_hash > 0) {
- CC_MD5_Update(&ctx, [image_data bytes], bytes_to_hash);
- }
-
- const std::string url_string = base::SysNSStringToUTF8([url absoluteString]);
- const char* url_c_string = url_string.c_str();
- CC_MD5_Update(&ctx, url_c_string, strlen(url_c_string));
-
- unsigned char hash[CC_MD5_DIGEST_LENGTH];
- CC_MD5_Final(hash, &ctx);
-
- NSData* data = [NSData dataWithBytes:hash length:4];
- return data;
-}
} // namespace
@@ -73,8 +37,6 @@ NSData* WeakMD5FromPasteboardData(NSString* string,
@property(nonatomic, strong) NSUserDefaults* sharedUserDefaults;
// The pasteboard's change count. Increases everytime the pasteboard changes.
@property(nonatomic) NSInteger lastPasteboardChangeCount;
-// MD5 hash of the last registered pasteboard entry.
-@property(nonatomic, strong) NSData* lastPasteboardEntryMD5;
// Contains the authorized schemes for URLs.
@property(nonatomic, readonly) NSSet* authorizedSchemes;
// Delegate for metrics.
@@ -82,8 +44,18 @@ NSData* WeakMD5FromPasteboardData(NSString* string,
// Maximum age of clipboard in seconds.
@property(nonatomic, readonly) NSTimeInterval maximumAgeOfClipboard;
-// If the content of the pasteboard has changed, updates the change count,
-// change date, and md5 of the latest pasteboard entry if necessary.
+// A cached version of an already-retrieved URL. This prevents subsequent URL
+// requests from triggering the iOS 14 pasteboard notification.
+@property(nonatomic, strong) NSURL* cachedURL;
+// A cached version of an already-retrieved string. This prevents subsequent
+// string requests from triggering the iOS 14 pasteboard notification.
+@property(nonatomic, copy) NSString* cachedText;
+// A cached version of an already-retrieved image. This prevents subsequent
+// image requests from triggering the iOS 14 pasteboard notification.
+@property(nonatomic, strong) UIImage* cachedImage;
+
+// If the content of the pasteboard has changed, updates the change count
+// and change date.
- (void)updateIfNeeded;
// Returns whether the pasteboard changed since the last time a pasteboard
@@ -99,13 +71,15 @@ NSData* WeakMD5FromPasteboardData(NSString* string,
// Returns the uptime.
- (NSTimeInterval)uptime;
+// Returns whether the value of the clipboard should be returned.
+- (BOOL)shouldReturnValueOfClipboard;
+
@end
@implementation ClipboardRecentContentImplIOS
@synthesize lastPasteboardChangeCount = _lastPasteboardChangeCount;
@synthesize lastPasteboardChangeDate = _lastPasteboardChangeDate;
-@synthesize lastPasteboardEntryMD5 = _lastPasteboardEntryMD5;
@synthesize sharedUserDefaults = _sharedUserDefaults;
@synthesize authorizedSchemes = _authorizedSchemes;
@synthesize delegate = _delegate;
@@ -146,93 +120,282 @@ NSData* WeakMD5FromPasteboardData(NSString* string,
[self updateIfNeeded];
}
-- (NSData*)getCurrentMD5 {
- NSString* pasteboardString = [UIPasteboard generalPasteboard].string;
- NSData* pasteboardImageData = [[UIPasteboard generalPasteboard]
- dataForPasteboardType:(NSString*)kUTTypeImage];
- NSURL* pasteboardURL = [UIPasteboard generalPasteboard].URL;
- NSData* md5 = WeakMD5FromPasteboardData(pasteboardString, pasteboardImageData,
- pasteboardURL);
+- (BOOL)hasPasteboardChanged {
+ return UIPasteboard.generalPasteboard.changeCount !=
+ self.lastPasteboardChangeCount;
+}
+
+- (NSURL*)recentURLFromClipboard {
+ [self updateIfNeeded];
+
+ if (![self shouldReturnValueOfClipboard])
+ return nil;
- return md5;
+ if (!self.cachedURL) {
+ self.cachedURL = [self URLFromPasteboard];
+ }
+ return self.cachedURL;
}
-- (BOOL)hasPasteboardChanged {
- // If |MD5Changed|, we know for sure there has been at least one pasteboard
- // copy since last time it was checked.
- // If the pasteboard content is still the same but the device was not
- // rebooted, the change count can be checked to see if it changed.
- // Note: due to a mismatch between the actual behavior and documentation, and
- // lack of consistency on different reboot scenarios, the change count cannot
- // be checked after a reboot.
- // See radar://21833556 for more information.
- BOOL deviceRebooted = [self clipboardContentAge] >= [self uptime];
-
- // On iOS 13, there is a bug where every time a UITextField is opened, the
- // changeCount increases by 2. Thus, if the difference in counts is even,
- // it is unknown whether there is a real change or the user just focused some
- // UITextFields.
- // See radar://7619972 or crbug.com/1058487 for more information.
- NSInteger changeCount = [UIPasteboard generalPasteboard].changeCount;
- BOOL changeCountChanged = changeCount != self.lastPasteboardChangeCount;
- if (@available(iOS 13, *)) {
- // No-op. This should be if !available(13), but that is not supported.
- } else {
- if (!deviceRebooted) {
- return changeCountChanged;
+- (NSString*)recentTextFromClipboard {
+ [self updateIfNeeded];
+
+ if (![self shouldReturnValueOfClipboard])
+ return nil;
+
+ if (!self.cachedText) {
+ self.cachedText = UIPasteboard.generalPasteboard.string;
+ }
+ return self.cachedText;
+}
+
+- (UIImage*)recentImageFromClipboard {
+ [self updateIfNeeded];
+
+ if (![self shouldReturnValueOfClipboard])
+ return nil;
+
+ if (!self.cachedImage) {
+ self.cachedImage = UIPasteboard.generalPasteboard.image;
+ }
+
+ 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();
+ }];
}
}
+}
- // If there was no reboot, and the number hasn't changed, the pasteboard
- // definitely hasn't changed.
- if (!deviceRebooted && !changeCountChanged) {
- return NO;
+- (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 there was no reboot and the size of the change is odd , the pasteboard
- // definitely has changed.
- BOOL changeCountIncreasedIsOdd =
- (changeCount - self.lastPasteboardChangeCount) % 2 != 0;
- if (!deviceRebooted && changeCountIncreasedIsOdd) {
- return YES;
+#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;
+ }
- // Otherwise, it is unknown whether or not there was a real change, so
- // fallback to looking at the MD5.
- self.lastPasteboardChangeCount = changeCount;
- BOOL md5Changed =
- ![[self getCurrentMD5] isEqualToData:self.lastPasteboardEntryMD5];
- return md5Changed;
+ callback(UIPasteboard.generalPasteboard.hasImages);
+ } else {
+ callback([self recentImageFromClipboard] != nil);
+ }
}
-- (NSURL*)recentURLFromClipboard {
- [self updateIfNeeded];
- if ([self clipboardContentAge] > self.maximumAgeOfClipboard) {
- return 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]);
}
- return [self URLFromPasteboard];
}
-- (NSString*)recentTextFromClipboard {
- [self updateIfNeeded];
- if ([self clipboardContentAge] > self.maximumAgeOfClipboard) {
- return nil;
+- (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]);
}
- return [UIPasteboard generalPasteboard].string;
}
-- (UIImage*)recentImageFromClipboard {
+- (void)recentImageFromClipboardAsync:(void (^)(UIImage*))callback {
+ DCHECK(callback);
[self updateIfNeeded];
- if ([self clipboardContentAge] > self.maximumAgeOfClipboard) {
- return nil;
+ if (![self shouldReturnValueOfClipboard]) {
+ callback(nil);
+ return;
+ }
+
+ if (!self.cachedImage) {
+ self.cachedImage = UIPasteboard.generalPasteboard.image;
}
- return [UIPasteboard generalPasteboard].image;
+ callback(self.cachedImage);
}
- (NSTimeInterval)clipboardContentAge {
return -[self.lastPasteboardChangeDate timeIntervalSinceNow];
}
+- (BOOL)shouldReturnValueOfClipboard {
+ if ([self clipboardContentAge] > self.maximumAgeOfClipboard)
+ return NO;
+
+ // It is the common convention on iOS that password managers tag confidential
+ // data with the flavor "org.nspasteboard.ConcealedType". Obey this
+ // convention; the user doesn't want for their confidential data to be
+ // suggested as a search, anyway. See http://nspasteboard.org/ for more info.
+ NSArray<NSString*>* types =
+ [[UIPasteboard generalPasteboard] pasteboardTypes];
+ if ([types containsObject:@"org.nspasteboard.ConcealedType"])
+ return NO;
+
+ return YES;
+}
+
- (void)suppressClipboardContent {
// User cleared the user data. The pasteboard entry must be removed from the
// omnibox list. Force entry expiration by setting copy date to 1970.
@@ -246,11 +409,15 @@ NSData* WeakMD5FromPasteboardData(NSString* string,
return;
}
- [self.delegate onClipboardChanged];
-
self.lastPasteboardChangeDate = [NSDate date];
self.lastPasteboardChangeCount = [UIPasteboard generalPasteboard].changeCount;
- self.lastPasteboardEntryMD5 = [self getCurrentMD5];
+
+ // Clear the cache because the pasteboard data has changed.
+ self.cachedURL = nil;
+ self.cachedText = nil;
+ self.cachedImage = nil;
+
+ [self.delegate onClipboardChanged];
[self saveToUserDefaults];
}
@@ -276,8 +443,6 @@ NSData* WeakMD5FromPasteboardData(NSString* string,
[self.sharedUserDefaults integerForKey:kPasteboardChangeCountKey];
self.lastPasteboardChangeDate = base::mac::ObjCCastStrict<NSDate>(
[self.sharedUserDefaults objectForKey:kPasteboardChangeDateKey]);
- self.lastPasteboardEntryMD5 = base::mac::ObjCCastStrict<NSData>(
- [self.sharedUserDefaults objectForKey:kPasteboardEntryMD5Key]);
}
- (void)saveToUserDefaults {
@@ -285,8 +450,6 @@ NSData* WeakMD5FromPasteboardData(NSString* string,
forKey:kPasteboardChangeCountKey];
[self.sharedUserDefaults setObject:self.lastPasteboardChangeDate
forKey:kPasteboardChangeDateKey];
- [self.sharedUserDefaults setObject:self.lastPasteboardEntryMD5
- forKey:kPasteboardEntryMD5Key];
}
- (NSTimeInterval)uptime {
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_ios.h b/chromium/components/open_from_clipboard/clipboard_recent_content_ios.h
index 63b3c407160..e264db8721f 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_ios.h
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content_ios.h
@@ -44,6 +44,10 @@ class ClipboardRecentContentIOS : public ClipboardRecentContent {
base::Optional<base::string16> GetRecentTextFromClipboard() override;
void GetRecentImageFromClipboard(GetRecentImageCallback callback) 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;
void SuppressClipboardContent() override;
void ClearClipboardContent() override;
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_ios.mm b/chromium/components/open_from_clipboard/clipboard_recent_content_ios.mm
index 5287c1c193e..3bec1c01602 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_ios.mm
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content_ios.mm
@@ -44,6 +44,29 @@ NSSet<NSString*>* getAuthorizedSchemeList(
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
@@ -95,13 +118,65 @@ ClipboardRecentContentIOS::GetRecentTextFromClipboard() {
void ClipboardRecentContentIOS::GetRecentImageFromClipboard(
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() {
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() {}
base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
diff --git a/chromium/components/open_from_clipboard/clipboard_recent_content_ios_unittest.mm b/chromium/components/open_from_clipboard/clipboard_recent_content_ios_unittest.mm
index 349ba4b1d7f..d56ba5b99e0 100644
--- a/chromium/components/open_from_clipboard/clipboard_recent_content_ios_unittest.mm
+++ b/chromium/components/open_from_clipboard/clipboard_recent_content_ios_unittest.mm
@@ -217,9 +217,22 @@ TEST_F(ClipboardRecentContentIOSTest, PasteboardURLObsolescence) {
VerifyClipboardTextDoesNotExist();
}
+// Checks that if the pasteboard is marked as having confidential data, it is
+// not returned.
+TEST_F(ClipboardRecentContentIOSTest, ConfidentialPasteboardText) {
+ [[UIPasteboard generalPasteboard]
+ setItems:@[ @{
+ @"public.plain-text" : @"hunter2",
+ @"org.nspasteboard.ConcealedType" : @"hunter2"
+ } ]
+ options:@{}];
+
+ VerifyClipboardTextDoesNotExist();
+}
+
// Checks that if the user suppresses content, no text will be returned,
// and if the text changes, the new text will be returned again.
-TEST_F(ClipboardRecentContentIOSTest, SupressedPasteboardText) {
+TEST_F(ClipboardRecentContentIOSTest, SuppressedPasteboardText) {
SetPasteboardContent(kRecognizedURL);
// Test that recent pasteboard data is provided.
@@ -248,7 +261,7 @@ TEST_F(ClipboardRecentContentIOSTest, SupressedPasteboardText) {
VerifyClipboardTextDoesNotExist();
// Check that if the pasteboard changes, the new content is not
- // supressed anymore.
+ // suppressed anymore.
SetPasteboardContent(kRecognizedURL2);
VerifyClipboardURLExists(kRecognizedURL2);
VerifyClipboardTextExists(kRecognizedURL2);
@@ -256,7 +269,7 @@ TEST_F(ClipboardRecentContentIOSTest, SupressedPasteboardText) {
// Checks that if the user suppresses content, no image will be returned,
// and if the image changes, the new image will be returned again.
-TEST_F(ClipboardRecentContentIOSTest, SupressedPasteboardImage) {
+TEST_F(ClipboardRecentContentIOSTest, SuppressedPasteboardImage) {
SetPasteboardImage(TestUIImage());
// Test that recent pasteboard data is provided.
@@ -281,7 +294,7 @@ TEST_F(ClipboardRecentContentIOSTest, SupressedPasteboardImage) {
VerifyIfClipboardImageExists(false);
// Check that if the pasteboard changes, the new content is not
- // supressed anymore.
+ // suppressed anymore.
SetPasteboardImage(TestUIImage([UIColor greenColor]));
VerifyIfClipboardImageExists(true);
}
diff --git a/chromium/components/open_from_clipboard/fake_clipboard_recent_content.cc b/chromium/components/open_from_clipboard/fake_clipboard_recent_content.cc
index 2ee12524db1..6fcc12cdadc 100644
--- a/chromium/components/open_from_clipboard/fake_clipboard_recent_content.cc
+++ b/chromium/components/open_from_clipboard/fake_clipboard_recent_content.cc
@@ -40,6 +40,42 @@ bool FakeClipboardRecentContent::HasRecentImageFromClipboard() {
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 {
return content_age_;
}
diff --git a/chromium/components/open_from_clipboard/fake_clipboard_recent_content.h b/chromium/components/open_from_clipboard/fake_clipboard_recent_content.h
index 8906925bc7b..b2d36ead079 100644
--- a/chromium/components/open_from_clipboard/fake_clipboard_recent_content.h
+++ b/chromium/components/open_from_clipboard/fake_clipboard_recent_content.h
@@ -23,6 +23,10 @@ class FakeClipboardRecentContent : public ClipboardRecentContent {
base::Optional<base::string16> GetRecentTextFromClipboard() override;
void GetRecentImageFromClipboard(GetRecentImageCallback callback) 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;
void SuppressClipboardContent() override;
void ClearClipboardContent() override;