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 namespace { |
37 | 40 const NSTimeInterval kSaveDelay = 2.5; // Value taken from Desktop Chrome. |
38 @interface SessionWindowUnarchiver () | 41 NSString* const kRootObjectKey = @"root"; // Key for the root object. |
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 } | 42 } |
57 | 43 |
58 - (instancetype)initForReadingWithData:(NSData*)data { | 44 @implementation NSKeyedUnarchiver (CrLegacySessionCompatibility) |
59 return [self initForReadingWithData:data browserState:nullptr]; | |
60 } | |
61 | |
62 + (void)initialize { | |
63 [super initialize]; | |
64 [self registerCompatibilityAliases]; | |
65 } | |
66 | 45 |
67 // When adding a new compatibility alias here, create a new crbug to track its | 46 // 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 | 47 // removal and mark it with a release at least one year after the introduction |
69 // of the alias. | 48 // of the alias. |
70 + (void)registerCompatibilityAliases { | 49 - (void)cr_registerCompatibilityAliases { |
71 // TODO(crbug.com/661633): those aliases where introduced between M57 and | 50 // TODO(crbug.com/661633): those aliases where introduced between M57 and |
72 // M58, so remove them after M67 has shipped to stable. | 51 // M58, so remove them after M67 has shipped to stable. |
73 [SessionWindowUnarchiver | 52 [self setClass:[CRWSessionCertificatePolicyCacheStorage class] |
74 setClass:[CRWSessionCertificatePolicyCacheStorage class] | |
75 forClassName:@"SessionCertificatePolicyManager"]; | 53 forClassName:@"SessionCertificatePolicyManager"]; |
76 [SessionWindowUnarchiver setClass:[CRWSessionStorage class] | 54 [self setClass:[CRWSessionStorage class] forClassName:@"SessionController"]; |
77 forClassName:@"SessionController"]; | 55 [self setClass:[CRWSessionStorage class] |
78 [SessionWindowUnarchiver setClass:[CRWSessionStorage class] | 56 forClassName:@"CRWSessionController"]; |
79 forClassName:@"CRWSessionController"]; | 57 [self setClass:[CRWNavigationItemStorage class] forClassName:@"SessionEntry"]; |
80 [SessionWindowUnarchiver setClass:[CRWNavigationItemStorage class] | 58 [self setClass:[CRWNavigationItemStorage class] |
81 forClassName:@"SessionEntry"]; | 59 forClassName:@"CRWSessionEntry"]; |
82 [SessionWindowUnarchiver setClass:[CRWNavigationItemStorage class] | 60 [self setClass:[SessionWindowIOS class] forClassName:@"SessionWindow"]; |
83 forClassName:@"CRWSessionEntry"]; | |
84 [SessionWindowUnarchiver setClass:[SessionWindowIOS class] | |
85 forClassName:@"SessionWindow"]; | |
86 | 61 |
87 // TODO(crbug.com/661633): this alias was introduced between M58 and M59, so | 62 // TODO(crbug.com/661633): this alias was introduced between M58 and M59, so |
88 // remove it after M68 has shipped to stable. | 63 // remove it after M68 has shipped to stable. |
89 [SessionWindowUnarchiver setClass:[CRWSessionStorage class] | 64 [self setClass:[CRWSessionStorage class] |
90 forClassName:@"CRWNavigationManagerStorage"]; | 65 forClassName:@"CRWNavigationManagerStorage"]; |
91 [SessionWindowUnarchiver | 66 [self setClass:[CRWSessionCertificatePolicyCacheStorage class] |
92 setClass:[CRWSessionCertificatePolicyCacheStorage class] | |
93 forClassName:@"CRWSessionCertificatePolicyManager"]; | 67 forClassName:@"CRWSessionCertificatePolicyManager"]; |
94 } | 68 } |
95 | 69 |
96 @end | 70 @end |
97 | 71 |
98 @interface SessionServiceIOS () { | 72 @interface SessionServiceIOS () { |
99 // The SequencedTaskRunner on which File IO operations are performed. | 73 // The SequencedTaskRunner on which File IO operations are performed. |
100 scoped_refptr<base::SequencedTaskRunner> _taskRunner; | 74 scoped_refptr<base::SequencedTaskRunner> _taskRunner; |
101 | 75 |
102 // Maps save directories to the pending SessionWindow for the delayed | 76 // Maps save directories to the pending SessionWindow for the delayed |
103 // save behavior. | 77 // save behavior. |
104 base::scoped_nsobject<NSMutableDictionary> _pendingWindows; | 78 NSMutableDictionary<NSString*, SessionWindowIOS*>* _pendingWindows; |
105 } | 79 } |
106 | 80 |
107 // Saves the session corresponding to |directory| on the background | 81 // Saves the session corresponding to |directory| on the background |
108 // task runner |_taskRunner|. | 82 // task runner |_taskRunner|. |
109 - (void)performSaveToDirectoryInBackground:(NSString*)directory; | 83 - (void)performSaveToDirectoryInBackground:(NSString*)directory; |
110 @end | 84 @end |
111 | 85 |
112 @implementation SessionServiceIOS | 86 @implementation SessionServiceIOS |
113 | 87 |
114 + (SessionServiceIOS*)sharedService { | 88 + (SessionServiceIOS*)sharedService { |
115 static SessionServiceIOS* singleton = nil; | 89 static SessionServiceIOS* singleton = nil; |
116 if (!singleton) { | 90 if (!singleton) { |
117 singleton = [[[self class] alloc] init]; | 91 singleton = [[[self class] alloc] init]; |
118 } | 92 } |
119 return singleton; | 93 return singleton; |
120 } | 94 } |
121 | 95 |
122 - (instancetype)init { | 96 - (instancetype)init { |
123 self = [super init]; | 97 self = [super init]; |
124 if (self) { | 98 if (self) { |
125 _pendingWindows.reset([[NSMutableDictionary alloc] init]); | 99 _pendingWindows = [NSMutableDictionary dictionary]; |
126 auto* pool = web::WebThread::GetBlockingPool(); | 100 base::SequencedWorkerPool* pool = web::WebThread::GetBlockingPool(); |
127 _taskRunner = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); | 101 _taskRunner = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); |
128 } | 102 } |
129 return self; | 103 return self; |
130 } | 104 } |
131 | 105 |
132 // Returns the path of the session file. | 106 // Returns the path of the session file. |
133 - (NSString*)sessionFilePathForDirectory:(NSString*)directory { | 107 - (NSString*)sessionFilePathForDirectory:(NSString*)directory { |
134 return [directory stringByAppendingPathComponent:@"session.plist"]; | 108 return [directory stringByAppendingPathComponent:@"session.plist"]; |
135 } | 109 } |
136 | 110 |
137 // Do the work of saving on a background thread. Assumes |window| is threadsafe. | 111 // Do the work of saving on a background thread. Assumes |window| is threadsafe. |
138 - (void)performSaveToDirectoryInBackground:(NSString*)directory { | 112 - (void)performSaveToDirectoryInBackground:(NSString*)directory { |
139 DCHECK(directory); | 113 DCHECK(directory); |
140 DCHECK([_pendingWindows objectForKey:directory] != nil); | 114 DCHECK([_pendingWindows objectForKey:directory] != nil); |
141 UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] | |
142 beginBackgroundTaskWithExpirationHandler:^{ | |
143 }]; | |
144 DCHECK(identifier != UIBackgroundTaskInvalid); | |
145 | 115 |
146 // Put the window into a local var so it can be retained for the block, yet | 116 // 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. | 117 // we can remove it from the dictionary to allow queuing another save. |
148 SessionWindowIOS* localWindow = | 118 SessionWindowIOS* localWindow = [_pendingWindows objectForKey:directory]; |
149 [[_pendingWindows objectForKey:directory] retain]; | |
150 [_pendingWindows removeObjectForKey:directory]; | 119 [_pendingWindows removeObjectForKey:directory]; |
151 | 120 |
152 _taskRunner->PostTask( | 121 _taskRunner->PostTask( |
153 FROM_HERE, base::BindBlock(^{ | 122 FROM_HERE, base::MakeCriticalClosure(base::BindBlockArc(^{ |
154 @try { | 123 @try { |
155 [self performSaveWindow:localWindow toDirectory:directory]; | 124 [self performSaveWindow:localWindow toDirectory:directory]; |
156 } @catch (NSException* e) { | 125 } @catch (NSException* e) { |
157 // Do nothing. | 126 // Do nothing. |
158 } | 127 } |
159 [localWindow release]; | 128 }))); |
160 [[UIApplication sharedApplication] endBackgroundTask:identifier]; | |
161 })); | |
162 } | 129 } |
163 | 130 |
164 // Saves a SessionWindowIOS in a given directory. In case the directory doesn't | 131 // Saves a SessionWindowIOS in a given directory. In case the directory doesn't |
165 // exists it will be automatically created. | 132 // exists it will be automatically created. |
166 - (void)performSaveWindow:(SessionWindowIOS*)window | 133 - (void)performSaveWindow:(SessionWindowIOS*)window |
167 toDirectory:(NSString*)directory { | 134 toDirectory:(NSString*)directory { |
168 base::ThreadRestrictions::AssertIOAllowed(); | 135 base::ThreadRestrictions::AssertIOAllowed(); |
169 NSFileManager* fileManager = [NSFileManager defaultManager]; | 136 NSFileManager* fileManager = [NSFileManager defaultManager]; |
170 BOOL isDir; | 137 BOOL isDir; |
171 if (![fileManager fileExistsAtPath:directory isDirectory:&isDir]) { | 138 if (![fileManager fileExistsAtPath:directory isDirectory:&isDir]) { |
172 NSError* error = nil; | 139 NSError* error = nil; |
173 BOOL result = [fileManager createDirectoryAtPath:directory | 140 BOOL result = [fileManager createDirectoryAtPath:directory |
174 withIntermediateDirectories:YES | 141 withIntermediateDirectories:YES |
175 attributes:nil | 142 attributes:nil |
176 error:&error]; | 143 error:&error]; |
177 DCHECK(result); | 144 DCHECK(result); |
178 if (!result) { | 145 if (!result) { |
179 DLOG(ERROR) << "Error creating destination dir: " | 146 DLOG(ERROR) << "Error creating destination directory: " |
| 147 << base::SysNSStringToUTF8(directory) << ": " |
180 << base::SysNSStringToUTF8([error description]); | 148 << base::SysNSStringToUTF8([error description]); |
181 return; | 149 return; |
182 } | 150 } |
183 } else { | 151 } else { |
184 DCHECK(isDir); | 152 DCHECK(isDir); |
185 if (!isDir) { | 153 if (!isDir) { |
186 DLOG(ERROR) << "Destination Directory already exists and is a file"; | 154 DLOG(ERROR) << "Error creating destination directory: " |
| 155 << base::SysNSStringToUTF8(directory) << ": " |
| 156 << "file exists and is not a directory."; |
187 return; | 157 return; |
188 } | 158 } |
189 } | 159 } |
190 | 160 |
191 NSString* filename = [self sessionFilePathForDirectory:directory]; | 161 NSString* filename = [self sessionFilePathForDirectory:directory]; |
192 if (filename) { | 162 if (filename) { |
193 BOOL result = [NSKeyedArchiver archiveRootObject:window toFile:filename]; | 163 BOOL result = [NSKeyedArchiver archiveRootObject:window toFile:filename]; |
194 DCHECK(result); | 164 DCHECK(result); |
195 if (!result) | 165 if (!result) { |
196 DLOG(ERROR) << "Error writing session file to " << filename; | 166 DLOG(ERROR) << "Error writing session file to " << filename; |
| 167 return; |
| 168 } |
| 169 |
197 // Encrypt the session file (mostly for Incognito, but can't hurt to | 170 // Encrypt the session file (mostly for Incognito, but can't hurt to |
198 // always do it). | 171 // always do it). |
199 NSDictionary* attributeDict = | |
200 [NSDictionary dictionaryWithObject:NSFileProtectionComplete | |
201 forKey:NSFileProtectionKey]; | |
202 NSError* error = nil; | 172 NSError* error = nil; |
203 BOOL success = [[NSFileManager defaultManager] setAttributes:attributeDict | 173 BOOL success = [[NSFileManager defaultManager] |
204 ofItemAtPath:filename | 174 setAttributes:@{NSFileProtectionKey : NSFileProtectionComplete} |
205 error:&error]; | 175 ofItemAtPath:filename |
| 176 error:&error]; |
206 if (!success) { | 177 if (!success) { |
207 DLOG(ERROR) << "Error encrypting session file" | 178 DLOG(ERROR) << "Error encrypting session file: " |
| 179 << base::SysNSStringToUTF8(filename) << ": " |
208 << base::SysNSStringToUTF8([error description]); | 180 << base::SysNSStringToUTF8([error description]); |
209 } | 181 } |
210 } | 182 } |
211 } | 183 } |
212 | 184 |
213 - (void)saveWindow:(SessionWindowIOS*)window | 185 - (void)saveWindow:(SessionWindowIOS*)window |
214 forBrowserState:(ios::ChromeBrowserState*)browserState | 186 forBrowserState:(ios::ChromeBrowserState*)browserState |
215 immediately:(BOOL)immediately { | 187 immediately:(BOOL)immediately { |
216 NSString* stashPath = | 188 NSString* stashPath = |
217 base::SysUTF8ToNSString(browserState->GetStatePath().value()); | 189 base::SysUTF8ToNSString(browserState->GetStatePath().value()); |
218 // If there's an existing session window for |stashPath|, clear it before it's | 190 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]; | 191 [_pendingWindows setObject:window forKey:stashPath]; |
225 if (immediately) { | 192 if (immediately) { |
226 [NSObject cancelPreviousPerformRequestsWithTarget:self]; | 193 [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
227 [self performSaveToDirectoryInBackground:stashPath]; | 194 [self performSaveToDirectoryInBackground:stashPath]; |
228 } else if (!pendingSession) { | 195 } else if (!hadPendingSession) { |
229 // If there wasn't previously a delayed save pending for |stashPath|, | 196 // If there wasn't previously a delayed save pending for |stashPath|, |
230 // enqueue one now. | 197 // enqueue one now. |
231 [self performSelector:@selector(performSaveToDirectoryInBackground:) | 198 [self performSelector:@selector(performSaveToDirectoryInBackground:) |
232 withObject:stashPath | 199 withObject:stashPath |
233 afterDelay:kSaveDelay]; | 200 afterDelay:kSaveDelay]; |
234 } | 201 } |
235 } | 202 } |
236 | 203 |
237 - (SessionWindowIOS*)loadWindowForBrowserState: | 204 - (SessionWindowIOS*)loadWindowForBrowserState: |
238 (ios::ChromeBrowserState*)browserState { | 205 (ios::ChromeBrowserState*)browserState { |
239 NSString* stashPath = | 206 NSString* stashPath = |
240 base::SysUTF8ToNSString(browserState->GetStatePath().value()); | 207 base::SysUTF8ToNSString(browserState->GetStatePath().value()); |
241 SessionWindowIOS* window = | 208 SessionWindowIOS* window = |
242 [self loadWindowFromPath:[self sessionFilePathForDirectory:stashPath] | 209 [self loadWindowFromPath:[self sessionFilePathForDirectory:stashPath]]; |
243 forBrowserState:browserState]; | |
244 return window; | 210 return window; |
245 } | 211 } |
246 | 212 |
247 - (SessionWindowIOS*)loadWindowFromPath:(NSString*)path | 213 - (SessionWindowIOS*)loadWindowFromPath:(NSString*)sessionPath { |
248 forBrowserState:(ios::ChromeBrowserState*)browserState { | |
249 SessionWindowIOS* window = nil; | |
250 @try { | 214 @try { |
251 NSData* data = [NSData dataWithContentsOfFile:path]; | 215 NSData* data = [NSData dataWithContentsOfFile:sessionPath]; |
252 if (data) { | 216 if (!data) |
253 base::scoped_nsobject<SessionWindowUnarchiver> unarchiver([ | 217 return nil; |
254 [SessionWindowUnarchiver alloc] initForReadingWithData:data | 218 |
255 browserState:browserState]); | 219 NSKeyedUnarchiver* unarchiver = |
256 window = [[[unarchiver decodeObjectForKey:@"root"] retain] autorelease]; | 220 [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; |
257 } | 221 |
| 222 // Register compatibility aliases to support legacy saved sessions. |
| 223 [unarchiver cr_registerCompatibilityAliases]; |
| 224 return [unarchiver decodeObjectForKey:kRootObjectKey]; |
258 } @catch (NSException* exception) { | 225 } @catch (NSException* exception) { |
259 DLOG(ERROR) << "Error loading session file."; | 226 DLOG(ERROR) << "Error loading session file: " |
| 227 << base::SysNSStringToUTF8(sessionPath) << ": " |
| 228 << base::SysNSStringToUTF8([exception reason]); |
| 229 return nil; |
260 } | 230 } |
261 return window; | |
262 } | 231 } |
263 | 232 |
264 // Deletes the file containing the commands for the last session in the given | 233 // Deletes the file containing the commands for the last session in the given |
265 // browserState directory. | 234 // browserState directory. |
266 - (void)deleteLastSession:(NSString*)directory { | 235 - (void)deleteLastSession:(NSString*)directory { |
267 NSString* sessionFile = [self sessionFilePathForDirectory:directory]; | 236 NSString* sessionPath = [self sessionFilePathForDirectory:directory]; |
268 _taskRunner->PostTask( | 237 _taskRunner->PostTask( |
269 FROM_HERE, base::BindBlock(^{ | 238 FROM_HERE, base::BindBlockArc(^{ |
270 base::ThreadRestrictions::AssertIOAllowed(); | 239 base::ThreadRestrictions::AssertIOAllowed(); |
271 NSFileManager* fileManager = [NSFileManager defaultManager]; | 240 NSFileManager* fileManager = [NSFileManager defaultManager]; |
272 if (![fileManager fileExistsAtPath:sessionFile]) | 241 if (![fileManager fileExistsAtPath:sessionPath]) |
273 return; | 242 return; |
274 if (![fileManager removeItemAtPath:sessionFile error:nil]) | 243 |
275 CHECK(false) << "Unable to delete session file."; | 244 NSError* error = nil; |
| 245 if (![fileManager removeItemAtPath:sessionPath error:nil]) |
| 246 CHECK(false) << "Unable to delete session file: " |
| 247 << base::SysNSStringToUTF8(sessionPath) << ": " |
| 248 << base::SysNSStringToUTF8([error description]); |
276 })); | 249 })); |
277 } | 250 } |
278 | 251 |
279 @end | 252 @end |
OLD | NEW |