Index: ios/chrome/browser/sessions/session_service.mm |
diff --git a/ios/chrome/browser/sessions/session_service.mm b/ios/chrome/browser/sessions/session_service.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..16f66347770dcd0e462307c0d7b6b3ac7a957dcf |
--- /dev/null |
+++ b/ios/chrome/browser/sessions/session_service.mm |
@@ -0,0 +1,251 @@ |
+// Copyright 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/chrome/browser/sessions/session_service.h" |
+ |
+#import <UIKit/UIKit.h> |
+ |
+#include "base/files/file_path.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#include "base/mac/bind_objc_block.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/sequenced_task_runner.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/synchronization/lock.h" |
+#include "base/threading/sequenced_worker_pool.h" |
+#include "base/threading/thread_restrictions.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#import "ios/chrome/browser/sessions/session_window.h" |
+#import "ios/web/navigation/crw_session_certificate_policy_manager.h" |
+#import "ios/web/navigation/crw_session_controller.h" |
+#import "ios/web/navigation/crw_session_entry.h" |
+#include "ios/web/public/web_thread.h" |
+ |
+// When C++ exceptions are disabled, the C++ library defines |try| and |
+// |catch| so as to allow exception-expecting C++ code to build properly when |
+// language support for exceptions is not present. These macros interfere |
+// with the use of |@try| and |@catch| in Objective-C files such as this one. |
+// Undefine these macros here, after everything has been #included, since |
+// there will be no C++ uses and only Objective-C uses from this point on. |
+#undef try |
+#undef catch |
+ |
+const NSTimeInterval kSaveDelay = 2.5; // Value taken from Desktop Chrome. |
+ |
+@interface SessionWindowUnarchiver () { |
+ ios::ChromeBrowserState* _browserState; |
+} |
+ |
+@end |
+ |
+@implementation SessionWindowUnarchiver |
+ |
+@synthesize browserState = _browserState; // weak |
+ |
+- (id)initForReadingWithData:(NSData*)data |
+ browserState:(ios::ChromeBrowserState*)browserState { |
+ if (self = [super initForReadingWithData:data]) { |
+ _browserState = browserState; |
+ } |
+ return self; |
+} |
+ |
+@end |
+ |
+@interface SessionServiceIOS () { |
+ @private |
+ // The SequencedTaskRunner on which File IO operations are performed. |
+ scoped_refptr<base::SequencedTaskRunner> taskRunner_; |
+ |
+ // Maps save directories to the pending SessionWindow for the delayed |
+ // save behavior. |
+ base::scoped_nsobject<NSMutableDictionary> pendingWindows_; |
+} |
+ |
+- (void)performSaveToDirectoryInBackground:(NSString*)directory; |
+- (void)performSaveWindow:(SessionWindowIOS*)window |
+ toDirectory:(NSString*)directory; |
+@end |
+ |
+@implementation SessionServiceIOS |
+ |
++ (SessionServiceIOS*)sharedService { |
+ static SessionServiceIOS* singleton = nil; |
+ if (!singleton) { |
+ singleton = [[[self class] alloc] init]; |
+ } |
+ return singleton; |
+} |
+ |
+- (id)init { |
+ self = [super init]; |
+ if (self) { |
+ pendingWindows_.reset([[NSMutableDictionary alloc] init]); |
+ auto pool = web::WebThread::GetBlockingPool(); |
+ taskRunner_ = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); |
+ } |
+ return self; |
+} |
+ |
+// Returns the path of the session file. |
+- (NSString*)sessionFilePathForDirectory:(NSString*)directory { |
+ return [directory stringByAppendingPathComponent:@"session.plist"]; |
+} |
+ |
+// Do the work of saving on a background thread. Assumes |window| is threadsafe. |
+- (void)performSaveToDirectoryInBackground:(NSString*)directory { |
+ DCHECK(directory); |
+ DCHECK([pendingWindows_ objectForKey:directory] != nil); |
+ UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] |
+ beginBackgroundTaskWithExpirationHandler:^{ |
+ }]; |
+ DCHECK(identifier != UIBackgroundTaskInvalid); |
+ |
+ // Put the window into a local var so it can be retained for the block, yet |
+ // we can remove it from the dictionary to allow queuing another save. |
+ SessionWindowIOS* localWindow = |
+ [[pendingWindows_ objectForKey:directory] retain]; |
+ [pendingWindows_ removeObjectForKey:directory]; |
+ |
+ taskRunner_->PostTask( |
+ FROM_HERE, base::BindBlock(^{ |
+ @try { |
+ [self performSaveWindow:localWindow toDirectory:directory]; |
+ } @catch (NSException* e) { |
+ // Do nothing. |
+ } |
+ [localWindow release]; |
+ [[UIApplication sharedApplication] endBackgroundTask:identifier]; |
+ })); |
+} |
+ |
+// Saves a SessionWindowIOS in a given directory. In case the directory doesn't |
+// exists it will be automatically created. |
+- (void)performSaveWindow:(SessionWindowIOS*)window |
+ toDirectory:(NSString*)directory { |
+ base::ThreadRestrictions::AssertIOAllowed(); |
+ NSFileManager* fileManager = [NSFileManager defaultManager]; |
+ BOOL isDir; |
+ if (![fileManager fileExistsAtPath:directory isDirectory:&isDir]) { |
+ NSError* error = nil; |
+ BOOL result = [fileManager createDirectoryAtPath:directory |
+ withIntermediateDirectories:YES |
+ attributes:nil |
+ error:&error]; |
+ DCHECK(result); |
+ if (!result) { |
+ DLOG(ERROR) << "Error creating destination dir: " |
+ << base::SysNSStringToUTF8([error description]); |
+ return; |
+ } |
+ } else { |
+ DCHECK(isDir); |
+ if (!isDir) { |
+ DLOG(ERROR) << "Destination Directory already exists and is a file"; |
+ return; |
+ } |
+ } |
+ |
+ NSString* filename = [self sessionFilePathForDirectory:directory]; |
+ if (filename) { |
+ BOOL result = [NSKeyedArchiver archiveRootObject:window toFile:filename]; |
+ DCHECK(result); |
+ if (!result) |
+ DLOG(ERROR) << "Error writing session file to " << filename; |
+ // Encrypt the session file (mostly for Incognito, but can't hurt to |
+ // always do it). |
+ NSDictionary* attributeDict = |
+ [NSDictionary dictionaryWithObject:NSFileProtectionComplete |
+ forKey:NSFileProtectionKey]; |
+ NSError* error = nil; |
+ BOOL success = [[NSFileManager defaultManager] setAttributes:attributeDict |
+ ofItemAtPath:filename |
+ error:&error]; |
+ if (!success) { |
+ DLOG(ERROR) << "Error encrypting session file" |
+ << base::SysNSStringToUTF8([error description]); |
+ } |
+ } |
+} |
+ |
+- (void)saveWindow:(SessionWindowIOS*)window |
+ forBrowserState:(ios::ChromeBrowserState*)browserState |
+ immediately:(BOOL)immediately { |
+ NSString* stashPath = |
+ base::SysUTF8ToNSString(browserState->GetStatePath().value()); |
+ // If there's an existing session window for |stashPath|, clear it before it's |
+ // replaced. |
+ SessionWindowIOS* pendingSession = base::mac::ObjCCast<SessionWindowIOS>( |
+ [pendingWindows_ objectForKey:stashPath]); |
+ [pendingSession clearSessions]; |
+ // Set |window| as the pending save for |stashPath|. |
+ [pendingWindows_ setObject:window forKey:stashPath]; |
+ if (immediately) { |
+ [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
+ [self performSaveToDirectoryInBackground:stashPath]; |
+ } else if (!pendingSession) { |
+ // If there wasn't previously a delayed save pending for |stashPath}|, |
+ // enqueue one now. |
+ [self performSelector:@selector(performSaveToDirectoryInBackground:) |
+ withObject:stashPath |
+ afterDelay:kSaveDelay]; |
+ } |
+} |
+ |
+- (SessionWindowIOS*)loadWindowForBrowserState: |
+ (ios::ChromeBrowserState*)browserState { |
+ NSString* stashPath = |
+ base::SysUTF8ToNSString(browserState->GetStatePath().value()); |
+ SessionWindowIOS* window = |
+ [self loadWindowFromPath:[self sessionFilePathForDirectory:stashPath] |
+ forBrowserState:browserState]; |
+ return window; |
+} |
+ |
+- (SessionWindowIOS*)loadWindowFromPath:(NSString*)path |
+ forBrowserState:(ios::ChromeBrowserState*)browserState { |
+ // HACK: Handle the case where we had to change the class name of a persisted |
+ // class on disk. |
+ [SessionWindowUnarchiver setClass:[CRWSessionCertificatePolicyManager class] |
+ forClassName:@"SessionCertificatePolicyManager"]; |
+ [SessionWindowUnarchiver setClass:[CRWSessionController class] |
+ forClassName:@"SessionController"]; |
+ [SessionWindowUnarchiver setClass:[CRWSessionEntry class] |
+ forClassName:@"SessionEntry"]; |
+ // TODO(crbug.com/661633): Remove this hack. |
+ [SessionWindowUnarchiver setClass:[SessionWindowIOS class] |
+ forClassName:@"SessionWindow"]; |
+ SessionWindowIOS* window = nil; |
+ @try { |
+ NSData* data = [NSData dataWithContentsOfFile:path]; |
+ if (data) { |
+ base::scoped_nsobject<SessionWindowUnarchiver> unarchiver([ |
+ [SessionWindowUnarchiver alloc] initForReadingWithData:data |
+ browserState:browserState]); |
+ window = [[[unarchiver decodeObjectForKey:@"root"] retain] autorelease]; |
+ } |
+ } @catch (NSException* exception) { |
+ DLOG(ERROR) << "Error loading session.plist"; |
+ } |
+ return window; |
+} |
+ |
+// Deletes the file containing the commands for the last session in the given |
+// browserState directory. |
+- (void)deleteLastSession:(NSString*)directory { |
+ NSString* sessionFile = [self sessionFilePathForDirectory:directory]; |
+ taskRunner_->PostTask( |
+ FROM_HERE, base::BindBlock(^{ |
+ base::ThreadRestrictions::AssertIOAllowed(); |
+ NSFileManager* fileManager = [NSFileManager defaultManager]; |
+ if (![fileManager fileExistsAtPath:sessionFile]) |
+ return; |
+ if (![fileManager removeItemAtPath:sessionFile error:nil]) |
+ CHECK(false) << "Unable to delete session file."; |
+ })); |
+} |
+ |
+@end |