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