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

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

Issue 2230983002: Stop using UIPasteboardChangedNotification. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed comments. Created 4 years, 4 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 #include "url/gurl.h" 18 #include "url/gurl.h"
19 #include "url/url_constants.h" 19 #include "url/url_constants.h"
20 20
21 // Bridge that forwards pasteboard change notifications to its delegate. 21 // Bridge that forwards UIApplicationDidBecomeActiveNotification notifications
22 @interface PasteboardNotificationListenerBridge : NSObject 22 // to its delegate.
23 @interface ApplicationDidBecomeActiveNotificationListenerBridge : NSObject
23 24
24 // Initialize the PasteboardNotificationListenerBridge with |delegate| which 25 // Initialize the ApplicationDidBecomeActiveNotificationListenerBridge with
25 // must not be null. 26 // |delegate| which must not be null.
26 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate 27 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate
27 NS_DESIGNATED_INITIALIZER; 28 NS_DESIGNATED_INITIALIZER;
28 29
29 - (instancetype)init NS_UNAVAILABLE; 30 - (instancetype)init NS_UNAVAILABLE;
30 31
31 @end 32 @end
32 33
33 @implementation PasteboardNotificationListenerBridge { 34 @implementation ApplicationDidBecomeActiveNotificationListenerBridge {
34 ClipboardRecentContentIOS* _delegate; 35 ClipboardRecentContentIOS* _delegate;
35 } 36 }
36 37
37 - (instancetype)init { 38 - (instancetype)init {
38 NOTREACHED(); 39 NOTREACHED();
39 return nil; 40 return nil;
40 } 41 }
41 42
42 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate { 43 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate {
43 DCHECK(delegate); 44 DCHECK(delegate);
44 self = [super init]; 45 self = [super init];
45 if (self) { 46 if (self) {
46 _delegate = delegate; 47 _delegate = delegate;
47 [[NSNotificationCenter defaultCenter] 48 [[NSNotificationCenter defaultCenter]
48 addObserver:self 49 addObserver:self
49 selector:@selector(pasteboardChangedNotification:)
50 name:UIPasteboardChangedNotification
51 object:[UIPasteboard generalPasteboard]];
52 [[NSNotificationCenter defaultCenter]
53 addObserver:self
54 selector:@selector(didBecomeActive:) 50 selector:@selector(didBecomeActive:)
55 name:UIApplicationDidBecomeActiveNotification 51 name:UIApplicationDidBecomeActiveNotification
56 object:nil]; 52 object:nil];
57 } 53 }
58 return self; 54 return self;
59 } 55 }
60 56
61 - (void)dealloc { 57 - (void)dealloc {
62 [[NSNotificationCenter defaultCenter] removeObserver:self]; 58 [[NSNotificationCenter defaultCenter] removeObserver:self];
63 [super dealloc]; 59 [super dealloc];
64 } 60 }
65 61
66 - (void)pasteboardChangedNotification:(NSNotification*)notification {
67 if (_delegate) {
68 _delegate->PasteboardChanged();
69 }
70 }
71
72 - (void)didBecomeActive:(NSNotification*)notification { 62 - (void)didBecomeActive:(NSNotification*)notification {
73 if (_delegate) { 63 if (_delegate) {
74 _delegate->LoadFromUserDefaults(); 64 _delegate->LoadFromUserDefaults();
75 if (_delegate->HasPasteboardChanged(base::SysInfo::Uptime())) { 65 _delegate->UpdateIfNeeded();
76 _delegate->PasteboardChanged();
77 }
78 } 66 }
79 } 67 }
80 68
81 - (void)disconnect { 69 - (void)disconnect {
82 _delegate = nullptr; 70 _delegate = nullptr;
83 } 71 }
84 72
85 @end 73 @end
86 74
87 namespace { 75 namespace {
88 // Key used to store the pasteboard's current change count. If when resuming 76 // Key used to store the pasteboard's current change count. If when resuming
89 // chrome the pasteboard's change count is different from the stored one, then 77 // chrome the pasteboard's change count is different from the stored one, then
90 // it means that the pasteboard's content has changed. 78 // it means that the pasteboard's content has changed.
91 NSString* kPasteboardChangeCountKey = @"PasteboardChangeCount"; 79 NSString* kPasteboardChangeCountKey = @"PasteboardChangeCount";
92 // Key used to store the last date at which it was detected that the pasteboard 80 // Key used to store the last date at which it was detected that the pasteboard
93 // changed. It is used to evaluate the age of the pasteboard's content. 81 // changed. It is used to evaluate the age of the pasteboard's content.
94 NSString* kPasteboardChangeDateKey = @"PasteboardChangeDate"; 82 NSString* kPasteboardChangeDateKey = @"PasteboardChangeDate";
95 // Key used to store the hash of the content of the pasteboard. Whenever the 83 // Key used to store the hash of the content of the pasteboard. Whenever the
96 // hash changed, the pasteboard content is considered to have changed. 84 // hash changed, the pasteboard content is considered to have changed.
97 NSString* kPasteboardEntryMD5Key = @"PasteboardEntryMD5"; 85 NSString* kPasteboardEntryMD5Key = @"PasteboardEntryMD5";
98 // Key used to store the date of the latest pasteboard entry displayed in the
99 // omnibox. This is used to report metrics on pasteboard change.
100 NSString* kLastDisplayedPasteboardEntryKey = @"LastDisplayedPasteboardEntry";
101 base::TimeDelta kMaximumAgeOfClipboard = base::TimeDelta::FromHours(3); 86 base::TimeDelta kMaximumAgeOfClipboard = base::TimeDelta::FromHours(3);
102 // Schemes accepted by the ClipboardRecentContentIOS. 87 // Schemes accepted by the ClipboardRecentContentIOS.
103 const char* kAuthorizedSchemes[] = { 88 const char* kAuthorizedSchemes[] = {
104 url::kHttpScheme, 89 url::kHttpScheme,
105 url::kHttpsScheme, 90 url::kHttpsScheme,
106 url::kDataScheme, 91 url::kDataScheme,
107 url::kAboutScheme, 92 url::kAboutScheme,
108 }; 93 };
109 94
110 // Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|. 95 // Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|.
111 // This value is used to detect pasteboard content change. Keeping only 4 bytes 96 // This value is used to detect pasteboard content change. Keeping only 4 bytes
112 // is a privacy requirement to introduce collision and allow deniability of 97 // is a privacy requirement to introduce collision and allow deniability of
113 // having copied a given string. 98 // having copied a given string.
114 NSData* WeakMD5FromNSString(NSString* string) { 99 NSData* WeakMD5FromNSString(NSString* string) {
115 unsigned char hash[CC_MD5_DIGEST_LENGTH]; 100 unsigned char hash[CC_MD5_DIGEST_LENGTH];
116 const char* c_string = [string UTF8String]; 101 const char* c_string = [string UTF8String];
117 CC_MD5(c_string, strlen(c_string), hash); 102 CC_MD5(c_string, strlen(c_string), hash);
118 NSData* data = [NSData dataWithBytes:hash length:4]; 103 NSData* data = [NSData dataWithBytes:hash length:4];
119 return data; 104 return data;
120 } 105 }
121 106
122 } // namespace 107 } // namespace
123 108
124 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) const { 109 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) {
125 DCHECK(url); 110 DCHECK(url);
111 UpdateIfNeeded();
126 if (GetClipboardContentAge() > kMaximumAgeOfClipboard) { 112 if (GetClipboardContentAge() > kMaximumAgeOfClipboard) {
127 return false; 113 return false;
128 } 114 }
129 115
130 if (url_from_pasteboard_cache_.is_valid()) { 116 GURL url_from_pasteboard = URLFromPasteboard();
131 *url = url_from_pasteboard_cache_; 117 if (url_from_pasteboard.is_valid()) {
118 *url = url_from_pasteboard;
132 return true; 119 return true;
133 } 120 }
134 return false; 121 return false;
135 } 122 }
136 123
137 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const { 124 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
138 return base::TimeDelta::FromSeconds(static_cast<int64_t>( 125 return base::TimeDelta::FromSeconds(static_cast<int64_t>(
139 -[last_pasteboard_change_date_ timeIntervalSinceNow])); 126 -[last_pasteboard_change_date_ timeIntervalSinceNow]));
140 } 127 }
141 128
142 void ClipboardRecentContentIOS::SuppressClipboardContent() { 129 void ClipboardRecentContentIOS::SuppressClipboardContent() {
143 // User cleared the user data. The pasteboard entry must be removed from the 130 // User cleared the user data. The pasteboard entry must be removed from the
144 // omnibox list. Force entry expiration by setting copy date to 1970. 131 // omnibox list. Force entry expiration by setting copy date to 1970.
145 last_pasteboard_change_date_.reset( 132 last_pasteboard_change_date_.reset(
146 [[NSDate alloc] initWithTimeIntervalSince1970:0]); 133 [[NSDate alloc] initWithTimeIntervalSince1970:0]);
147 SaveToUserDefaults(); 134 SaveToUserDefaults();
148 } 135 }
149 136
150 void ClipboardRecentContentIOS::PasteboardChanged() { 137 void ClipboardRecentContentIOS::UpdateIfNeeded() {
151 url_from_pasteboard_cache_ = URLFromPasteboard(); 138 if (!HasPasteboardChanged())
152 if (!url_from_pasteboard_cache_.is_empty()) { 139 return;
153 base::RecordAction( 140
154 base::UserMetricsAction("MobileOmniboxClipboardChanged")); 141 base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged"));
Olivier 2016/08/17 11:54:29 This is changing the semantic of the metric. Is th
jif 2016/08/17 15:43:01 Indeed, it's changing the semantic in 2 ways: 1/ W
155 } 142
143 GURL url_from_pasteboard = URLFromPasteboard();
156 last_pasteboard_change_date_.reset([[NSDate date] retain]); 144 last_pasteboard_change_date_.reset([[NSDate date] retain]);
157 last_pasteboard_change_count_ = [UIPasteboard generalPasteboard].changeCount; 145 last_pasteboard_change_count_ = [UIPasteboard generalPasteboard].changeCount;
158 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string]; 146 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string];
159 if (!pasteboard_string) { 147 if (!pasteboard_string) {
160 pasteboard_string = @""; 148 pasteboard_string = @"";
161 } 149 }
162 NSData* MD5 = WeakMD5FromNSString(pasteboard_string); 150 NSData* MD5 = WeakMD5FromNSString(pasteboard_string);
163 last_pasteboard_entry_md5_.reset([MD5 retain]); 151 last_pasteboard_entry_md5_.reset([MD5 retain]);
164 SaveToUserDefaults(); 152 SaveToUserDefaults();
165 } 153 }
166 154
167 ClipboardRecentContentIOS::ClipboardRecentContentIOS( 155 ClipboardRecentContentIOS::ClipboardRecentContentIOS(
168 const std::string& application_scheme, 156 const std::string& application_scheme,
169 NSUserDefaults* group_user_defaults) 157 NSUserDefaults* group_user_defaults)
170 : application_scheme_(application_scheme), 158 : application_scheme_(application_scheme),
171 shared_user_defaults_([group_user_defaults retain]) { 159 shared_user_defaults_([group_user_defaults retain]) {
172 Init(base::SysInfo::Uptime()); 160 last_pasteboard_change_count_ = NSIntegerMax;
161 LoadFromUserDefaults();
162
163 UpdateIfNeeded();
164
165 // Makes sure |last_pasteboard_change_count_| was properly initialized.
166 DCHECK_NE(last_pasteboard_change_count_, NSIntegerMax);
167 notification_bridge_.reset(
168 [[ApplicationDidBecomeActiveNotificationListenerBridge alloc]
169 initWithDelegate:this]);
173 } 170 }
174 171
175 ClipboardRecentContentIOS::ClipboardRecentContentIOS( 172 bool ClipboardRecentContentIOS::HasPasteboardChanged() const {
176 const std::string& application_scheme,
177 base::TimeDelta uptime)
178 : application_scheme_(application_scheme),
179 shared_user_defaults_([[NSUserDefaults standardUserDefaults] retain]) {
180 Init(uptime);
181 }
182
183 bool ClipboardRecentContentIOS::HasPasteboardChanged(base::TimeDelta uptime) {
184 // If |MD5Changed|, we know for sure there has been at least one pasteboard 173 // If |MD5Changed|, we know for sure there has been at least one pasteboard
185 // copy since last time it was checked. 174 // copy since last time it was checked.
186 // If the pasteboard content is still the same but the device was not 175 // If the pasteboard content is still the same but the device was not
187 // rebooted, the change count can be checked to see if it changed. 176 // rebooted, the change count can be checked to see if it changed.
188 // Note: due to a mismatch between the actual behavior and documentation, and 177 // Note: due to a mismatch between the actual behavior and documentation, and
189 // lack of consistency on different reboot scenarios, the change count cannot 178 // lack of consistency on different reboot scenarios, the change count cannot
190 // be checked after a reboot. 179 // be checked after a reboot.
191 // See radar://21833556 for more information. 180 // See radar://21833556 for more information.
192 NSInteger change_count = [UIPasteboard generalPasteboard].changeCount; 181 NSInteger change_count = [UIPasteboard generalPasteboard].changeCount;
193 bool change_count_changed = change_count != last_pasteboard_change_count_; 182 bool change_count_changed = change_count != last_pasteboard_change_count_;
194 183
195 bool not_rebooted = uptime > GetClipboardContentAge(); 184 bool not_rebooted = Uptime() > GetClipboardContentAge();
196 if (not_rebooted) 185 if (not_rebooted)
197 return change_count_changed; 186 return change_count_changed;
198 187
199 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string]; 188 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string];
200 if (!pasteboard_string) { 189 if (!pasteboard_string) {
201 pasteboard_string = @""; 190 pasteboard_string = @"";
202 } 191 }
203 NSData* md5 = WeakMD5FromNSString(pasteboard_string); 192 NSData* md5 = WeakMD5FromNSString(pasteboard_string);
204 BOOL md5_changed = ![md5 isEqualToData:last_pasteboard_entry_md5_]; 193 BOOL md5_changed = ![md5 isEqualToData:last_pasteboard_entry_md5_];
205 194
206 return md5_changed; 195 return md5_changed;
207 } 196 }
208 197
209 bool ClipboardRecentContentIOS::GetCurrentURLFromClipboard(GURL* url) {
210 if (HasPasteboardChanged(base::SysInfo::Uptime())) {
211 PasteboardChanged();
212 }
213 return GetRecentURLFromClipboard(url);
214 }
215
216 void ClipboardRecentContentIOS::Init(base::TimeDelta uptime) {
217 last_pasteboard_change_count_ = NSIntegerMax;
218 url_from_pasteboard_cache_ = URLFromPasteboard();
219 LoadFromUserDefaults();
220
221 if (HasPasteboardChanged(uptime))
222 PasteboardChanged();
223
224 // Makes sure |last_pasteboard_change_count_| was properly initialized.
225 DCHECK_NE(last_pasteboard_change_count_, NSIntegerMax);
226 notification_bridge_.reset(
227 [[PasteboardNotificationListenerBridge alloc] initWithDelegate:this]);
228 }
229
230 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() { 198 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {
231 [notification_bridge_ disconnect]; 199 [notification_bridge_ disconnect];
232 } 200 }
233 201
234 GURL ClipboardRecentContentIOS::URLFromPasteboard() { 202 GURL ClipboardRecentContentIOS::URLFromPasteboard() {
235 NSString* clipboard_string = [[UIPasteboard generalPasteboard] string]; 203 NSString* clipboard_string = [[UIPasteboard generalPasteboard] string];
236 if (!clipboard_string) { 204 if (!clipboard_string) {
237 return GURL::EmptyGURL(); 205 return GURL::EmptyGURL();
238 } 206 }
239 const std::string clipboard = base::SysNSStringToUTF8(clipboard_string); 207 const std::string clipboard = base::SysNSStringToUTF8(clipboard_string);
240 GURL gurl = GURL(clipboard); 208 GURL gurl = GURL(clipboard);
241 if (gurl.is_valid()) { 209 if (gurl.is_valid()) {
242 for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) { 210 for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) {
243 if (gurl.SchemeIs(kAuthorizedSchemes[i])) { 211 if (gurl.SchemeIs(kAuthorizedSchemes[i])) {
244 return gurl; 212 return gurl;
245 } 213 }
246 } 214 }
247 if (!application_scheme_.empty() && 215 if (!application_scheme_.empty() &&
248 gurl.SchemeIs(application_scheme_.c_str())) { 216 gurl.SchemeIs(application_scheme_.c_str())) {
249 return gurl; 217 return gurl;
250 } 218 }
251 } 219 }
252 return GURL::EmptyGURL(); 220 return GURL::EmptyGURL();
253 } 221 }
254 222
255 void ClipboardRecentContentIOS::RecentURLDisplayed() {
Olivier 2016/08/17 11:54:29 not sure why this was needed and is not needed any
jif 2016/08/17 15:43:01 added in: https://codereview.chromium.org/12887330
256 if ([last_pasteboard_change_date_
257 isEqualToDate:last_displayed_pasteboard_entry_.get()]) {
258 return;
259 }
260 base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged"));
261 last_pasteboard_change_date_ = last_displayed_pasteboard_entry_;
262 SaveToUserDefaults();
263 }
264
265 void ClipboardRecentContentIOS::LoadFromUserDefaults() { 223 void ClipboardRecentContentIOS::LoadFromUserDefaults() {
266 last_pasteboard_change_count_ = 224 last_pasteboard_change_count_ =
267 [shared_user_defaults_ integerForKey:kPasteboardChangeCountKey]; 225 [shared_user_defaults_ integerForKey:kPasteboardChangeCountKey];
268 last_pasteboard_change_date_.reset( 226 last_pasteboard_change_date_.reset(
269 [[shared_user_defaults_ objectForKey:kPasteboardChangeDateKey] retain]); 227 [[shared_user_defaults_ objectForKey:kPasteboardChangeDateKey] retain]);
270 last_pasteboard_entry_md5_.reset( 228 last_pasteboard_entry_md5_.reset(
271 [[shared_user_defaults_ objectForKey:kPasteboardEntryMD5Key] retain]); 229 [[shared_user_defaults_ objectForKey:kPasteboardEntryMD5Key] retain]);
272 last_displayed_pasteboard_entry_.reset([[shared_user_defaults_
273 objectForKey:kLastDisplayedPasteboardEntryKey] retain]);
274 230
275 DCHECK(!last_pasteboard_change_date_ || 231 DCHECK(!last_pasteboard_change_date_ ||
276 [last_pasteboard_change_date_ isKindOfClass:[NSDate class]]); 232 [last_pasteboard_change_date_ isKindOfClass:[NSDate class]]);
277 } 233 }
278 234
279 void ClipboardRecentContentIOS::SaveToUserDefaults() { 235 void ClipboardRecentContentIOS::SaveToUserDefaults() {
280 [shared_user_defaults_ setInteger:last_pasteboard_change_count_ 236 [shared_user_defaults_ setInteger:last_pasteboard_change_count_
281 forKey:kPasteboardChangeCountKey]; 237 forKey:kPasteboardChangeCountKey];
282 [shared_user_defaults_ setObject:last_pasteboard_change_date_ 238 [shared_user_defaults_ setObject:last_pasteboard_change_date_
283 forKey:kPasteboardChangeDateKey]; 239 forKey:kPasteboardChangeDateKey];
284 [shared_user_defaults_ setObject:last_pasteboard_entry_md5_ 240 [shared_user_defaults_ setObject:last_pasteboard_entry_md5_
285 forKey:kPasteboardEntryMD5Key]; 241 forKey:kPasteboardEntryMD5Key];
286 [shared_user_defaults_ setObject:last_displayed_pasteboard_entry_
287 forKey:kLastDisplayedPasteboardEntryKey];
288 } 242 }
243
244 base::TimeDelta ClipboardRecentContentIOS::Uptime() const {
245 return base::SysInfo::Uptime();
246 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698