OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #import "ios/chrome/browser/sessions/session_service.h" | |
6 | |
7 #import <UIKit/UIKit.h> | |
8 | |
9 #include "base/files/file_path.h" | |
10 #include "base/location.h" | |
11 #include "base/logging.h" | |
12 #include "base/mac/bind_objc_block.h" | |
13 #include "base/mac/foundation_util.h" | |
14 #include "base/memory/ref_counted.h" | |
15 #include "base/sequenced_task_runner.h" | |
16 #include "base/strings/sys_string_conversions.h" | |
17 #include "base/synchronization/lock.h" | |
18 #include "base/threading/sequenced_worker_pool.h" | |
19 #include "base/threading/thread_restrictions.h" | |
20 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" | |
21 #import "ios/chrome/browser/sessions/session_window_ios.h" | |
22 #import "ios/web/public/crw_navigation_item_storage.h" | |
23 #import "ios/web/public/crw_session_certificate_policy_cache_storage.h" | |
24 #import "ios/web/public/crw_session_storage.h" | |
25 #include "ios/web/public/web_thread.h" | |
26 | |
27 // 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 | |
29 // 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. | |
31 // 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. | |
33 #undef try | |
34 #undef catch | |
35 | |
36 const NSTimeInterval kSaveDelay = 2.5; // Value taken from Desktop Chrome. | |
37 | |
38 @interface SessionWindowUnarchiver () | |
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 | |
67 // 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 | |
69 // of the alias. | |
70 + (void)registerCompatibilityAliases { | |
71 // TODO(crbug.com/661633): those aliases where introduced between M57 and | |
72 // M58, so remove them after M67 has shipped to stable. | |
73 [SessionWindowUnarchiver | |
74 setClass:[CRWSessionCertificatePolicyCacheStorage class] | |
75 forClassName:@"SessionCertificatePolicyManager"]; | |
76 [SessionWindowUnarchiver setClass:[CRWSessionStorage class] | |
77 forClassName:@"SessionController"]; | |
78 [SessionWindowUnarchiver setClass:[CRWSessionStorage class] | |
79 forClassName:@"CRWSessionController"]; | |
80 [SessionWindowUnarchiver setClass:[CRWNavigationItemStorage class] | |
81 forClassName:@"SessionEntry"]; | |
82 [SessionWindowUnarchiver setClass:[CRWNavigationItemStorage class] | |
83 forClassName:@"CRWSessionEntry"]; | |
84 [SessionWindowUnarchiver setClass:[SessionWindowIOS class] | |
85 forClassName:@"SessionWindow"]; | |
86 | |
87 // TODO(crbug.com/661633): this alias was introduced between M58 and M59, so | |
88 // remove it after M68 has shipped to stable. | |
89 [SessionWindowUnarchiver setClass:[CRWSessionStorage class] | |
90 forClassName:@"CRWNavigationManagerStorage"]; | |
91 [SessionWindowUnarchiver | |
92 setClass:[CRWSessionCertificatePolicyCacheStorage class] | |
93 forClassName:@"CRWSessionCertificatePolicyManager"]; | |
94 } | |
95 | |
96 @end | |
97 | |
98 @interface SessionServiceIOS () { | |
99 // The SequencedTaskRunner on which File IO operations are performed. | |
100 scoped_refptr<base::SequencedTaskRunner> _taskRunner; | |
101 | |
102 // Maps save directories to the pending SessionWindow for the delayed | |
103 // save behavior. | |
104 base::scoped_nsobject<NSMutableDictionary> _pendingWindows; | |
105 } | |
106 | |
107 // Saves the session corresponding to |directory| on the background | |
108 // task runner |_taskRunner|. | |
109 - (void)performSaveToDirectoryInBackground:(NSString*)directory; | |
110 @end | |
111 | |
112 @implementation SessionServiceIOS | |
113 | |
114 + (SessionServiceIOS*)sharedService { | |
115 static SessionServiceIOS* singleton = nil; | |
116 if (!singleton) { | |
117 singleton = [[[self class] alloc] init]; | |
118 } | |
119 return singleton; | |
120 } | |
121 | |
122 - (instancetype)init { | |
123 self = [super init]; | |
124 if (self) { | |
125 _pendingWindows.reset([[NSMutableDictionary alloc] init]); | |
126 auto* pool = web::WebThread::GetBlockingPool(); | |
127 _taskRunner = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); | |
128 } | |
129 return self; | |
130 } | |
131 | |
132 // Returns the path of the session file. | |
133 - (NSString*)sessionFilePathForDirectory:(NSString*)directory { | |
134 return [directory stringByAppendingPathComponent:@"session.plist"]; | |
135 } | |
136 | |
137 // Do the work of saving on a background thread. Assumes |window| is threadsafe. | |
138 - (void)performSaveToDirectoryInBackground:(NSString*)directory { | |
139 DCHECK(directory); | |
140 DCHECK([_pendingWindows objectForKey:directory] != nil); | |
141 UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] | |
142 beginBackgroundTaskWithExpirationHandler:^{ | |
143 }]; | |
144 DCHECK(identifier != UIBackgroundTaskInvalid); | |
145 | |
146 // 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. | |
148 SessionWindowIOS* localWindow = | |
149 [[_pendingWindows objectForKey:directory] retain]; | |
150 [_pendingWindows removeObjectForKey:directory]; | |
151 | |
152 _taskRunner->PostTask( | |
153 FROM_HERE, base::BindBlock(^{ | |
154 @try { | |
155 [self performSaveWindow:localWindow toDirectory:directory]; | |
156 } @catch (NSException* e) { | |
157 // Do nothing. | |
158 } | |
159 [localWindow release]; | |
160 [[UIApplication sharedApplication] endBackgroundTask:identifier]; | |
161 })); | |
162 } | |
163 | |
164 // Saves a SessionWindowIOS in a given directory. In case the directory doesn't | |
165 // exists it will be automatically created. | |
166 - (void)performSaveWindow:(SessionWindowIOS*)window | |
167 toDirectory:(NSString*)directory { | |
168 base::ThreadRestrictions::AssertIOAllowed(); | |
169 NSFileManager* fileManager = [NSFileManager defaultManager]; | |
170 BOOL isDir; | |
171 if (![fileManager fileExistsAtPath:directory isDirectory:&isDir]) { | |
172 NSError* error = nil; | |
173 BOOL result = [fileManager createDirectoryAtPath:directory | |
174 withIntermediateDirectories:YES | |
175 attributes:nil | |
176 error:&error]; | |
177 DCHECK(result); | |
178 if (!result) { | |
179 DLOG(ERROR) << "Error creating destination dir: " | |
180 << base::SysNSStringToUTF8([error description]); | |
181 return; | |
182 } | |
183 } else { | |
184 DCHECK(isDir); | |
185 if (!isDir) { | |
186 DLOG(ERROR) << "Destination Directory already exists and is a file"; | |
187 return; | |
188 } | |
189 } | |
190 | |
191 NSString* filename = [self sessionFilePathForDirectory:directory]; | |
192 if (filename) { | |
193 BOOL result = [NSKeyedArchiver archiveRootObject:window toFile:filename]; | |
194 DCHECK(result); | |
195 if (!result) | |
196 DLOG(ERROR) << "Error writing session file to " << filename; | |
197 // Encrypt the session file (mostly for Incognito, but can't hurt to | |
198 // always do it). | |
199 NSDictionary* attributeDict = | |
200 [NSDictionary dictionaryWithObject:NSFileProtectionComplete | |
201 forKey:NSFileProtectionKey]; | |
202 NSError* error = nil; | |
203 BOOL success = [[NSFileManager defaultManager] setAttributes:attributeDict | |
204 ofItemAtPath:filename | |
205 error:&error]; | |
206 if (!success) { | |
207 DLOG(ERROR) << "Error encrypting session file" | |
208 << base::SysNSStringToUTF8([error description]); | |
209 } | |
210 } | |
211 } | |
212 | |
213 - (void)saveWindow:(SessionWindowIOS*)window | |
214 forBrowserState:(ios::ChromeBrowserState*)browserState | |
215 immediately:(BOOL)immediately { | |
216 NSString* stashPath = | |
217 base::SysUTF8ToNSString(browserState->GetStatePath().value()); | |
218 // If there's an existing session window for |stashPath|, clear it before it's | |
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]; | |
225 if (immediately) { | |
226 [NSObject cancelPreviousPerformRequestsWithTarget:self]; | |
227 [self performSaveToDirectoryInBackground:stashPath]; | |
228 } else if (!pendingSession) { | |
229 // If there wasn't previously a delayed save pending for |stashPath|, | |
230 // enqueue one now. | |
231 [self performSelector:@selector(performSaveToDirectoryInBackground:) | |
232 withObject:stashPath | |
233 afterDelay:kSaveDelay]; | |
234 } | |
235 } | |
236 | |
237 - (SessionWindowIOS*)loadWindowForBrowserState: | |
238 (ios::ChromeBrowserState*)browserState { | |
239 NSString* stashPath = | |
240 base::SysUTF8ToNSString(browserState->GetStatePath().value()); | |
241 SessionWindowIOS* window = | |
242 [self loadWindowFromPath:[self sessionFilePathForDirectory:stashPath] | |
243 forBrowserState:browserState]; | |
244 return window; | |
245 } | |
246 | |
247 - (SessionWindowIOS*)loadWindowFromPath:(NSString*)path | |
248 forBrowserState:(ios::ChromeBrowserState*)browserState { | |
249 SessionWindowIOS* window = nil; | |
250 @try { | |
251 NSData* data = [NSData dataWithContentsOfFile:path]; | |
252 if (data) { | |
253 base::scoped_nsobject<SessionWindowUnarchiver> unarchiver([ | |
254 [SessionWindowUnarchiver alloc] initForReadingWithData:data | |
255 browserState:browserState]); | |
256 window = [[[unarchiver decodeObjectForKey:@"root"] retain] autorelease]; | |
257 } | |
258 } @catch (NSException* exception) { | |
259 DLOG(ERROR) << "Error loading session file."; | |
260 } | |
261 return window; | |
262 } | |
263 | |
264 // Deletes the file containing the commands for the last session in the given | |
265 // browserState directory. | |
266 - (void)deleteLastSession:(NSString*)directory { | |
267 NSString* sessionFile = [self sessionFilePathForDirectory:directory]; | |
268 _taskRunner->PostTask( | |
269 FROM_HERE, base::BindBlock(^{ | |
270 base::ThreadRestrictions::AssertIOAllowed(); | |
271 NSFileManager* fileManager = [NSFileManager defaultManager]; | |
272 if (![fileManager fileExistsAtPath:sessionFile]) | |
273 return; | |
274 if (![fileManager removeItemAtPath:sessionFile error:nil]) | |
275 CHECK(false) << "Unable to delete session file."; | |
276 })); | |
277 } | |
278 | |
279 @end | |
OLD | NEW |