Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(50)

Side by Side Diff: components/open_from_clipboard/clipboard_recent_content_ios.mm

Issue 2782823003: Rewrite implementation of ClipboardRecentContent in Objective C. (Closed)
Patch Set: build.gn fix Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "components/open_from_clipboard/clipboard_recent_content_ios.h" 5 #import "components/open_from_clipboard/clipboard_recent_content_ios.h"
6 6
7 #import <CommonCrypto/CommonDigest.h> 7 #import <CommonCrypto/CommonDigest.h>
8 #include <stddef.h> 8 #include <stddef.h>
9 #include <stdint.h> 9 #include <stdint.h>
10 #import <UIKit/UIKit.h> 10 #import <UIKit/UIKit.h>
11 11
12 #import "base/ios/ios_util.h" 12 #import "base/ios/ios_util.h"
13 #include "base/logging.h" 13 #include "base/logging.h"
14 #include "base/macros.h" 14 #include "base/macros.h"
15 #include "base/metrics/user_metrics.h" 15 #include "base/metrics/user_metrics.h"
16 #include "base/strings/sys_string_conversions.h" 16 #include "base/strings/sys_string_conversions.h"
17 #include "base/sys_info.h" 17 #include "base/sys_info.h"
18 #import "components/open_from_clipboard/clipboard_recent_content_impl_ios.h"
19 #import "net/base/mac/url_conversions.h"
18 #include "url/gurl.h" 20 #include "url/gurl.h"
21 #include "url/url_constants.h"
19 22
20 // Bridge that forwards UIApplicationDidBecomeActiveNotification notifications 23 namespace {
21 // to its delegate.
22 @interface ApplicationDidBecomeActiveNotificationListenerBridge : NSObject
23 24
24 // Initialize the ApplicationDidBecomeActiveNotificationListenerBridge with 25 // Schemes accepted by the ClipboardRecentContentIOS.
25 // |delegate| which must not be null. 26 const char* kAuthorizedSchemes[] = {
26 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate 27 url::kHttpScheme, url::kHttpsScheme, url::kDataScheme, url::kAboutScheme,
27 NS_DESIGNATED_INITIALIZER; 28 };
28 29
29 - (instancetype)init NS_UNAVAILABLE; 30 // Get the list of authorized schemes.
31 NSSet<NSString*>* getAuthorizedSchemeList(
32 const std::string& application_scheme) {
33 NSMutableSet<NSString*>* schemes = [NSMutableSet set];
34 for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) {
35 [schemes addObject:base::SysUTF8ToNSString(kAuthorizedSchemes[i])];
36 }
37 if (!application_scheme.empty()) {
38 [schemes addObject:base::SysUTF8ToNSString(application_scheme)];
39 }
30 40
41 return [schemes copy];
42 }
43
44 } // namespace
45
46 @interface ClipboardRecentContentDelegateImpl
47 : NSObject<ClipboardRecentContentDelegate>
31 @end 48 @end
32 49
33 @implementation ApplicationDidBecomeActiveNotificationListenerBridge { 50 @implementation ClipboardRecentContentDelegateImpl
34 ClipboardRecentContentIOS* _delegate;
35 }
36 51
37 - (instancetype)init { 52 - (void)onClipboardChanged {
38 NOTREACHED(); 53 base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged"));
39 return nil;
40 }
41
42 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate {
43 DCHECK(delegate);
44 self = [super init];
45 if (self) {
46 _delegate = delegate;
47 [[NSNotificationCenter defaultCenter]
48 addObserver:self
49 selector:@selector(didBecomeActive:)
50 name:UIApplicationDidBecomeActiveNotification
51 object:nil];
52 }
53 return self;
54 }
55
56 - (void)dealloc {
57 [[NSNotificationCenter defaultCenter] removeObserver:self];
58 [super dealloc];
59 }
60
61 - (void)didBecomeActive:(NSNotification*)notification {
62 if (_delegate) {
63 _delegate->LoadFromUserDefaults();
64 _delegate->UpdateIfNeeded();
65 }
66 }
67
68 - (void)disconnect {
69 _delegate = nullptr;
70 } 54 }
71 55
72 @end 56 @end
73 57
74 namespace { 58 ClipboardRecentContentIOS::ClipboardRecentContentIOS(
75 // Key used to store the pasteboard's current change count. If when resuming 59 const std::string& application_scheme,
76 // chrome the pasteboard's change count is different from the stored one, then 60 NSUserDefaults* group_user_defaults)
77 // it means that the pasteboard's content has changed. 61 : ClipboardRecentContentIOS([[ClipboardRecentContentImplIOS alloc]
78 NSString* kPasteboardChangeCountKey = @"PasteboardChangeCount"; 62 initWithAuthorizedSchemes:getAuthorizedSchemeList(application_scheme)
79 // Key used to store the last date at which it was detected that the pasteboard 63 userDefaults:group_user_defaults
80 // changed. It is used to evaluate the age of the pasteboard's content. 64 delegate:[[ClipboardRecentContentDelegateImpl alloc]
81 NSString* kPasteboardChangeDateKey = @"PasteboardChangeDate"; 65 init]]) {}
82 // Key used to store the hash of the content of the pasteboard. Whenever the
83 // hash changed, the pasteboard content is considered to have changed.
84 NSString* kPasteboardEntryMD5Key = @"PasteboardEntryMD5";
85 66
86 // Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|. 67 ClipboardRecentContentIOS::ClipboardRecentContentIOS(
87 // This value is used to detect pasteboard content change. Keeping only 4 bytes 68 ClipboardRecentContentImplIOS* implementation) {
88 // is a privacy requirement to introduce collision and allow deniability of 69 implementation_.reset(implementation);
89 // having copied a given string.
90 NSData* WeakMD5FromNSString(NSString* string) {
91 unsigned char hash[CC_MD5_DIGEST_LENGTH];
92 const std::string clipboard = base::SysNSStringToUTF8(string);
93 const char* c_string = clipboard.c_str();
94 CC_MD5(c_string, strlen(c_string), hash);
95 NSData* data = [NSData dataWithBytes:hash length:4];
96 return data;
97 } 70 }
98 71
99 } // namespace
100
101 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) { 72 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) {
102 DCHECK(url); 73 DCHECK(url);
103 UpdateIfNeeded(); 74 NSURL* url_from_pasteboard = [implementation_ recentURLFromClipboard];
104 if (GetClipboardContentAge() > kMaximumAgeOfClipboard) { 75 GURL converted_url = net::GURLWithNSURL(url_from_pasteboard);
105 return false; 76 if (converted_url.is_valid()) {
106 } 77 *url = std::move(converted_url);
107
108 GURL url_from_pasteboard = URLFromPasteboard();
109 if (url_from_pasteboard.is_valid()) {
110 *url = url_from_pasteboard;
111 return true; 78 return true;
112 } 79 }
113 return false; 80 return false;
114 } 81 }
115 82
83 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {}
84
116 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const { 85 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
117 return base::TimeDelta::FromSeconds(static_cast<int64_t>( 86 return base::TimeDelta::FromSeconds(
118 -[last_pasteboard_change_date_ timeIntervalSinceNow])); 87 static_cast<int64_t>([implementation_ clipboardContentAge]));
119 } 88 }
120 89
121 void ClipboardRecentContentIOS::SuppressClipboardContent() { 90 void ClipboardRecentContentIOS::SuppressClipboardContent() {
122 // User cleared the user data. The pasteboard entry must be removed from the 91 [implementation_ suppressClipboardContent];
123 // omnibox list. Force entry expiration by setting copy date to 1970.
124 last_pasteboard_change_date_.reset(
125 [[NSDate alloc] initWithTimeIntervalSince1970:0]);
126 SaveToUserDefaults();
127 } 92 }
128
129 void ClipboardRecentContentIOS::UpdateIfNeeded() {
130 if (!HasPasteboardChanged())
131 return;
132
133 base::RecordAction(base::UserMetricsAction("MobileClipboardChanged"));
134
135 GURL url_from_pasteboard = URLFromPasteboard();
136 last_pasteboard_change_date_.reset([[NSDate date] retain]);
137 last_pasteboard_change_count_ = [UIPasteboard generalPasteboard].changeCount;
138 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string];
139 if (!pasteboard_string) {
140 pasteboard_string = @"";
141 }
142 NSData* MD5 = WeakMD5FromNSString(pasteboard_string);
143 last_pasteboard_entry_md5_.reset([MD5 retain]);
144 SaveToUserDefaults();
145 }
146
147 ClipboardRecentContentIOS::ClipboardRecentContentIOS(
148 const std::string& application_scheme,
149 NSUserDefaults* group_user_defaults)
150 : application_scheme_(application_scheme),
151 shared_user_defaults_([group_user_defaults retain]) {
152 last_pasteboard_change_count_ = NSIntegerMax;
153 LoadFromUserDefaults();
154
155 UpdateIfNeeded();
156
157 // Makes sure |last_pasteboard_change_count_| was properly initialized.
158 DCHECK_NE(last_pasteboard_change_count_, NSIntegerMax);
159 notification_bridge_.reset(
160 [[ApplicationDidBecomeActiveNotificationListenerBridge alloc]
161 initWithDelegate:this]);
162 }
163
164 bool ClipboardRecentContentIOS::HasPasteboardChanged() const {
165 // If |MD5Changed|, we know for sure there has been at least one pasteboard
166 // copy since last time it was checked.
167 // If the pasteboard content is still the same but the device was not
168 // rebooted, the change count can be checked to see if it changed.
169 // Note: due to a mismatch between the actual behavior and documentation, and
170 // lack of consistency on different reboot scenarios, the change count cannot
171 // be checked after a reboot.
172 // See radar://21833556 for more information.
173 NSInteger change_count = [UIPasteboard generalPasteboard].changeCount;
174 bool change_count_changed = change_count != last_pasteboard_change_count_;
175
176 bool not_rebooted = Uptime() > GetClipboardContentAge();
177 if (not_rebooted)
178 return change_count_changed;
179
180 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string];
181 if (!pasteboard_string) {
182 pasteboard_string = @"";
183 }
184 NSData* md5 = WeakMD5FromNSString(pasteboard_string);
185 BOOL md5_changed = ![md5 isEqualToData:last_pasteboard_entry_md5_];
186
187 return md5_changed;
188 }
189
190 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {
191 [notification_bridge_ disconnect];
192 }
193
194 GURL ClipboardRecentContentIOS::URLFromPasteboard() {
195 NSString* clipboard_string = [[UIPasteboard generalPasteboard] string];
196 if (!clipboard_string) {
197 return GURL::EmptyGURL();
198 }
199 const std::string clipboard = base::SysNSStringToUTF8(clipboard_string);
200 GURL gurl = GURL(clipboard);
201 if (gurl.is_valid()) {
202 if (IsAppropriateSuggestion(gurl))
203 return gurl;
204 if (!application_scheme_.empty() &&
205 gurl.SchemeIs(application_scheme_.c_str())) {
206 return gurl;
207 }
208 }
209 return GURL::EmptyGURL();
210 }
211
212 void ClipboardRecentContentIOS::LoadFromUserDefaults() {
213 last_pasteboard_change_count_ =
214 [shared_user_defaults_ integerForKey:kPasteboardChangeCountKey];
215 last_pasteboard_change_date_.reset(
216 [[shared_user_defaults_ objectForKey:kPasteboardChangeDateKey] retain]);
217 last_pasteboard_entry_md5_.reset(
218 [[shared_user_defaults_ objectForKey:kPasteboardEntryMD5Key] retain]);
219
220 DCHECK(!last_pasteboard_change_date_ ||
221 [last_pasteboard_change_date_ isKindOfClass:[NSDate class]]);
222 }
223
224 void ClipboardRecentContentIOS::SaveToUserDefaults() {
225 [shared_user_defaults_ setInteger:last_pasteboard_change_count_
226 forKey:kPasteboardChangeCountKey];
227 [shared_user_defaults_ setObject:last_pasteboard_change_date_
228 forKey:kPasteboardChangeDateKey];
229 [shared_user_defaults_ setObject:last_pasteboard_entry_md5_
230 forKey:kPasteboardEntryMD5Key];
231 }
232
233 base::TimeDelta ClipboardRecentContentIOS::Uptime() const {
234 return base::SysInfo::Uptime();
235 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698