| Index: components/open_from_clipboard/clipboard_recent_content_ios.mm
|
| diff --git a/components/open_from_clipboard/clipboard_recent_content_ios.mm b/components/open_from_clipboard/clipboard_recent_content_ios.mm
|
| index d27d2566eb2d3f4b2f893cdbe50c517cd46fbaef..7665e46731dd30992369991cabd68b0035c3ae39 100644
|
| --- a/components/open_from_clipboard/clipboard_recent_content_ios.mm
|
| +++ b/components/open_from_clipboard/clipboard_recent_content_ios.mm
|
| @@ -4,6 +4,7 @@
|
|
|
| #import "components/open_from_clipboard/clipboard_recent_content_ios.h"
|
|
|
| +#import <CommonCrypto/CommonDigest.h>
|
| #import <UIKit/UIKit.h>
|
|
|
| #import "base/ios/ios_util.h"
|
| @@ -48,7 +49,7 @@
|
| object:[UIPasteboard generalPasteboard]];
|
| [[NSNotificationCenter defaultCenter]
|
| addObserver:self
|
| - selector:@selector(pasteboardChangedNotification:)
|
| + selector:@selector(didBecomeActive:)
|
| name:UIApplicationDidBecomeActiveNotification
|
| object:nil];
|
| }
|
| @@ -66,6 +67,17 @@
|
| }
|
| }
|
|
|
| +- (void)didBecomeActive:(NSNotification*)notification {
|
| + if (_delegate) {
|
| + _delegate->LoadFromUserDefaults();
|
| + base::TimeDelta uptime =
|
| + base::TimeDelta::FromMilliseconds(base::SysInfo::Uptime());
|
| + if (_delegate->HasPasteboardChanged(uptime)) {
|
| + _delegate->PasteboardChanged();
|
| + }
|
| + }
|
| +}
|
| +
|
| - (void)disconnect {
|
| _delegate = nullptr;
|
| }
|
| @@ -80,8 +92,12 @@ NSString* 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* kPasteboardChangeDateKey = @"PasteboardChangeDate";
|
| -// Key used to store the
|
| -NSString* kSuppressedPasteboardEntryCountKey = @"PasteboardSupressedEntryCount";
|
| +// 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* kPasteboardEntryMD5Key = @"PasteboardEntryMD5";
|
| +// Key used to store the date of the latest pasteboard entry displayed in the
|
| +// omnibox. This is used to report metrics on pasteboard change.
|
| +NSString* kLastDisplayedPasteboardEntryKey = @"LastDisplayedPasteboardEntry";
|
| base::TimeDelta kMaximumAgeOfClipboard = base::TimeDelta::FromHours(3);
|
| // Schemes accepted by the ClipboardRecentContentIOS.
|
| const char* kAuthorizedSchemes[] = {
|
| @@ -90,18 +106,29 @@ const char* kAuthorizedSchemes[] = {
|
| url::kDataScheme,
|
| url::kAboutScheme,
|
| };
|
| +
|
| +// Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|.
|
| +// 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.
|
| +NSData* WeakMD5FromNSString(NSString* string) {
|
| + unsigned char hash[CC_MD5_DIGEST_LENGTH];
|
| + const char* c_string = [string UTF8String];
|
| + CC_MD5(c_string, strlen(c_string), hash);
|
| + NSData* data = [NSData dataWithBytes:hash length:4];
|
| + return data;
|
| +}
|
| +
|
| } // namespace
|
|
|
| bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) const {
|
| DCHECK(url);
|
| - if (GetClipboardContentAge() > kMaximumAgeOfClipboard ||
|
| - [UIPasteboard generalPasteboard].changeCount ==
|
| - suppressedPasteboardEntryCount_) {
|
| + if (GetClipboardContentAge() > kMaximumAgeOfClipboard) {
|
| return false;
|
| }
|
|
|
| - if (urlFromPasteboardCache_.is_valid()) {
|
| - *url = urlFromPasteboardCache_;
|
| + if (url_from_pasteboard_cache_.is_valid()) {
|
| + *url = url_from_pasteboard_cache_;
|
| return true;
|
| }
|
| return false;
|
| @@ -109,81 +136,92 @@ bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) const {
|
|
|
| base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
|
| return base::TimeDelta::FromSeconds(
|
| - static_cast<int64>(-[lastPasteboardChangeDate_ timeIntervalSinceNow]));
|
| + static_cast<int64>(-[last_pasteboard_change_date_ timeIntervalSinceNow]));
|
| }
|
|
|
| void ClipboardRecentContentIOS::SuppressClipboardContent() {
|
| - suppressedPasteboardEntryCount_ =
|
| - [UIPasteboard generalPasteboard].changeCount;
|
| + // User cleared the user data. The pasteboard entry must be removed from the
|
| + // omnibox list. Force entry expiration by setting copy date to 1970.
|
| + last_pasteboard_change_date_.reset(
|
| + [[NSDate alloc] initWithTimeIntervalSince1970:0]);
|
| SaveToUserDefaults();
|
| }
|
|
|
| void ClipboardRecentContentIOS::PasteboardChanged() {
|
| - urlFromPasteboardCache_ = URLFromPasteboard();
|
| - if (!urlFromPasteboardCache_.is_empty()) {
|
| + url_from_pasteboard_cache_ = URLFromPasteboard();
|
| + if (!url_from_pasteboard_cache_.is_empty()) {
|
| base::RecordAction(
|
| base::UserMetricsAction("MobileOmniboxClipboardChanged"));
|
| }
|
| - lastPasteboardChangeDate_.reset([[NSDate date] retain]);
|
| - lastPasteboardChangeCount_ = [UIPasteboard generalPasteboard].changeCount;
|
| - if (lastPasteboardChangeCount_ != suppressedPasteboardEntryCount_) {
|
| - suppressedPasteboardEntryCount_ = NSIntegerMax;
|
| + last_pasteboard_change_date_.reset([[NSDate date] retain]);
|
| + last_pasteboard_change_count_ = [UIPasteboard generalPasteboard].changeCount;
|
| + NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string];
|
| + if (!pasteboard_string) {
|
| + pasteboard_string = @"";
|
| }
|
| + NSData* MD5 = WeakMD5FromNSString(pasteboard_string);
|
| + last_pasteboard_entry_md5_.reset([MD5 retain]);
|
| + SaveToUserDefaults();
|
| }
|
|
|
| ClipboardRecentContentIOS::ClipboardRecentContentIOS(
|
| - const std::string& application_scheme)
|
| - : application_scheme_(application_scheme) {
|
| + const std::string& application_scheme,
|
| + NSUserDefaults* group_user_defaults)
|
| + : application_scheme_(application_scheme),
|
| + shared_user_defaults_([group_user_defaults retain]) {
|
| Init(base::TimeDelta::FromMilliseconds(base::SysInfo::Uptime()));
|
| }
|
|
|
| ClipboardRecentContentIOS::ClipboardRecentContentIOS(
|
| const std::string& application_scheme,
|
| base::TimeDelta uptime)
|
| - : application_scheme_(application_scheme) {
|
| + : application_scheme_(application_scheme),
|
| + shared_user_defaults_([[NSUserDefaults standardUserDefaults] retain]) {
|
| Init(uptime);
|
| }
|
|
|
| +bool ClipboardRecentContentIOS::HasPasteboardChanged(base::TimeDelta uptime) {
|
| + // 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.
|
| + NSInteger change_count = [UIPasteboard generalPasteboard].changeCount;
|
| + bool change_count_changed = change_count != last_pasteboard_change_count_;
|
| +
|
| + bool not_rebooted = uptime > GetClipboardContentAge();
|
| + if (not_rebooted)
|
| + return change_count_changed;
|
| +
|
| + NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string];
|
| + if (!pasteboard_string) {
|
| + pasteboard_string = @"";
|
| + }
|
| + NSData* md5 = WeakMD5FromNSString(pasteboard_string);
|
| + BOOL md5_changed = ![md5 isEqualToData:last_pasteboard_entry_md5_];
|
| +
|
| + return md5_changed;
|
| +}
|
| +
|
| void ClipboardRecentContentIOS::Init(base::TimeDelta uptime) {
|
| - lastPasteboardChangeCount_ = NSIntegerMax;
|
| - suppressedPasteboardEntryCount_ = NSIntegerMax;
|
| - urlFromPasteboardCache_ = URLFromPasteboard();
|
| + last_pasteboard_change_count_ = NSIntegerMax;
|
| + url_from_pasteboard_cache_ = URLFromPasteboard();
|
| LoadFromUserDefaults();
|
|
|
| - // On iOS 7 (unlike on iOS 8, despite what the documentation says), the change
|
| - // count is reset when the device is rebooted.
|
| - if (uptime < GetClipboardContentAge() &&
|
| - !base::ios::IsRunningOnIOS8OrLater()) {
|
| - if ([UIPasteboard generalPasteboard].changeCount == 0) {
|
| - // The user hasn't pasted anything in the clipboard since the device's
|
| - // reboot. |PasteboardChanged| isn't called because it would update
|
| - // |lastPasteboardChangeData_|, and record metrics.
|
| - lastPasteboardChangeCount_ = 0;
|
| - if (suppressedPasteboardEntryCount_ != NSIntegerMax) {
|
| - // If the last time Chrome was running the pasteboard was suppressed,
|
| - // and the user has not copied anything since the device launched, then
|
| - // supress this entry.
|
| - suppressedPasteboardEntryCount_ = 0;
|
| - }
|
| - SaveToUserDefaults();
|
| - } else {
|
| - // The user pasted something in the clipboard since the device's reboot.
|
| - PasteboardChanged();
|
| - }
|
| - } else {
|
| - NSInteger changeCount = [UIPasteboard generalPasteboard].changeCount;
|
| - if (changeCount != lastPasteboardChangeCount_) {
|
| - PasteboardChanged();
|
| - }
|
| - }
|
| - // Makes sure |lastPasteboardChangeCount_| was properly initialized.
|
| - DCHECK_NE(lastPasteboardChangeCount_, NSIntegerMax);
|
| - notificationBridge_.reset(
|
| + if (HasPasteboardChanged(uptime))
|
| + PasteboardChanged();
|
| +
|
| + // Makes sure |last_pasteboard_change_count_| was properly initialized.
|
| + DCHECK_NE(last_pasteboard_change_count_, NSIntegerMax);
|
| + notification_bridge_.reset(
|
| [[PasteboardNotificationListenerBridge alloc] initWithDelegate:this]);
|
| }
|
|
|
| ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {
|
| - [notificationBridge_ disconnect];
|
| + [notification_bridge_ disconnect];
|
| }
|
|
|
| GURL ClipboardRecentContentIOS::URLFromPasteboard() {
|
| @@ -204,32 +242,37 @@ GURL ClipboardRecentContentIOS::URLFromPasteboard() {
|
| return GURL::EmptyGURL();
|
| }
|
|
|
| -void ClipboardRecentContentIOS::LoadFromUserDefaults() {
|
| - NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
|
| -
|
| - lastPasteboardChangeCount_ =
|
| - [defaults integerForKey:kPasteboardChangeCountKey];
|
| - lastPasteboardChangeDate_.reset(
|
| - [[defaults objectForKey:kPasteboardChangeDateKey] retain]);
|
| -
|
| - if ([[[defaults dictionaryRepresentation] allKeys]
|
| - containsObject:kSuppressedPasteboardEntryCountKey]) {
|
| - suppressedPasteboardEntryCount_ =
|
| - [defaults integerForKey:kSuppressedPasteboardEntryCountKey];
|
| - } else {
|
| - suppressedPasteboardEntryCount_ = NSIntegerMax;
|
| +void ClipboardRecentContentIOS::RecentURLDisplayed() {
|
| + if ([last_pasteboard_change_date_
|
| + isEqualToDate:last_displayed_pasteboard_entry_.get()]) {
|
| + return;
|
| }
|
| + base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged"));
|
| + last_pasteboard_change_date_ = last_displayed_pasteboard_entry_;
|
| + SaveToUserDefaults();
|
| +}
|
| +
|
| +void ClipboardRecentContentIOS::LoadFromUserDefaults() {
|
| + last_pasteboard_change_count_ =
|
| + [shared_user_defaults_ integerForKey:kPasteboardChangeCountKey];
|
| + last_pasteboard_change_date_.reset(
|
| + [[shared_user_defaults_ objectForKey:kPasteboardChangeDateKey] retain]);
|
| + last_pasteboard_entry_md5_.reset(
|
| + [[shared_user_defaults_ objectForKey:kPasteboardEntryMD5Key] retain]);
|
| + last_displayed_pasteboard_entry_.reset([[shared_user_defaults_
|
| + objectForKey:kLastDisplayedPasteboardEntryKey] retain]);
|
|
|
| - DCHECK(!lastPasteboardChangeDate_ ||
|
| - [lastPasteboardChangeDate_ isKindOfClass:[NSDate class]]);
|
| + DCHECK(!last_pasteboard_change_date_ ||
|
| + [last_pasteboard_change_date_ isKindOfClass:[NSDate class]]);
|
| }
|
|
|
| void ClipboardRecentContentIOS::SaveToUserDefaults() {
|
| - NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
|
| - [defaults setInteger:lastPasteboardChangeCount_
|
| - forKey:kPasteboardChangeCountKey];
|
| - [defaults setObject:lastPasteboardChangeDate_
|
| - forKey:kPasteboardChangeDateKey];
|
| - [defaults setInteger:suppressedPasteboardEntryCount_
|
| - forKey:kSuppressedPasteboardEntryCountKey];
|
| + [shared_user_defaults_ setInteger:last_pasteboard_change_count_
|
| + forKey:kPasteboardChangeCountKey];
|
| + [shared_user_defaults_ setObject:last_pasteboard_change_date_
|
| + forKey:kPasteboardChangeDateKey];
|
| + [shared_user_defaults_ setObject:last_pasteboard_entry_md5_
|
| + forKey:kPasteboardEntryMD5Key];
|
| + [shared_user_defaults_ setObject:last_displayed_pasteboard_entry_
|
| + forKey:kLastDisplayedPasteboardEntryKey];
|
| }
|
|
|