OLD | NEW |
---|---|
1 // Copyright 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2012 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 "ios/chrome/browser/sessions/session_service.h" | 5 #import "ios/chrome/browser/sessions/session_service_ios.h" |
6 | 6 |
7 #import <UIKit/UIKit.h> | 7 #import <UIKit/UIKit.h> |
8 | 8 |
9 #include "base/critical_closure.h" | |
9 #include "base/files/file_path.h" | 10 #include "base/files/file_path.h" |
10 #include "base/location.h" | 11 #include "base/location.h" |
11 #include "base/logging.h" | 12 #include "base/logging.h" |
12 #include "base/mac/bind_objc_block.h" | 13 #import "base/mac/bind_objc_block.h" |
13 #include "base/mac/foundation_util.h" | |
14 #include "base/memory/ref_counted.h" | 14 #include "base/memory/ref_counted.h" |
15 #include "base/sequenced_task_runner.h" | 15 #include "base/sequenced_task_runner.h" |
16 #include "base/strings/sys_string_conversions.h" | 16 #include "base/strings/sys_string_conversions.h" |
17 #include "base/synchronization/lock.h" | |
18 #include "base/threading/sequenced_worker_pool.h" | 17 #include "base/threading/sequenced_worker_pool.h" |
19 #include "base/threading/thread_restrictions.h" | 18 #include "base/threading/thread_restrictions.h" |
20 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" | 19 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
21 #import "ios/chrome/browser/sessions/session_window_ios.h" | 20 #import "ios/chrome/browser/sessions/session_window_ios.h" |
22 #import "ios/web/public/crw_navigation_item_storage.h" | 21 #import "ios/web/public/crw_navigation_item_storage.h" |
23 #import "ios/web/public/crw_session_certificate_policy_cache_storage.h" | 22 #import "ios/web/public/crw_session_certificate_policy_cache_storage.h" |
24 #import "ios/web/public/crw_session_storage.h" | 23 #import "ios/web/public/crw_session_storage.h" |
25 #include "ios/web/public/web_thread.h" | 24 #include "ios/web/public/web_thread.h" |
26 | 25 |
26 #if !defined(__has_feature) || !__has_feature(objc_arc) | |
27 #error "This file requires ARC support." | |
28 #endif | |
29 | |
27 // When C++ exceptions are disabled, the C++ library defines |try| and | 30 // When C++ exceptions are disabled, the C++ library defines |try| and |
28 // |catch| so as to allow exception-expecting C++ code to build properly when | 31 // |catch| so as to allow exception-expecting C++ code to build properly when |
29 // language support for exceptions is not present. These macros interfere | 32 // language support for exceptions is not present. These macros interfere |
30 // with the use of |@try| and |@catch| in Objective-C files such as this one. | 33 // with the use of |@try| and |@catch| in Objective-C files such as this one. |
31 // Undefine these macros here, after everything has been #included, since | 34 // Undefine these macros here, after everything has been #included, since |
32 // there will be no C++ uses and only Objective-C uses from this point on. | 35 // there will be no C++ uses and only Objective-C uses from this point on. |
33 #undef try | 36 #undef try |
34 #undef catch | 37 #undef catch |
35 | 38 |
36 const NSTimeInterval kSaveDelay = 2.5; // Value taken from Desktop Chrome. | 39 const NSTimeInterval kSaveDelay = 2.5; // Value taken from Desktop Chrome. |
37 | 40 |
38 @interface SessionWindowUnarchiver () | 41 @implementation NSKeyedUnarchiver (CrLegacySessionCompatibility) |
39 | |
40 // Register compatibility aliases to support loading serialised sessions | |
41 // informations when the serialised classes are renamed. | |
42 + (void)registerCompatibilityAliases; | |
43 | |
44 @end | |
45 | |
46 @implementation SessionWindowUnarchiver | |
47 | |
48 @synthesize browserState = _browserState; | |
49 | |
50 - (instancetype)initForReadingWithData:(NSData*)data | |
51 browserState:(ios::ChromeBrowserState*)browserState { | |
52 if (self = [super initForReadingWithData:data]) { | |
53 _browserState = browserState; | |
54 } | |
55 return self; | |
56 } | |
57 | |
58 - (instancetype)initForReadingWithData:(NSData*)data { | |
59 return [self initForReadingWithData:data browserState:nullptr]; | |
60 } | |
61 | |
62 + (void)initialize { | |
63 [super initialize]; | |
64 [self registerCompatibilityAliases]; | |
65 } | |
66 | 42 |
67 // When adding a new compatibility alias here, create a new crbug to track its | 43 // When adding a new compatibility alias here, create a new crbug to track its |
68 // removal and mark it with a release at least one year after the introduction | 44 // removal and mark it with a release at least one year after the introduction |
69 // of the alias. | 45 // of the alias. |
70 + (void)registerCompatibilityAliases { | 46 - (void)cr_registerCompatibilityAliases { |
71 // TODO(crbug.com/661633): those aliases where introduced between M57 and | 47 // TODO(crbug.com/661633): those aliases where introduced between M57 and |
72 // M58, so remove them after M67 has shipped to stable. | 48 // M58, so remove them after M67 has shipped to stable. |
73 [SessionWindowUnarchiver | 49 [self setClass:[CRWSessionCertificatePolicyCacheStorage class] |
74 setClass:[CRWSessionCertificatePolicyCacheStorage class] | |
75 forClassName:@"SessionCertificatePolicyManager"]; | 50 forClassName:@"SessionCertificatePolicyManager"]; |
76 [SessionWindowUnarchiver setClass:[CRWSessionStorage class] | 51 [self setClass:[CRWSessionStorage class] forClassName:@"SessionController"]; |
77 forClassName:@"SessionController"]; | 52 [self setClass:[CRWSessionStorage class] |
78 [SessionWindowUnarchiver setClass:[CRWSessionStorage class] | 53 forClassName:@"CRWSessionController"]; |
79 forClassName:@"CRWSessionController"]; | 54 [self setClass:[CRWNavigationItemStorage class] forClassName:@"SessionEntry"]; |
80 [SessionWindowUnarchiver setClass:[CRWNavigationItemStorage class] | 55 [self setClass:[CRWNavigationItemStorage class] |
81 forClassName:@"SessionEntry"]; | 56 forClassName:@"CRWSessionEntry"]; |
82 [SessionWindowUnarchiver setClass:[CRWNavigationItemStorage class] | 57 [self setClass:[SessionWindowIOS class] forClassName:@"SessionWindow"]; |
83 forClassName:@"CRWSessionEntry"]; | |
84 [SessionWindowUnarchiver setClass:[SessionWindowIOS class] | |
85 forClassName:@"SessionWindow"]; | |
86 | 58 |
87 // TODO(crbug.com/661633): this alias was introduced between M58 and M59, so | 59 // TODO(crbug.com/661633): this alias was introduced between M58 and M59, so |
88 // remove it after M68 has shipped to stable. | 60 // remove it after M68 has shipped to stable. |
89 [SessionWindowUnarchiver setClass:[CRWSessionStorage class] | 61 [self setClass:[CRWSessionStorage class] |
90 forClassName:@"CRWNavigationManagerStorage"]; | 62 forClassName:@"CRWNavigationManagerStorage"]; |
91 [SessionWindowUnarchiver | 63 [self setClass:[CRWSessionCertificatePolicyCacheStorage class] |
92 setClass:[CRWSessionCertificatePolicyCacheStorage class] | |
93 forClassName:@"CRWSessionCertificatePolicyManager"]; | 64 forClassName:@"CRWSessionCertificatePolicyManager"]; |
94 } | 65 } |
95 | 66 |
96 @end | 67 @end |
97 | 68 |
98 @interface SessionServiceIOS () { | 69 @interface SessionServiceIOS () { |
99 // The SequencedTaskRunner on which File IO operations are performed. | 70 // The SequencedTaskRunner on which File IO operations are performed. |
100 scoped_refptr<base::SequencedTaskRunner> _taskRunner; | 71 scoped_refptr<base::SequencedTaskRunner> _taskRunner; |
101 | 72 |
102 // Maps save directories to the pending SessionWindow for the delayed | 73 // Maps save directories to the pending SessionWindow for the delayed |
103 // save behavior. | 74 // save behavior. |
104 base::scoped_nsobject<NSMutableDictionary> _pendingWindows; | 75 NSMutableDictionary<NSString*, SessionWindowIOS*>* _pendingWindows; |
105 } | 76 } |
106 | 77 |
107 // Saves the session corresponding to |directory| on the background | 78 // Saves the session corresponding to |directory| on the background |
108 // task runner |_taskRunner|. | 79 // task runner |_taskRunner|. |
109 - (void)performSaveToDirectoryInBackground:(NSString*)directory; | 80 - (void)performSaveToDirectoryInBackground:(NSString*)directory; |
110 @end | 81 @end |
111 | 82 |
112 @implementation SessionServiceIOS | 83 @implementation SessionServiceIOS |
113 | 84 |
114 + (SessionServiceIOS*)sharedService { | 85 + (SessionServiceIOS*)sharedService { |
115 static SessionServiceIOS* singleton = nil; | 86 static SessionServiceIOS* singleton = nil; |
116 if (!singleton) { | 87 if (!singleton) { |
117 singleton = [[[self class] alloc] init]; | 88 singleton = [[[self class] alloc] init]; |
118 } | 89 } |
119 return singleton; | 90 return singleton; |
120 } | 91 } |
121 | 92 |
122 - (instancetype)init { | 93 - (instancetype)init { |
123 self = [super init]; | 94 self = [super init]; |
124 if (self) { | 95 if (self) { |
125 _pendingWindows.reset([[NSMutableDictionary alloc] init]); | 96 _pendingWindows = [NSMutableDictionary dictionary]; |
126 auto* pool = web::WebThread::GetBlockingPool(); | 97 base::SequencedWorkerPool* pool = web::WebThread::GetBlockingPool(); |
127 _taskRunner = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); | 98 _taskRunner = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); |
128 } | 99 } |
129 return self; | 100 return self; |
130 } | 101 } |
131 | 102 |
132 // Returns the path of the session file. | 103 // Returns the path of the session file. |
133 - (NSString*)sessionFilePathForDirectory:(NSString*)directory { | 104 - (NSString*)sessionFilePathForDirectory:(NSString*)directory { |
134 return [directory stringByAppendingPathComponent:@"session.plist"]; | 105 return [directory stringByAppendingPathComponent:@"session.plist"]; |
135 } | 106 } |
136 | 107 |
137 // Do the work of saving on a background thread. Assumes |window| is threadsafe. | 108 // Do the work of saving on a background thread. Assumes |window| is threadsafe. |
138 - (void)performSaveToDirectoryInBackground:(NSString*)directory { | 109 - (void)performSaveToDirectoryInBackground:(NSString*)directory { |
139 DCHECK(directory); | 110 DCHECK(directory); |
140 DCHECK([_pendingWindows objectForKey:directory] != nil); | 111 DCHECK([_pendingWindows objectForKey:directory] != nil); |
141 UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] | |
marq (ping after 24h)
2017/04/06 11:58:16
Was this code just not doing anything useful? I'm
sdefresne
2017/04/06 14:57:01
This is replaced by CriticalClosure (look in scope
| |
142 beginBackgroundTaskWithExpirationHandler:^{ | |
143 }]; | |
144 DCHECK(identifier != UIBackgroundTaskInvalid); | |
145 | 112 |
146 // Put the window into a local var so it can be retained for the block, yet | 113 // Put the window into a local var so it can be retained for the block, yet |
147 // we can remove it from the dictionary to allow queuing another save. | 114 // we can remove it from the dictionary to allow queuing another save. |
148 SessionWindowIOS* localWindow = | 115 SessionWindowIOS* localWindow = [_pendingWindows objectForKey:directory]; |
149 [[_pendingWindows objectForKey:directory] retain]; | |
150 [_pendingWindows removeObjectForKey:directory]; | 116 [_pendingWindows removeObjectForKey:directory]; |
151 | 117 |
152 _taskRunner->PostTask( | 118 _taskRunner->PostTask( |
153 FROM_HERE, base::BindBlock(^{ | 119 FROM_HERE, base::MakeCriticalClosure(base::BindBlockArc(^{ |
154 @try { | 120 @try { |
155 [self performSaveWindow:localWindow toDirectory:directory]; | 121 [self performSaveWindow:localWindow toDirectory:directory]; |
156 } @catch (NSException* e) { | 122 } @catch (NSException* e) { |
157 // Do nothing. | 123 // Do nothing. |
158 } | 124 } |
159 [localWindow release]; | 125 }))); |
160 [[UIApplication sharedApplication] endBackgroundTask:identifier]; | |
161 })); | |
162 } | 126 } |
163 | 127 |
164 // Saves a SessionWindowIOS in a given directory. In case the directory doesn't | 128 // Saves a SessionWindowIOS in a given directory. In case the directory doesn't |
165 // exists it will be automatically created. | 129 // exists it will be automatically created. |
166 - (void)performSaveWindow:(SessionWindowIOS*)window | 130 - (void)performSaveWindow:(SessionWindowIOS*)window |
167 toDirectory:(NSString*)directory { | 131 toDirectory:(NSString*)directory { |
168 base::ThreadRestrictions::AssertIOAllowed(); | 132 base::ThreadRestrictions::AssertIOAllowed(); |
169 NSFileManager* fileManager = [NSFileManager defaultManager]; | 133 NSFileManager* fileManager = [NSFileManager defaultManager]; |
170 BOOL isDir; | 134 BOOL isDir; |
171 if (![fileManager fileExistsAtPath:directory isDirectory:&isDir]) { | 135 if (![fileManager fileExistsAtPath:directory isDirectory:&isDir]) { |
172 NSError* error = nil; | 136 NSError* error = nil; |
173 BOOL result = [fileManager createDirectoryAtPath:directory | 137 BOOL result = [fileManager createDirectoryAtPath:directory |
174 withIntermediateDirectories:YES | 138 withIntermediateDirectories:YES |
175 attributes:nil | 139 attributes:nil |
176 error:&error]; | 140 error:&error]; |
177 DCHECK(result); | 141 DCHECK(result); |
178 if (!result) { | 142 if (!result) { |
179 DLOG(ERROR) << "Error creating destination dir: " | 143 DLOG(ERROR) << "Error creating destination directory: " |
144 << base::SysNSStringToUTF8(directory) << ": " | |
180 << base::SysNSStringToUTF8([error description]); | 145 << base::SysNSStringToUTF8([error description]); |
181 return; | 146 return; |
182 } | 147 } |
183 } else { | 148 } else { |
184 DCHECK(isDir); | 149 DCHECK(isDir); |
185 if (!isDir) { | 150 if (!isDir) { |
186 DLOG(ERROR) << "Destination Directory already exists and is a file"; | 151 DLOG(ERROR) << "Error creating destination directory: " |
152 << base::SysNSStringToUTF8(directory) << ": " | |
153 << "file exists and is not a directory."; | |
187 return; | 154 return; |
188 } | 155 } |
189 } | 156 } |
190 | 157 |
191 NSString* filename = [self sessionFilePathForDirectory:directory]; | 158 NSString* filename = [self sessionFilePathForDirectory:directory]; |
192 if (filename) { | 159 if (filename) { |
193 BOOL result = [NSKeyedArchiver archiveRootObject:window toFile:filename]; | 160 BOOL result = [NSKeyedArchiver archiveRootObject:window toFile:filename]; |
194 DCHECK(result); | 161 DCHECK(result); |
195 if (!result) | 162 if (!result) { |
196 DLOG(ERROR) << "Error writing session file to " << filename; | 163 DLOG(ERROR) << "Error writing session file to " << filename; |
164 return; | |
165 } | |
166 | |
197 // Encrypt the session file (mostly for Incognito, but can't hurt to | 167 // Encrypt the session file (mostly for Incognito, but can't hurt to |
198 // always do it). | 168 // always do it). |
199 NSDictionary* attributeDict = | 169 NSDictionary* attributeDict = |
marq (ping after 24h)
2017/04/06 11:58:16
Since you're here, make this
NSDictionary<NSFile
sdefresne
2017/04/06 14:57:01
Inlined in the method call.
| |
200 [NSDictionary dictionaryWithObject:NSFileProtectionComplete | 170 [NSDictionary dictionaryWithObject:NSFileProtectionComplete |
201 forKey:NSFileProtectionKey]; | 171 forKey:NSFileProtectionKey]; |
202 NSError* error = nil; | 172 NSError* error = nil; |
203 BOOL success = [[NSFileManager defaultManager] setAttributes:attributeDict | 173 BOOL success = [[NSFileManager defaultManager] setAttributes:attributeDict |
204 ofItemAtPath:filename | 174 ofItemAtPath:filename |
205 error:&error]; | 175 error:&error]; |
206 if (!success) { | 176 if (!success) { |
207 DLOG(ERROR) << "Error encrypting session file" | 177 DLOG(ERROR) << "Error encrypting session file: " |
178 << base::SysNSStringToUTF8(filename) << ": " | |
208 << base::SysNSStringToUTF8([error description]); | 179 << base::SysNSStringToUTF8([error description]); |
209 } | 180 } |
210 } | 181 } |
211 } | 182 } |
212 | 183 |
213 - (void)saveWindow:(SessionWindowIOS*)window | 184 - (void)saveWindow:(SessionWindowIOS*)window |
214 forBrowserState:(ios::ChromeBrowserState*)browserState | 185 forBrowserState:(ios::ChromeBrowserState*)browserState |
215 immediately:(BOOL)immediately { | 186 immediately:(BOOL)immediately { |
216 NSString* stashPath = | 187 NSString* stashPath = |
217 base::SysUTF8ToNSString(browserState->GetStatePath().value()); | 188 base::SysUTF8ToNSString(browserState->GetStatePath().value()); |
218 // If there's an existing session window for |stashPath|, clear it before it's | 189 BOOL hadPendingSession = [_pendingWindows objectForKey:stashPath] != nil; |
219 // replaced. | |
220 SessionWindowIOS* pendingSession = base::mac::ObjCCast<SessionWindowIOS>( | |
221 [_pendingWindows objectForKey:stashPath]); | |
222 [pendingSession clearSessions]; | |
223 // Set |window| as the pending save for |stashPath|. | |
224 [_pendingWindows setObject:window forKey:stashPath]; | 190 [_pendingWindows setObject:window forKey:stashPath]; |
225 if (immediately) { | 191 if (immediately) { |
226 [NSObject cancelPreviousPerformRequestsWithTarget:self]; | 192 [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
227 [self performSaveToDirectoryInBackground:stashPath]; | 193 [self performSaveToDirectoryInBackground:stashPath]; |
228 } else if (!pendingSession) { | 194 } else if (!hadPendingSession) { |
229 // If there wasn't previously a delayed save pending for |stashPath|, | 195 // If there wasn't previously a delayed save pending for |stashPath|, |
230 // enqueue one now. | 196 // enqueue one now. |
231 [self performSelector:@selector(performSaveToDirectoryInBackground:) | 197 [self performSelector:@selector(performSaveToDirectoryInBackground:) |
232 withObject:stashPath | 198 withObject:stashPath |
233 afterDelay:kSaveDelay]; | 199 afterDelay:kSaveDelay]; |
234 } | 200 } |
235 } | 201 } |
236 | 202 |
237 - (SessionWindowIOS*)loadWindowForBrowserState: | 203 - (SessionWindowIOS*)loadWindowForBrowserState: |
238 (ios::ChromeBrowserState*)browserState { | 204 (ios::ChromeBrowserState*)browserState { |
239 NSString* stashPath = | 205 NSString* stashPath = |
240 base::SysUTF8ToNSString(browserState->GetStatePath().value()); | 206 base::SysUTF8ToNSString(browserState->GetStatePath().value()); |
241 SessionWindowIOS* window = | 207 SessionWindowIOS* window = |
242 [self loadWindowFromPath:[self sessionFilePathForDirectory:stashPath] | 208 [self loadWindowFromPath:[self sessionFilePathForDirectory:stashPath]]; |
243 forBrowserState:browserState]; | |
244 return window; | 209 return window; |
245 } | 210 } |
246 | 211 |
247 - (SessionWindowIOS*)loadWindowFromPath:(NSString*)path | 212 - (SessionWindowIOS*)loadWindowFromPath:(NSString*)sessionPath { |
248 forBrowserState:(ios::ChromeBrowserState*)browserState { | |
249 SessionWindowIOS* window = nil; | |
250 @try { | 213 @try { |
251 NSData* data = [NSData dataWithContentsOfFile:path]; | 214 NSData* data = [NSData dataWithContentsOfFile:sessionPath]; |
252 if (data) { | 215 if (!data) |
253 base::scoped_nsobject<SessionWindowUnarchiver> unarchiver([ | 216 return nil; |
254 [SessionWindowUnarchiver alloc] initForReadingWithData:data | 217 |
255 browserState:browserState]); | 218 NSKeyedUnarchiver* unarchiver = |
256 window = [[[unarchiver decodeObjectForKey:@"root"] retain] autorelease]; | 219 [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; |
257 } | 220 |
221 // Register compatibility aliases to support legacy saved sessions. | |
222 [unarchiver cr_registerCompatibilityAliases]; | |
223 return [unarchiver decodeObjectForKey:@"root"]; | |
marq (ping after 24h)
2017/04/06 11:58:16
Extract @"root" into a constant?
sdefresne
2017/04/06 14:57:01
Done.
| |
258 } @catch (NSException* exception) { | 224 } @catch (NSException* exception) { |
259 DLOG(ERROR) << "Error loading session file."; | 225 DLOG(ERROR) << "Error loading session file: " |
226 << base::SysNSStringToUTF8(sessionPath) << ": " | |
227 << base::SysNSStringToUTF8([exception reason]); | |
228 return nil; | |
260 } | 229 } |
261 return window; | |
262 } | 230 } |
263 | 231 |
264 // Deletes the file containing the commands for the last session in the given | 232 // Deletes the file containing the commands for the last session in the given |
265 // browserState directory. | 233 // browserState directory. |
266 - (void)deleteLastSession:(NSString*)directory { | 234 - (void)deleteLastSession:(NSString*)directory { |
267 NSString* sessionFile = [self sessionFilePathForDirectory:directory]; | 235 NSString* sessionPath = [self sessionFilePathForDirectory:directory]; |
268 _taskRunner->PostTask( | 236 _taskRunner->PostTask( |
269 FROM_HERE, base::BindBlock(^{ | 237 FROM_HERE, base::BindBlockArc(^{ |
marq (ping after 24h)
2017/04/06 11:58:16
Should this use criticalClosure()?
sdefresne
2017/04/06 14:57:01
Not sure. This is called from the startup code to
marq (ping after 24h)
2017/04/06 16:15:56
I think the answer to my question is "no", then. ;
| |
270 base::ThreadRestrictions::AssertIOAllowed(); | 238 base::ThreadRestrictions::AssertIOAllowed(); |
271 NSFileManager* fileManager = [NSFileManager defaultManager]; | 239 NSFileManager* fileManager = [NSFileManager defaultManager]; |
272 if (![fileManager fileExistsAtPath:sessionFile]) | 240 if (![fileManager fileExistsAtPath:sessionPath]) |
273 return; | 241 return; |
274 if (![fileManager removeItemAtPath:sessionFile error:nil]) | 242 |
275 CHECK(false) << "Unable to delete session file."; | 243 NSError* error = nil; |
244 if (![fileManager removeItemAtPath:sessionPath error:nil]) | |
245 CHECK(false) << "Unable to delete session file: " | |
246 << base::SysNSStringToUTF8(sessionPath) << ": " | |
247 << base::SysNSStringToUTF8([error description]); | |
276 })); | 248 })); |
277 } | 249 } |
278 | 250 |
279 @end | 251 @end |
OLD | NEW |