blob: 2c4105206e5c86a521c309eabc179986dcfba459 [file] [log] [blame]
// 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"
#include "components/metrics/metrics_service.h"
#import "ios/chrome/app/main_application_delegate.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"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
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.
__weak id<StartupInformation> _startupInformation;
// Browser launcher to launch browser in different states.
__weak id<BrowserLauncher> _browserLauncher;
// UIApplicationDelegate for the application.
__weak MainApplicationDelegate* _mainApplicationDelegate;
// Window for the application.
__weak UIWindow* _window;
// Variables backing properties of same name.
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.
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, strong) 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 = startupInformation;
_browserLauncher = browserLauncher;
_mainApplicationDelegate = applicationDelegate;
}
return self;
}
#pragma mark - Properties implementation
- (SafeModeCoordinator*)safeModeCoordinator {
return _safeModeCoordinator;
}
- (void)setSafeModeCoordinator:(SafeModeCoordinator*)safeModeCoordinator {
_safeModeCoordinator = safeModeCoordinator;
}
- (void)setWindow:(UIWindow*)window {
_window = 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 = [[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::BindBlockArc(^{
DCHECK_CURRENTLY_ON(web::WebThread::UI);
_savingCookies = NO;
}));
web::WebThread::PostTask(
web::WebThread::IO, FROM_HERE, base::BindBlockArc(^{
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 = nil;
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 = nil;
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];
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