Index: ios/chrome/browser/crash_report/crash_restore_helper.mm |
diff --git a/ios/chrome/browser/crash_report/crash_restore_helper.mm b/ios/chrome/browser/crash_report/crash_restore_helper.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ad2b05f963ad6bad12ca8240ae26bedf3f1a5739 |
--- /dev/null |
+++ b/ios/chrome/browser/crash_report/crash_restore_helper.mm |
@@ -0,0 +1,307 @@ |
+// 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/crash_report/crash_restore_helper.h" |
+ |
+#include <memory> |
+#include <utility> |
+ |
+#include "base/metrics/histogram_macros.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "components/infobars/core/confirm_infobar_delegate.h" |
+#include "components/infobars/core/infobar.h" |
+#include "components/infobars/core/infobar_manager.h" |
+#include "components/sessions/core/tab_restore_service.h" |
+#include "components/sessions/ios/ios_live_tab.h" |
+#include "components/strings/grit/components_chromium_strings.h" |
+#include "components/strings/grit/components_google_chrome_strings.h" |
+#include "components/strings/grit/components_strings.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#import "ios/chrome/browser/crash_report/breakpad_helper.h" |
+#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h" |
+#import "ios/chrome/browser/sessions/session_service.h" |
+#import "ios/chrome/browser/sessions/session_window.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#import "ios/chrome/browser/tabs/tab_model.h" |
+#include "ios/chrome/grit/ios_theme_resources.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/base/resource/resource_bundle.h" |
+ |
+@protocol InfoBarManagerObserverBridgeProtocol |
+- (void)infoBarRemoved:(infobars::InfoBar*)infobar; |
+@end |
+ |
+// Private methods. |
+@interface CrashRestoreHelper ()<InfoBarManagerObserverBridgeProtocol> |
+// Deletes the session file for the given browser state, optionally backing it |
+// up beforehand to |backupFile| if it is not nil. This method returns YES in |
+// case of success, NO otherwise. |
+- (BOOL)deleteSessionForBrowserState:(ios::ChromeBrowserState*)browserState |
+ backupFile:(NSString*)file; |
+// Returns the path where the sessions for the main browser state are backed up. |
+- (NSString*)sessionBackupPath; |
+// Restores the sessions after a crash. It should only be called if |
+// |moveAsideSessionInformation| was successful. |
+- (BOOL)restoreSessionsAfterCrash; |
+@end |
+ |
+namespace { |
+ |
+class InfoBarManagerObserverBridge : infobars::InfoBarManager::Observer { |
+ public: |
+ InfoBarManagerObserverBridge( |
+ infobars::InfoBarManager* infoBarManager, |
+ id<InfoBarManagerObserverBridgeProtocol> observer) |
+ : infobars::InfoBarManager::Observer(), |
+ manager_(infoBarManager), |
+ observer_(observer) { |
+ DCHECK(infoBarManager); |
+ DCHECK(observer); |
+ manager_->AddObserver(this); |
+ } |
+ |
+ ~InfoBarManagerObserverBridge() override { |
+ if (manager_) |
+ manager_->RemoveObserver(this); |
+ } |
+ |
+ void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) override { |
+ [observer_ infoBarRemoved:infobar]; |
+ } |
+ |
+ void OnInfoBarReplaced(infobars::InfoBar* old_infobar, |
+ infobars::InfoBar* new_infobar) override { |
+ [observer_ infoBarRemoved:old_infobar]; |
+ } |
+ |
+ void OnManagerShuttingDown(infobars::InfoBarManager* manager) override { |
+ manager_->RemoveObserver(this); |
+ manager_ = nullptr; |
+ } |
+ |
+ private: |
+ infobars::InfoBarManager* manager_; |
+ id<InfoBarManagerObserverBridgeProtocol> observer_; |
+}; |
+ |
+// SessionCrashedInfoBarDelegate ---------------------------------------------- |
+ |
+// A delegate for the InfoBar shown when the previous session has crashed. |
+class SessionCrashedInfoBarDelegate : public ConfirmInfoBarDelegate { |
+ public: |
+ // Creates a session crashed infobar and adds it to |infobar_manager|. |
+ static bool Create(infobars::InfoBarManager* infobar_manager, |
+ CrashRestoreHelper* crash_restore_helper); |
+ |
+ private: |
+ SessionCrashedInfoBarDelegate(CrashRestoreHelper* crash_restore_helper); |
+ ~SessionCrashedInfoBarDelegate() override; |
+ |
+ // InfoBarDelegate: |
+ InfoBarIdentifier GetIdentifier() const override; |
+ |
+ // ConfirmInfoBarDelegate: |
+ base::string16 GetMessageText() const override; |
+ int GetButtons() const override; |
+ base::string16 GetButtonLabel(InfoBarButton button) const override; |
+ bool Accept() override; |
+ int GetIconId() const override; |
+ |
+ // The CrashRestoreHelper to restore sessions. |
+ base::scoped_nsobject<CrashRestoreHelper> crash_restore_helper_; |
+ // The TabModel to restore sessions to. |
+ base::scoped_nsobject<TabModel> tab_model_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(SessionCrashedInfoBarDelegate); |
+}; |
+ |
+SessionCrashedInfoBarDelegate::SessionCrashedInfoBarDelegate( |
+ CrashRestoreHelper* crash_restore_helper) |
+ : crash_restore_helper_([crash_restore_helper retain]) {} |
+ |
+SessionCrashedInfoBarDelegate::~SessionCrashedInfoBarDelegate() {} |
+ |
+// static |
+bool SessionCrashedInfoBarDelegate::Create( |
+ infobars::InfoBarManager* infobar_manager, |
+ CrashRestoreHelper* crash_restore_helper) { |
+ DCHECK(infobar_manager); |
+ std::unique_ptr<ConfirmInfoBarDelegate> delegate( |
+ new SessionCrashedInfoBarDelegate(crash_restore_helper)); |
+ return !!infobar_manager->AddInfoBar( |
+ infobar_manager->CreateConfirmInfoBar(std::move(delegate))); |
+} |
+ |
+infobars::InfoBarDelegate::InfoBarIdentifier |
+SessionCrashedInfoBarDelegate::GetIdentifier() const { |
+ return SESSION_CRASHED_INFOBAR_DELEGATE; |
+} |
+ |
+base::string16 SessionCrashedInfoBarDelegate::GetMessageText() const { |
+ return l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_MESSAGE); |
+} |
+ |
+int SessionCrashedInfoBarDelegate::GetButtons() const { |
+ return BUTTON_OK; |
+} |
+ |
+base::string16 SessionCrashedInfoBarDelegate::GetButtonLabel( |
+ InfoBarButton button) const { |
+ DCHECK_EQ(BUTTON_OK, button); |
+ return l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON); |
+} |
+ |
+bool SessionCrashedInfoBarDelegate::Accept() { |
+ // Accept should return NO if the infobar is going to be dismissed. |
+ // Since |restoreSessionAfterCrash| returns YES if a single NTP tab is closed, |
+ // which will dismiss the infobar, invert the bool. |
+ return ![crash_restore_helper_ restoreSessionsAfterCrash]; |
+} |
+ |
+int SessionCrashedInfoBarDelegate::GetIconId() const { |
+ return IDR_IOS_INFOBAR_RESTORE_SESSION; |
+} |
+ |
+} // namespace |
+ |
+@implementation CrashRestoreHelper { |
+ ios::ChromeBrowserState* _browserState; |
+ BOOL _needRestoration; |
+ std::unique_ptr<InfoBarManagerObserverBridge> _infoBarBridge; |
+ // The TabModel to restore sessions to. |
+ base::scoped_nsobject<TabModel> _tabModel; |
+ |
+ // Indicate that the session has been restored to tabs or to recently closed |
+ // and should not be rerestored. |
+ BOOL _sessionRestored; |
+} |
+ |
+- (id)initWithBrowserState:(ios::ChromeBrowserState*)browserState { |
+ if (self = [super init]) { |
+ _browserState = browserState; |
+ } |
+ return self; |
+} |
+ |
+- (void)showRestoreIfNeeded:(TabModel*)tabModel { |
+ if (!_needRestoration) |
+ return; |
+ |
+ // The last session didn't exit cleanly. Show an infobar to the user so |
+ // that they can restore if they want. The delegate deletes itself when |
+ // it is closed. |
+ DCHECK([tabModel currentTab]); |
+ infobars::InfoBarManager* infoBarManager = |
+ [[tabModel currentTab] infoBarManager]; |
+ _tabModel.reset([tabModel retain]); |
+ SessionCrashedInfoBarDelegate::Create(infoBarManager, self); |
+ _infoBarBridge.reset(new InfoBarManagerObserverBridge(infoBarManager, self)); |
+} |
+ |
+- (BOOL)deleteSessionForBrowserState:(ios::ChromeBrowserState*)browserState |
+ backupFile:(NSString*)file { |
+ SessionServiceIOS* sessionService = [SessionServiceIOS sharedService]; |
+ NSString* stashPath = |
+ base::SysUTF8ToNSString(browserState->GetStatePath().value()); |
+ NSString* sessionPath = |
+ [sessionService sessionFilePathForDirectory:stashPath]; |
+ NSFileManager* fileManager = [NSFileManager defaultManager]; |
+ if (![fileManager fileExistsAtPath:sessionPath]) |
+ return NO; |
+ if (file) { |
+ NSError* error = nil; |
+ BOOL fileOperationSuccess = |
+ [fileManager removeItemAtPath:file error:&error]; |
+ NSInteger errorCode = fileOperationSuccess ? 0 : [error code]; |
+ UMA_HISTOGRAM_SPARSE_SLOWLY("TabRestore.error_remove_backup_at_path", |
+ errorCode); |
+ if (!fileOperationSuccess && errorCode != NSFileNoSuchFileError) { |
+ return NO; |
+ } |
+ fileOperationSuccess = |
+ [fileManager moveItemAtPath:sessionPath toPath:file error:&error]; |
+ errorCode = fileOperationSuccess ? 0 : [error code]; |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ "TabRestore.error_move_session_at_path_to_backup", errorCode); |
+ if (!fileOperationSuccess) { |
+ return NO; |
+ } |
+ } else { |
+ NSError* error; |
+ BOOL fileOperationSuccess = |
+ [fileManager removeItemAtPath:sessionPath error:&error]; |
+ NSInteger errorCode = fileOperationSuccess ? 0 : [error code]; |
+ UMA_HISTOGRAM_SPARSE_SLOWLY("TabRestore.error_remove_session_at_path", |
+ errorCode); |
+ if (!fileOperationSuccess) { |
+ return NO; |
+ } |
+ } |
+ return YES; |
+} |
+ |
+- (NSString*)sessionBackupPath { |
+ NSString* tmpDirectory = NSTemporaryDirectory(); |
+ return [tmpDirectory stringByAppendingPathComponent:@"session.bak"]; |
+} |
+ |
+- (void)moveAsideSessionInformation { |
+ // This may be the first time that the OTR browser state is being accessed, so |
+ // ensure that the OTR ChromeBrowserState is created first. |
+ ios::ChromeBrowserState* otrBrowserState = |
+ _browserState->GetOffTheRecordChromeBrowserState(); |
+ [self deleteSessionForBrowserState:otrBrowserState backupFile:nil]; |
+ _needRestoration = |
+ [self deleteSessionForBrowserState:_browserState |
+ backupFile:[self sessionBackupPath]]; |
+} |
+ |
+- (BOOL)restoreSessionsAfterCrash { |
+ DCHECK(!_sessionRestored); |
+ _sessionRestored = YES; |
+ _infoBarBridge.reset(); |
+ SessionWindowIOS* sessionWindow = [[SessionServiceIOS sharedService] |
+ loadWindowFromPath:[self sessionBackupPath] |
+ forBrowserState:[_tabModel browserState]]; |
+ if (sessionWindow) { |
+ breakpad_helper::WillStartCrashRestoration(); |
+ return [_tabModel restoreSessionWindow:sessionWindow]; |
+ } |
+ return NO; |
+} |
+ |
+- (void)infoBarRemoved:(infobars::InfoBar*)infobar { |
+ DCHECK(infobar->delegate()); |
+ if (_sessionRestored || |
+ infobar->delegate()->GetIdentifier() != |
+ infobars::InfoBarDelegate::SESSION_CRASHED_INFOBAR_DELEGATE) { |
+ return; |
+ } |
+ |
+ // If the infobar is dismissed without restoring the tabs (either by closing |
+ // it with the cross or after a navigation), all the entries will be added to |
+ // the recently closed tabs. |
+ _sessionRestored = YES; |
+ |
+ SessionWindowIOS* window = [[SessionServiceIOS sharedService] |
+ loadWindowFromPath:[self sessionBackupPath] |
+ forBrowserState:[_tabModel browserState]]; |
+ DCHECK(window); |
+ if (!window.unclaimedSessions) |
+ return; |
+ sessions::TabRestoreService* const tabRestoreService = |
+ IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState); |
+ tabRestoreService->LoadTabsFromLastSession(); |
+ |
+ while (window.unclaimedSessions) { |
+ // Add all tabs at the 0 position as the position is relative to an old |
+ // tabModel. |
+ std::unique_ptr<web::WebStateImpl> webState = [window nextSession]; |
+ tabRestoreService->CreateHistoricalTab( |
+ sessions::IOSLiveTab::GetForWebState(webState.get()), 0); |
+ } |
+ return; |
+} |
+ |
+@end |