| 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 |