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

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: address comments 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_ios_impl.h"
19 #import "net/base/mac/url_conversions.h"
18 #include "url/gurl.h" 20 #include "url/gurl.h"
19 #include "url/url_constants.h" 21 #include "url/url_constants.h"
20 22
21 // Bridge that forwards UIApplicationDidBecomeActiveNotification notifications 23 namespace {
22 // to its delegate. 24 // Schemes accepted by the ClipboardRecentContentIOS.
23 @interface ApplicationDidBecomeActiveNotificationListenerBridge : NSObject 25 const char* kAuthorizedSchemes[] = {
26 url::kHttpScheme, url::kHttpsScheme, url::kDataScheme, url::kAboutScheme,
27 };
28 }
24 29
25 // Initialize the ApplicationDidBecomeActiveNotificationListenerBridge with 30 @interface ClipboardRecentContentMetricsDelegateImpl
26 // |delegate| which must not be null. 31 : NSObject<ClipboardRecentContentMetricsDelegate>
27 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate
28 NS_DESIGNATED_INITIALIZER;
29
30 - (instancetype)init NS_UNAVAILABLE;
31
32 @end 32 @end
33 33
34 @implementation ApplicationDidBecomeActiveNotificationListenerBridge { 34 @implementation ClipboardRecentContentMetricsDelegateImpl
35 ClipboardRecentContentIOS* _delegate;
36 }
37 35
38 - (instancetype)init { 36 - (void)onClipboardChanged {
39 NOTREACHED(); 37 base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged"));
sdefresne 2017/04/03 09:27:16 (outside of the CL) I think action that is recorde
40 return nil;
41 }
42
43 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate {
44 DCHECK(delegate);
45 self = [super init];
46 if (self) {
47 _delegate = delegate;
48 [[NSNotificationCenter defaultCenter]
49 addObserver:self
50 selector:@selector(didBecomeActive:)
51 name:UIApplicationDidBecomeActiveNotification
52 object:nil];
53 }
54 return self;
55 }
56
57 - (void)dealloc {
58 [[NSNotificationCenter defaultCenter] removeObserver:self];
59 [super dealloc];
60 }
61
62 - (void)didBecomeActive:(NSNotification*)notification {
63 if (_delegate) {
64 _delegate->LoadFromUserDefaults();
65 _delegate->UpdateIfNeeded();
66 }
67 }
68
69 - (void)disconnect {
70 _delegate = nullptr;
71 } 38 }
72 39
73 @end 40 @end
74 41
75 namespace { 42 ClipboardRecentContentIOS::ClipboardRecentContentIOS(
76 // Key used to store the pasteboard's current change count. If when resuming 43 const std::string& application_scheme,
77 // chrome the pasteboard's change count is different from the stored one, then 44 NSUserDefaults* group_user_defaults) {
78 // it means that the pasteboard's content has changed. 45 ClipboardRecentContentMetricsDelegateImpl* metricsDelegate =
sdefresne 2017/04/03 09:27:16 s/metricsDelegate/metrics_delegate/.
lody 2017/04/04 13:42:18 Done.
79 NSString* kPasteboardChangeCountKey = @"PasteboardChangeCount"; 46 [[ClipboardRecentContentMetricsDelegateImpl alloc] init];
80 // Key used to store the last date at which it was detected that the pasteboard
81 // changed. It is used to evaluate the age of the pasteboard's content.
82 NSString* kPasteboardChangeDateKey = @"PasteboardChangeDate";
83 // Key used to store the hash of the content of the pasteboard. Whenever the
84 // hash changed, the pasteboard content is considered to have changed.
85 NSString* kPasteboardEntryMD5Key = @"PasteboardEntryMD5";
86 base::TimeDelta kMaximumAgeOfClipboard = base::TimeDelta::FromHours(3);
87 // Schemes accepted by the ClipboardRecentContentIOS.
88 const char* kAuthorizedSchemes[] = {
89 url::kHttpScheme,
90 url::kHttpsScheme,
91 url::kDataScheme,
92 url::kAboutScheme,
93 };
94 47
95 // Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|. 48 implementation_.reset([[ClipboardRecentContentIOSImpl alloc]
96 // This value is used to detect pasteboard content change. Keeping only 4 bytes 49 initWithDelegate:metricsDelegate
97 // is a privacy requirement to introduce collision and allow deniability of 50 authorizedSchemes:GetAuthorizedSchemeList(application_scheme)
98 // having copied a given string. 51 userDefaults:group_user_defaults]);
99 NSData* WeakMD5FromNSString(NSString* string) {
100 unsigned char hash[CC_MD5_DIGEST_LENGTH];
101 const std::string clipboard = base::SysNSStringToUTF8(string);
102 const char* c_string = clipboard.c_str();
103 CC_MD5(c_string, strlen(c_string), hash);
104 NSData* data = [NSData dataWithBytes:hash length:4];
105 return data;
106 } 52 }
107 53
108 } // namespace
109
110 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) { 54 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) {
111 DCHECK(url); 55 DCHECK(url);
112 UpdateIfNeeded(); 56 NSURL* nsurl = [implementation_ getRecentURLFromClipboard];
sdefresne 2017/04/03 09:27:16 url_from_pasteboard
lody 2017/04/04 13:42:18 Done.
113 if (GetClipboardContentAge() > kMaximumAgeOfClipboard) { 57 if (nsurl != nil) {
sdefresne 2017/04/03 09:27:16 No need to test nsurl for nil, net::GURLWithNSURL(
lody 2017/04/04 13:42:18 Done.
114 return false; 58 GURL tempUrl = net::GURLWithNSURL(nsurl);
sdefresne 2017/04/03 09:27:16 NSURL* url_from_pasteboard = [implementation_ getR
lody 2017/04/04 13:42:18 Done.
115 } 59 if (tempUrl.is_valid()) {
116 60 *url = std::move(tempUrl);
117 GURL url_from_pasteboard = URLFromPasteboard(); 61 return true;
118 if (url_from_pasteboard.is_valid()) { 62 }
119 *url = url_from_pasteboard;
120 return true;
121 } 63 }
122 return false; 64 return false;
123 } 65 }
124 66
67 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {}
68
125 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const { 69 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
126 return base::TimeDelta::FromSeconds(static_cast<int64_t>( 70 return base::TimeDelta::FromSeconds(
127 -[last_pasteboard_change_date_ timeIntervalSinceNow])); 71 static_cast<int64_t>([implementation_ getClipboardContentAge]));
128 } 72 }
129 73
130 void ClipboardRecentContentIOS::SuppressClipboardContent() { 74 void ClipboardRecentContentIOS::SuppressClipboardContent() {
131 // User cleared the user data. The pasteboard entry must be removed from the 75 [implementation_ suppressClipboardContent];
132 // omnibox list. Force entry expiration by setting copy date to 1970.
133 last_pasteboard_change_date_.reset(
134 [[NSDate alloc] initWithTimeIntervalSince1970:0]);
135 SaveToUserDefaults();
136 }
137
138 void ClipboardRecentContentIOS::UpdateIfNeeded() {
139 if (!HasPasteboardChanged())
140 return;
141
142 base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged"));
143
144 GURL url_from_pasteboard = URLFromPasteboard();
145 last_pasteboard_change_date_.reset([[NSDate date] retain]);
146 last_pasteboard_change_count_ = [UIPasteboard generalPasteboard].changeCount;
147 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string];
148 if (!pasteboard_string) {
149 pasteboard_string = @"";
150 }
151 NSData* MD5 = WeakMD5FromNSString(pasteboard_string);
152 last_pasteboard_entry_md5_.reset([MD5 retain]);
153 SaveToUserDefaults();
154 }
155
156 ClipboardRecentContentIOS::ClipboardRecentContentIOS(
157 const std::string& application_scheme,
158 NSUserDefaults* group_user_defaults)
159 : application_scheme_(application_scheme),
160 shared_user_defaults_([group_user_defaults retain]) {
161 last_pasteboard_change_count_ = NSIntegerMax;
162 LoadFromUserDefaults();
163
164 UpdateIfNeeded();
165
166 // Makes sure |last_pasteboard_change_count_| was properly initialized.
167 DCHECK_NE(last_pasteboard_change_count_, NSIntegerMax);
168 notification_bridge_.reset(
169 [[ApplicationDidBecomeActiveNotificationListenerBridge alloc]
170 initWithDelegate:this]);
171 }
172
173 bool ClipboardRecentContentIOS::HasPasteboardChanged() const {
174 // If |MD5Changed|, we know for sure there has been at least one pasteboard
175 // copy since last time it was checked.
176 // If the pasteboard content is still the same but the device was not
177 // rebooted, the change count can be checked to see if it changed.
178 // Note: due to a mismatch between the actual behavior and documentation, and
179 // lack of consistency on different reboot scenarios, the change count cannot
180 // be checked after a reboot.
181 // See radar://21833556 for more information.
182 NSInteger change_count = [UIPasteboard generalPasteboard].changeCount;
183 bool change_count_changed = change_count != last_pasteboard_change_count_;
184
185 bool not_rebooted = Uptime() > GetClipboardContentAge();
186 if (not_rebooted)
187 return change_count_changed;
188
189 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string];
190 if (!pasteboard_string) {
191 pasteboard_string = @"";
192 }
193 NSData* md5 = WeakMD5FromNSString(pasteboard_string);
194 BOOL md5_changed = ![md5 isEqualToData:last_pasteboard_entry_md5_];
195
196 return md5_changed;
197 }
198
199 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {
200 [notification_bridge_ disconnect];
201 }
202
203 GURL ClipboardRecentContentIOS::URLFromPasteboard() {
204 NSString* clipboard_string = [[UIPasteboard generalPasteboard] string];
205 if (!clipboard_string) {
206 return GURL::EmptyGURL();
207 }
208 const std::string clipboard = base::SysNSStringToUTF8(clipboard_string);
209 GURL gurl = GURL(clipboard);
210 if (gurl.is_valid()) {
211 for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) {
212 if (gurl.SchemeIs(kAuthorizedSchemes[i])) {
213 return gurl;
214 }
215 }
216 if (!application_scheme_.empty() &&
217 gurl.SchemeIs(application_scheme_.c_str())) {
218 return gurl;
219 }
220 }
221 return GURL::EmptyGURL();
222 }
223
224 void ClipboardRecentContentIOS::LoadFromUserDefaults() {
225 last_pasteboard_change_count_ =
226 [shared_user_defaults_ integerForKey:kPasteboardChangeCountKey];
227 last_pasteboard_change_date_.reset(
228 [[shared_user_defaults_ objectForKey:kPasteboardChangeDateKey] retain]);
229 last_pasteboard_entry_md5_.reset(
230 [[shared_user_defaults_ objectForKey:kPasteboardEntryMD5Key] retain]);
231
232 DCHECK(!last_pasteboard_change_date_ ||
233 [last_pasteboard_change_date_ isKindOfClass:[NSDate class]]);
234 } 76 }
235 77
236 void ClipboardRecentContentIOS::SaveToUserDefaults() { 78 void ClipboardRecentContentIOS::SaveToUserDefaults() {
237 [shared_user_defaults_ setInteger:last_pasteboard_change_count_ 79 [implementation_ saveToUserDefaults];
238 forKey:kPasteboardChangeCountKey];
239 [shared_user_defaults_ setObject:last_pasteboard_change_date_
240 forKey:kPasteboardChangeDateKey];
241 [shared_user_defaults_ setObject:last_pasteboard_entry_md5_
242 forKey:kPasteboardEntryMD5Key];
243 } 80 }
244 81
245 base::TimeDelta ClipboardRecentContentIOS::Uptime() const { 82 void ClipboardRecentContentIOS::SetLastPasteboardChangeDate(NSDate* date) {
246 return base::SysInfo::Uptime(); 83 [implementation_ setLastPasteboardChangeDate:date];
247 } 84 }
85
86 NSArray* ClipboardRecentContentIOS::GetAuthorizedSchemeList(
sdefresne 2017/04/03 09:27:16 This can be a free function, no need to export thi
lody 2017/04/04 13:42:18 Done.
87 const std::string& application_scheme) {
88 NSMutableArray* schemes = [NSMutableArray array];
89 for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) {
90 [schemes addObject:base::SysUTF8ToNSString(kAuthorizedSchemes[i])];
91 }
92 if (!application_scheme.empty()) {
93 [schemes addObject:base::SysUTF8ToNSString(application_scheme)];
94 }
95
96 return schemes;
97 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698