Index: ios/chrome/app/application_delegate/app_state.mm |
diff --git a/ios/chrome/app/application_delegate/app_state.mm b/ios/chrome/app/application_delegate/app_state.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..593da2515eca72d7e6ac80e65c1aa12d5da70114 |
--- /dev/null |
+++ b/ios/chrome/app/application_delegate/app_state.mm |
@@ -0,0 +1,499 @@ |
+// Copyright 2016 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/app/application_delegate/app_state.h" |
+ |
+#include "base/critical_closure.h" |
+#import "base/mac/bind_objc_block.h" |
+#import "base/mac/scoped_nsobject.h" |
+#include "components/metrics/metrics_service.h" |
+#import "ios/chrome/app/application_delegate/app_navigation.h" |
+#import "ios/chrome/app/application_delegate/browser_launcher.h" |
+#import "ios/chrome/app/application_delegate/memory_warning_helper.h" |
+#import "ios/chrome/app/application_delegate/metrics_mediator.h" |
+#import "ios/chrome/app/application_delegate/startup_information.h" |
+#import "ios/chrome/app/application_delegate/tab_opening.h" |
+#import "ios/chrome/app/application_delegate/tab_switching.h" |
+#import "ios/chrome/app/application_delegate/user_activity_handler.h" |
+#import "ios/chrome/app/deferred_initialization_runner.h" |
+#import "ios/chrome/app/safe_mode/safe_mode_coordinator.h" |
+#import "ios/chrome/app/safe_mode_crashing_modules_config.h" |
+#include "ios/chrome/browser/application_context.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#include "ios/chrome/browser/chrome_constants.h" |
+#include "ios/chrome/browser/crash_loop_detection_util.h" |
+#include "ios/chrome/browser/crash_report/breakpad_helper.h" |
+#import "ios/chrome/browser/crash_report/crash_report_background_uploader.h" |
+#import "ios/chrome/browser/device_sharing/device_sharing_manager.h" |
+#import "ios/chrome/browser/geolocation/omnibox_geolocation_config.h" |
+#import "ios/chrome/browser/metrics/previous_session_info.h" |
+#import "ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.h" |
+#include "ios/chrome/browser/ui/background_generator.h" |
+#import "ios/chrome/browser/ui/browser_view_controller.h" |
+#import "ios/chrome/browser/ui/main/browser_view_information.h" |
+#include "ios/net/cookies/cookie_store_ios.h" |
+#include "ios/net/cookies/system_cookie_util.h" |
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
+#include "ios/public/provider/chrome/browser/distribution/app_distribution_provider.h" |
+#import "ios/public/provider/chrome/browser/user_feedback/user_feedback_provider.h" |
+#include "ios/web/net/request_tracker_impl.h" |
+#include "net/url_request/url_request_context.h" |
+ |
+namespace { |
+// Helper method to post |closure| on the UI thread. |
+void PostTaskOnUIThread(const base::Closure& closure) { |
+ web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, closure); |
+} |
+NSString* const kStartupAttemptReset = @"StartupAttempReset"; |
+} // namespace |
+ |
+@interface AppState ()<SafeModeCoordinatorDelegate> { |
+ // Container for startup information. |
+ base::WeakNSProtocol<id<StartupInformation>> _startupInformation; |
+ // Browser launcher to launch browser in different states. |
+ base::WeakNSProtocol<id<BrowserLauncher>> _browserLauncher; |
+ // UIApplicationDelegate for the application. |
+ base::WeakNSObject<MainApplicationDelegate> _mainApplicationDelegate; |
+ // Window for the application. |
+ base::WeakNSObject<UIWindow> _window; |
+ |
+ // Variables backing properties of same name. |
+ base::scoped_nsobject<SafeModeCoordinator> _safeModeCoordinator; |
+ |
+ // Start of the current session, used for UMA. |
+ base::TimeTicks _sessionStartTime; |
+ // YES if the app is currently in the process of terminating. |
+ BOOL _appIsTerminating; |
+ // Indicates if an NTP tab should be opened once the application has become |
+ // active. |
+ BOOL _shouldOpenNTPTabOnActive; |
+ // Interstitial view used to block any incognito tabs after backgrounding. |
+ base::scoped_nsobject<UIView> _incognitoBlocker; |
+ // Whether the application is currently in the background. |
+ // This is a workaround for rdar://22392526 where |
+ // -applicationDidEnterBackground: can be called twice. |
+ // TODO(crbug.com/546196): Remove this once rdar://22392526 is fixed. |
+ BOOL _applicationInBackground; |
+ // YES if cookies are currently being flushed to disk. |
+ BOOL _savingCookies; |
+} |
+ |
+// Safe mode coordinator. If this is non-nil, the app is displaying the safe |
+// mode UI. |
+@property(nonatomic, retain) SafeModeCoordinator* safeModeCoordinator; |
+ |
+// Return value for -requiresHandlingAfterLaunchWithOptions that determines if |
+// UIKit should make followup delegate calls such as |
+// -performActionForShortcutItem or -openURL. |
+@property(nonatomic, assign) BOOL shouldPerformAdditionalDelegateHandling; |
+ |
+// This method is the first to be called when user launches the application. |
+// Depending on the background tasks history, the state of the application is |
+// either INITIALIZATION_STAGE_BASIC or INITIALIZATION_STAGE_BACKGROUND so this |
+// step cannot be included in the |startUpBrowserToStage:| method. |
+- (void)initializeUI; |
+// Saves the current launch details to user defaults. |
+- (void)saveLaunchDetailsToDefaults; |
+ |
+@end |
+ |
+@implementation AppState |
+ |
+@synthesize shouldPerformAdditionalDelegateHandling = |
+ _shouldPerformAdditionalDelegateHandling; |
+@synthesize userInteracted = _userInteracted; |
+ |
+- (instancetype)init { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (instancetype) |
+initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher |
+ startupInformation:(id<StartupInformation>)startupInformation |
+ applicationDelegate:(MainApplicationDelegate*)applicationDelegate { |
+ self = [super init]; |
+ if (self) { |
+ _startupInformation.reset(startupInformation); |
+ _browserLauncher.reset(browserLauncher); |
+ _mainApplicationDelegate.reset(applicationDelegate); |
+ } |
+ return self; |
+} |
+ |
+#pragma mark - Properties implementation |
+ |
+- (SafeModeCoordinator*)safeModeCoordinator { |
+ return _safeModeCoordinator; |
+} |
+ |
+- (void)setSafeModeCoordinator:(SafeModeCoordinator*)safeModeCoordinator { |
+ _safeModeCoordinator.reset([safeModeCoordinator retain]); |
+} |
+ |
+- (void)setWindow:(UIWindow*)window { |
+ _window.reset(window); |
+} |
+ |
+- (UIWindow*)window { |
+ return _window; |
+} |
+ |
+#pragma mark - Public methods. |
+ |
+- (void)applicationDidEnterBackground:(UIApplication*)application |
+ memoryHelper:(MemoryWarningHelper*)memoryHelper |
+ tabSwitcherIsActive:(BOOL)tabSwitcherIsActive { |
+ if ([self isInSafeMode]) { |
+ // Force a crash when backgrounding and in safe mode, so users don't get |
+ // stuck in safe mode. |
+ breakpad_helper::SetEnabled(false); |
+ exit(0); |
+ return; |
+ } |
+ |
+ if (_applicationInBackground) { |
+ return; |
+ } |
+ _applicationInBackground = YES; |
+ |
+ breakpad_helper::SetCurrentlyInBackground(true); |
+ |
+ if ([_browserLauncher browserInitializationStage] < |
+ INITIALIZATION_STAGE_FOREGROUND) { |
+ // The clean-up done in |-applicationDidEnterBackground:| is only valid for |
+ // the case when the application is started in foreground, so there is |
+ // nothing to clean up as the application was not initialized for foregound. |
+ // |
+ // From the stack trace of the crash bug http://crbug.com/437307 , it |
+ // seems that |-applicationDidEnterBackground:| may be called when the app |
+ // is started in background and before the initialization for background |
+ // stage is done. Note that the crash bug could not be reproduced though. |
+ return; |
+ } |
+ |
+ [MetricsMediator |
+ applicationDidEnterBackground:[memoryHelper |
+ foregroundMemoryWarningCount]]; |
+ |
+ [_startupInformation expireFirstUserActionRecorder]; |
+ |
+ // If the current BVC is incognito, or if we are in the tab switcher and there |
+ // are incognito tabs visible, place a full screen view containing the |
+ // switcher background to hide any incognito content. |
+ if (([[_browserLauncher browserViewInformation] currentBrowserState] && |
+ [[_browserLauncher browserViewInformation] currentBrowserState] |
+ ->IsOffTheRecord()) || |
+ (tabSwitcherIsActive && |
+ ![[[_browserLauncher browserViewInformation] otrTabModel] isEmpty])) { |
+ // Cover the largest area potentially shown in the app switcher, in case the |
+ // screenshot is reused in a different orientation or size class. |
+ CGRect screenBounds = [[UIScreen mainScreen] bounds]; |
+ CGFloat maxDimension = |
+ std::max(CGRectGetWidth(screenBounds), CGRectGetHeight(screenBounds)); |
+ _incognitoBlocker.reset([[UIView alloc] |
+ initWithFrame:CGRectMake(0, 0, maxDimension, maxDimension)]); |
+ InstallBackgroundInView(_incognitoBlocker); |
+ [_window addSubview:_incognitoBlocker]; |
+ } |
+ |
+ // Do not save cookies if it is already in progress. |
+ if ([[_browserLauncher browserViewInformation] currentBVC].browserState && |
+ !_savingCookies) { |
+ // Save cookies to disk. The empty critical closure guarantees that the task |
+ // will be run before backgrounding. |
+ scoped_refptr<net::URLRequestContextGetter> getter = |
+ [[_browserLauncher browserViewInformation] currentBVC] |
+ .browserState->GetRequestContext(); |
+ _savingCookies = YES; |
+ base::Closure criticalClosure = base::MakeCriticalClosure(base::BindBlock(^{ |
+ DCHECK_CURRENTLY_ON(web::WebThread::UI); |
+ _savingCookies = NO; |
+ })); |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, base::BindBlock(^{ |
+ net::CookieStoreIOS* store = static_cast<net::CookieStoreIOS*>( |
+ getter->GetURLRequestContext()->cookie_store()); |
+ // FlushStore() runs its callback on any thread. Jump back to UI. |
+ store->FlushStore(base::Bind(&PostTaskOnUIThread, criticalClosure)); |
+ })); |
+ } |
+ |
+ // Mark the startup as clean if it hasn't already been. |
+ [[DeferredInitializationRunner sharedInstance] |
+ runBlockIfNecessary:kStartupAttemptReset]; |
+ // Set date/time that the background fetch handler was called in the user |
+ // defaults. |
+ [MetricsMediator logDateInUserDefaults]; |
+ // Clear the memory warning flag since the app is now safely in background. |
+ [[PreviousSessionInfo sharedInstance] resetMemoryWarningFlag]; |
+ |
+ // Turn off uploading of crash reports and metrics, in case the method of |
+ // communication changes while in the background. |
+ [MetricsMediator disableReporting]; |
+ |
+ GetApplicationContext()->OnAppEnterBackground(); |
+ if (![[CrashReportBackgroundUploader sharedInstance] |
+ hasPendingCrashReportsToUploadAtStartup]) { |
+ [application setMinimumBackgroundFetchInterval: |
+ UIApplicationBackgroundFetchIntervalNever]; |
+ } |
+} |
+ |
+- (void)applicationWillEnterForeground:(UIApplication*)application |
+ metricsMediator:(MetricsMediator*)metricsMediator |
+ memoryHelper:(MemoryWarningHelper*)memoryHelper |
+ tabOpener:(id<TabOpening>)tabOpener |
+ appNavigation:(id<AppNavigation>)appNavigation { |
+ if ([_browserLauncher browserInitializationStage] < |
+ INITIALIZATION_STAGE_FOREGROUND) { |
+ // The application has been launched in background and the initialization |
+ // is not complete. |
+ [self initializeUI]; |
+ return; |
+ } |
+ if ([self isInSafeMode]) |
+ return; |
+ |
+ _applicationInBackground = NO; |
+ |
+ [_incognitoBlocker removeFromSuperview]; |
+ _incognitoBlocker.reset(); |
+ |
+ breakpad_helper::SetCurrentlyInBackground(false); |
+ |
+ // Update the state of metrics and crash reporting, as the method of |
+ // communication may have changed while the app was in the background. |
+ [metricsMediator updateMetricsStateBasedOnPrefsUserTriggered:NO]; |
+ |
+ // Send any feedback that might be still on temporary storage. |
+ ios::GetChromeBrowserProvider()->GetUserFeedbackProvider()->Synchronize(); |
+ |
+ GetApplicationContext()->OnAppEnterForeground(); |
+ |
+ [MetricsMediator |
+ logLaunchMetricsWithStartupInformation:_startupInformation |
+ browserViewInformation:[_browserLauncher |
+ browserViewInformation]]; |
+ [memoryHelper resetForegroundMemoryWarningCount]; |
+ |
+ // Check if a NTP tab should be opened; the tab will actually be opened in |
+ // |applicationDidBecomeActive| after the application gets prepared to |
+ // record user actions. |
+ // TODO(crbug.com/623491): opening a tab when the application is launched |
+ // without a tab should not be counted as a user action. Revisit the way tab |
+ // creation is counted. |
+ _shouldOpenNTPTabOnActive = [tabOpener |
+ shouldOpenNTPTabOnActivationOfTabModel:[[_browserLauncher |
+ browserViewInformation] |
+ currentTabModel]]; |
+ |
+ ios::ChromeBrowserState* currentBrowserState = |
+ [[_browserLauncher browserViewInformation] currentBrowserState]; |
+ if ([SignedInAccountsViewController |
+ shouldBePresentedForBrowserState:currentBrowserState]) { |
+ [appNavigation presentSignedInAccountsViewControllerForBrowserState: |
+ currentBrowserState]; |
+ } |
+ |
+ // If the current browser state is not OTR, check for cookie loss. |
+ if (currentBrowserState && !currentBrowserState->IsOffTheRecord() && |
+ currentBrowserState->GetOriginalChromeBrowserState() |
+ ->GetStatePath() |
+ .BaseName() |
+ .value() == kIOSChromeInitialBrowserState) { |
+ NSUInteger cookie_count = |
+ [[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] count]; |
+ UMA_HISTOGRAM_COUNTS_10000("CookieIOS.CookieCountOnForegrounding", |
+ cookie_count); |
+ net::CheckForCookieLoss(cookie_count, |
+ net::COOKIES_APPLICATION_FOREGROUNDED); |
+ } |
+} |
+ |
+- (void)resumeSessionWithTabOpener:(id<TabOpening>)tabOpener |
+ tabSwitcher:(id<TabSwitching>)tabSwitcher { |
+ [_incognitoBlocker removeFromSuperview]; |
+ _incognitoBlocker.reset(); |
+ |
+ DCHECK([_browserLauncher browserInitializationStage] == |
+ INITIALIZATION_STAGE_FOREGROUND); |
+ _sessionStartTime = base::TimeTicks::Now(); |
+ [[[_browserLauncher browserViewInformation] mainTabModel] |
+ resetSessionMetrics]; |
+ |
+ if ([_startupInformation startupParameters]) { |
+ [UserActivityHandler |
+ handleStartupParametersWithTabOpener:tabOpener |
+ startupInformation:_startupInformation |
+ browserViewInformation:[_browserLauncher |
+ browserViewInformation]]; |
+ } else if (_shouldOpenNTPTabOnActive) { |
+ if (![tabSwitcher openNewTabFromTabSwitcher]) { |
+ [[[_browserLauncher browserViewInformation] currentBVC] newTab:nil]; |
+ } |
+ _shouldOpenNTPTabOnActive = NO; |
+ } |
+ |
+ [MetricsMediator logStartupDuration:_startupInformation]; |
+} |
+ |
+- (void)applicationWillTerminate:(UIApplication*)application |
+ applicationNavigation:(id<AppNavigation>)appNavigation { |
+ if (_appIsTerminating) { |
+ // Previous handling of this method spun the runloop, resulting in |
+ // recursive calls; this does not appear to happen with the new shutdown |
+ // flow, but this is here to ensure that if it can happen, it gets noticed |
+ // and fixed. |
+ CHECK(false); |
+ } |
+ _appIsTerminating = YES; |
+ |
+ // Dismiss any UI that is presented on screen and that is listening for |
+ // profile notifications. |
+ if ([appNavigation settingsNavigationController]) |
+ [appNavigation closeSettingsAnimated:NO completion:nil]; |
+ |
+ // Clean up the device sharing manager before the main browser state is shut |
+ // down. |
+ if ([_browserLauncher browserInitializationStage] >= |
+ INITIALIZATION_STAGE_FOREGROUND) { |
+ [[_browserLauncher browserViewInformation] cleanDeviceSharingManager]; |
+ } |
+ |
+ // Cancel any in-flight distribution notifications. |
+ ios::GetChromeBrowserProvider() |
+ ->GetAppDistributionProvider() |
+ ->CancelDistributionNotifications(); |
+ |
+ // Halt the tabs, so any outstanding requests get cleaned up, without actually |
+ // closing the tabs. |
+ if ([_browserLauncher browserInitializationStage] >= |
+ INITIALIZATION_STAGE_FOREGROUND) { |
+ [[_browserLauncher browserViewInformation] haltAllTabs]; |
+ } |
+ |
+ // TODO(crbug.com/585700): remove this. |
+ web::RequestTrackerImpl::BlockUntilTrackersShutdown(); |
+ |
+ [_startupInformation stopChromeMain]; |
+ |
+ if (![[CrashReportBackgroundUploader sharedInstance] |
+ hasPendingCrashReportsToUploadAtStartup]) { |
+ [application setMinimumBackgroundFetchInterval: |
+ UIApplicationBackgroundFetchIntervalNever]; |
+ } |
+} |
+ |
+- (void)willResignActiveTabModel { |
+ if ([_browserLauncher browserInitializationStage] < |
+ INITIALIZATION_STAGE_FOREGROUND) { |
+ // If the application did not pass the foreground initialization stage, |
+ // there is no active tab model to resign. |
+ return; |
+ } |
+ |
+ // Set [_startupInformation isColdStart] to NO in anticipation of the next |
+ // time the app becomes active. |
+ [_startupInformation setIsColdStart:NO]; |
+ |
+ base::TimeDelta duration = base::TimeTicks::Now() - _sessionStartTime; |
+ UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration", duration); |
+ [[[_browserLauncher browserViewInformation] mainTabModel] |
+ recordSessionMetrics]; |
+} |
+ |
+- (BOOL)requiresHandlingAfterLaunchWithOptions:(NSDictionary*)launchOptions |
+ stateBackground:(BOOL)stateBackground { |
+ [_browserLauncher setLaunchOptions:launchOptions]; |
+ self.shouldPerformAdditionalDelegateHandling = YES; |
+ |
+ [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_BASIC]; |
+ if (!stateBackground) { |
+ [self initializeUI]; |
+ } |
+ |
+ return self.shouldPerformAdditionalDelegateHandling; |
+} |
+ |
+- (BOOL)isInSafeMode { |
+ return self.safeModeCoordinator != nil; |
+} |
+ |
+- (void)launchFromURLHandled:(BOOL)URLHandled { |
+ self.shouldPerformAdditionalDelegateHandling = !URLHandled; |
+} |
+ |
+#pragma mark - SafeModeCoordinatorDelegate Implementation |
+ |
+- (void)coordinatorDidExitSafeMode:(nonnull SafeModeCoordinator*)coordinator { |
+ self.safeModeCoordinator = nil; |
+ [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND]; |
+ [_mainApplicationDelegate |
+ applicationDidBecomeActive:[UIApplication sharedApplication]]; |
+} |
+ |
+#pragma mark - Internal methods. |
+ |
+- (void)initializeUI { |
+ _userInteracted = YES; |
+ [self saveLaunchDetailsToDefaults]; |
+ |
+ DCHECK([_window rootViewController] == nil); |
+ if ([SafeModeCoordinator shouldStart]) { |
+ SafeModeCoordinator* safeModeCoordinator = |
+ [[[SafeModeCoordinator alloc] initWithWindow:_window] autorelease]; |
+ |
+ self.safeModeCoordinator = safeModeCoordinator; |
+ [self.safeModeCoordinator setDelegate:self]; |
+ |
+ // Activate the main window, which will prompt the views to load. |
+ [_window makeKeyAndVisible]; |
+ |
+ [self.safeModeCoordinator start]; |
+ return; |
+ } |
+ |
+ // Don't add code here. Add it in MainController's |
+ // -startUpBrowserForegroundInitialization. |
+ DCHECK([_startupInformation isColdStart]); |
+ [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND]; |
+} |
+ |
+- (void)saveLaunchDetailsToDefaults { |
+ // Reset the failure count on first launch, increment it on other launches. |
+ if ([[PreviousSessionInfo sharedInstance] isFirstSessionAfterUpgrade]) |
+ crash_util::ResetFailedStartupAttemptCount(); |
+ else |
+ crash_util::IncrementFailedStartupAttemptCount(false); |
+ |
+ // The startup failure count *must* be synchronized now, since the crashes it |
+ // is trying to count are during startup. |
+ // -[PreviousSessionInfo beginRecordingCurrentSession] calls |synchronize| on |
+ // the user defaults, so leverage that to prevent calling it twice. |
+ |
+ // Start recording info about this session. |
+ [[PreviousSessionInfo sharedInstance] beginRecordingCurrentSession]; |
+} |
+ |
+@end |
+ |
+@implementation AppState (Testing) |
+ |
+- (instancetype) |
+initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher |
+ startupInformation:(id<StartupInformation>)startupInformation |
+ applicationDelegate:(MainApplicationDelegate*)applicationDelegate |
+ window:(UIWindow*)window |
+ shouldOpenNTP:(BOOL)shouldOpenNTP { |
+ self = [self initWithBrowserLauncher:browserLauncher |
+ startupInformation:startupInformation |
+ applicationDelegate:applicationDelegate]; |
+ if (self) { |
+ _shouldOpenNTPTabOnActive = shouldOpenNTP; |
+ } |
+ return self; |
+} |
+ |
+@end |