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

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: sdefresne's 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_impl_ios.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 };
24 28
25 // Initialize the ApplicationDidBecomeActiveNotificationListenerBridge with 29 // Get the list of authorized schemes.
26 // |delegate| which must not be null. 30 NSSet* getAuthorizedSchemeList(const std::string& application_scheme) {
marq (ping after 24h) 2017/04/05 07:58:10 NSSet<NSString*>*
lody 2017/04/06 11:27:22 Done.
27 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate 31 NSMutableSet* schemes = [NSMutableSet set];
marq (ping after 24h) 2017/04/05 07:58:10 NSMutableSet<NSString*>*
lody 2017/04/06 11:27:22 Done.
28 NS_DESIGNATED_INITIALIZER; 32 for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) {
33 [schemes addObject:base::SysUTF8ToNSString(kAuthorizedSchemes[i])];
34 }
35 if (!application_scheme.empty()) {
36 [schemes addObject:base::SysUTF8ToNSString(application_scheme)];
37 }
29 38
30 - (instancetype)init NS_UNAVAILABLE; 39 return schemes;
marq (ping after 24h) 2017/04/05 07:58:10 nit: return [schemes copy] so the return value isn
lody 2017/04/06 11:27:22 Done.
40 }
41 }
31 42
43 @interface ClipboardRecentContentDelegateImpl
44 : NSObject<ClipboardRecentContentDelegate>
32 @end 45 @end
33 46
34 @implementation ApplicationDidBecomeActiveNotificationListenerBridge { 47 @implementation ClipboardRecentContentDelegateImpl
35 ClipboardRecentContentIOS* _delegate;
36 }
37 48
38 - (instancetype)init { 49 - (void)onClipboardChanged {
39 NOTREACHED(); 50 base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged"));
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 } 51 }
72 52
73 @end 53 @end
74 54
75 namespace { 55 ClipboardRecentContentIOS::ClipboardRecentContentIOS(
76 // Key used to store the pasteboard's current change count. If when resuming 56 const std::string& application_scheme,
77 // chrome the pasteboard's change count is different from the stored one, then 57 NSUserDefaults* group_user_defaults)
78 // it means that the pasteboard's content has changed. 58 : ClipboardRecentContentIOS([[ClipboardRecentContentImplIOS alloc]
79 NSString* kPasteboardChangeCountKey = @"PasteboardChangeCount"; 59 initWithAuthorizedSchemes:getAuthorizedSchemeList(application_scheme)
80 // Key used to store the last date at which it was detected that the pasteboard 60 userDefaults:group_user_defaults
81 // changed. It is used to evaluate the age of the pasteboard's content. 61 delegate:[[ClipboardRecentContentDelegateImpl alloc]
82 NSString* kPasteboardChangeDateKey = @"PasteboardChangeDate"; 62 init]]) {}
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 63
95 // Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|. 64 ClipboardRecentContentIOS::ClipboardRecentContentIOS(
96 // This value is used to detect pasteboard content change. Keeping only 4 bytes 65 ClipboardRecentContentImplIOS* implementation) {
97 // is a privacy requirement to introduce collision and allow deniability of 66 implementation_.reset(implementation);
98 // having copied a given string.
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 } 67 }
107 68
108 } // namespace
109
110 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) { 69 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) {
111 DCHECK(url); 70 DCHECK(url);
112 UpdateIfNeeded(); 71 NSURL* url_from_pasteboard = [implementation_ getRecentURLFromClipboard];
113 if (GetClipboardContentAge() > kMaximumAgeOfClipboard) { 72 GURL converted_url = net::GURLWithNSURL(url_from_pasteboard);
114 return false; 73 if (converted_url.is_valid()) {
115 } 74 *url = std::move(converted_url);
116
117 GURL url_from_pasteboard = URLFromPasteboard();
118 if (url_from_pasteboard.is_valid()) {
119 *url = url_from_pasteboard;
120 return true; 75 return true;
121 } 76 }
122 return false; 77 return false;
123 } 78 }
124 79
80 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {}
81
125 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const { 82 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
126 return base::TimeDelta::FromSeconds(static_cast<int64_t>( 83 return base::TimeDelta::FromSeconds(
127 -[last_pasteboard_change_date_ timeIntervalSinceNow])); 84 static_cast<int64_t>([implementation_ getClipboardContentAge]));
128 } 85 }
129 86
130 void ClipboardRecentContentIOS::SuppressClipboardContent() { 87 void ClipboardRecentContentIOS::SuppressClipboardContent() {
131 // User cleared the user data. The pasteboard entry must be removed from the 88 [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 } 89 }
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 }
235
236 void ClipboardRecentContentIOS::SaveToUserDefaults() {
237 [shared_user_defaults_ setInteger:last_pasteboard_change_count_
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 }
244
245 base::TimeDelta ClipboardRecentContentIOS::Uptime() const {
246 return base::SysInfo::Uptime();
247 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698