blob: 676ef7514de44900dcbb42b900ed40351c79783e [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 <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/critical_closure.h"
#import "base/ios/crb_protocol_observers.h"
#include "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "components/feature_engagement/public/event_constants.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/metrics/metrics_service.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/main_application_delegate.h"
#include "ios/chrome/browser/application_context.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/browsing_data/browsing_data_remover.h"
#include "ios/chrome/browser/browsing_data/browsing_data_remover_factory.h"
#include "ios/chrome/browser/chrome_constants.h"
#include "ios/chrome/browser/crash_report/breakpad_helper.h"
#include "ios/chrome/browser/crash_report/crash_keys_helper.h"
#include "ios/chrome/browser/crash_report/crash_loop_detection_util.h"
#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
#import "ios/chrome/browser/geolocation/omnibox_geolocation_config.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/metrics/ios_profile_session_durations_service.h"
#import "ios/chrome/browser/metrics/ios_profile_session_durations_service_factory.h"
#import "ios/chrome/browser/metrics/previous_session_info.h"
#import "ios/chrome/browser/signin/authentication_service.h"
#import "ios/chrome/browser/signin/authentication_service_factory.h"
#import "ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller.h"
#import "ios/chrome/browser/ui/commands/application_commands.h"
#import "ios/chrome/browser/ui/commands/browser_commands.h"
#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
#import "ios/chrome/browser/ui/commands/help_commands.h"
#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
#import "ios/chrome/browser/ui/main/browser_interface_provider.h"
#import "ios/chrome/browser/ui/main/scene_delegate.h"
#import "ios/chrome/browser/ui/safe_mode/safe_mode_coordinator.h"
#import "ios/chrome/browser/ui/scoped_ui_blocker/scoped_ui_blocker.h"
#import "ios/chrome/browser/ui/util/multi_window_support.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#include "ios/chrome/browser/web_state_list/session_metrics.h"
#import "ios/chrome/browser/web_state_list/web_state_list_metrics_browser_agent.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/public/thread/web_task_traits.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.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(base::OnceClosure closure) {
base::PostTask(FROM_HERE, {web::WebThread::UI}, std::move(closure));
}
NSString* const kStartupAttemptReset = @"StartupAttempReset";
} // namespace
#pragma mark - AppStateObserverList
@interface AppStateObserverList : CRBProtocolObservers <AppStateObserver>
@end
@implementation AppStateObserverList
@end
#pragma mark - AppState
@interface AppState () <SafeModeCoordinatorDelegate> {
// Browser launcher to launch browser in different states.
__weak id<BrowserLauncher> _browserLauncher;
// UIApplicationDelegate for the application.
__weak MainApplicationDelegate* _mainApplicationDelegate;
// 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;
// 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;
// Multiwindow UI blocker used when safe mode is active.
std::unique_ptr<ScopedUIBlocker> _safeModeBlocker;
}
// Container for observers.
@property(nonatomic, strong) AppStateObserverList* observers;
// Safe mode coordinator. If this is non-nil, the app is displaying the safe
// mode UI.
@property(nonatomic, strong) SafeModeCoordinator* safeModeCoordinator;
// Flag to track when the app is in safe mode.
@property(nonatomic, assign, getter=isInSafeMode) BOOL inSafeMode;
// 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;
// This flag is set when the first scene has activated since the startup, and
// never reset.
@property(nonatomic, assign) BOOL firstSceneHasActivated;
// The current blocker target if any.
@property(nonatomic, weak, readwrite) id<UIBlockerTarget> uiBlockerTarget;
// The counter of currently shown blocking UIs. Do not use this directly,
// instead use incrementBlockingUICounterForScene: and
// incrementBlockingUICounterForScene or the ScopedUIBlocker.
@property(nonatomic, assign) NSUInteger blockingUICounter;
// Agents attached to this app state.
@property(nonatomic, strong) NSMutableArray<id<AppStateAgent>>* agents;
@end
@implementation AppState
@synthesize shouldPerformAdditionalDelegateHandling =
_shouldPerformAdditionalDelegateHandling;
@synthesize userInteracted = _userInteracted;
- (instancetype)
initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher
startupInformation:(id<StartupInformation>)startupInformation
applicationDelegate:(MainApplicationDelegate*)applicationDelegate {
self = [super init];
if (self) {
_observers = [AppStateObserverList
observersWithProtocol:@protocol(AppStateObserver)];
_agents = [[NSMutableArray alloc] init];
_startupInformation = startupInformation;
_browserLauncher = browserLauncher;
_mainApplicationDelegate = applicationDelegate;
_appCommandDispatcher = [[CommandDispatcher alloc] init];
// Subscribe to scene-related notifications when using scenes.
// Note these are also sent when not using scenes, so avoid subscribing to
// them unless necessary.
if (IsSceneStartupSupported()) {
if (@available(iOS 13, *)) {
// Subscribe to scene connection notifications.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(sceneWillConnect:)
name:UISceneWillConnectNotification
object:nil];
}
}
}
return self;
}
#pragma mark - Properties implementation
- (void)setMainSceneState:(SceneState*)mainSceneState {
DCHECK(!_mainSceneState);
_mainSceneState = mainSceneState;
[self.observers appState:self sceneConnected:mainSceneState];
}
- (SafeModeCoordinator*)safeModeCoordinator {
return _safeModeCoordinator;
}
- (void)setSafeModeCoordinator:(SafeModeCoordinator*)safeModeCoordinator {
_safeModeCoordinator = safeModeCoordinator;
}
- (void)setUiBlockerTarget:(id<UIBlockerTarget>)uiBlockerTarget {
_uiBlockerTarget = uiBlockerTarget;
for (SceneState* scene in self.connectedScenes) {
// When there's a scene with blocking UI, all other scenes should show the
// overlay.
BOOL shouldPresentOverlay =
(uiBlockerTarget != nil) && (scene != uiBlockerTarget);
scene.presentingModalOverlay = shouldPresentOverlay;
}
}
#pragma mark - Public methods.
- (void)applicationDidEnterBackground:(UIApplication*)application
memoryHelper:(MemoryWarningHelper*)memoryHelper {
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;
ChromeBrowserState* browserState =
_browserLauncher.interfaceProvider.mainInterface.browserState;
if (browserState) {
AuthenticationServiceFactory::GetForBrowserState(browserState)
->OnApplicationDidEnterBackground();
}
crash_keys::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]];
[self.startupInformation expireFirstUserActionRecorder];
// Do not save cookies if it is already in progress.
id<BrowserInterface> currentInterface =
_browserLauncher.interfaceProvider.currentInterface;
if (currentInterface.browserState && !_savingCookies) {
// Save cookies to disk. The empty critical closure guarantees that the task
// will be run before backgrounding.
scoped_refptr<net::URLRequestContextGetter> getter =
currentInterface.browserState->GetRequestContext();
_savingCookies = YES;
__block base::OnceClosure criticalClosure = base::MakeCriticalClosure(
"applicationDidEnterBackground:_savingCookies", base::BindOnce(^{
DCHECK_CURRENTLY_ON(web::WebThread::UI);
_savingCookies = NO;
}));
base::PostTask(
FROM_HERE, {web::WebThread::IO}, base::BindOnce(^{
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::BindOnce(&PostTaskOnUIThread, std::move(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();
}
- (void)applicationWillEnterForeground:(UIApplication*)application
metricsMediator:(MetricsMediator*)metricsMediator
memoryHelper:(MemoryWarningHelper*)memoryHelper {
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] || !_applicationInBackground)
return;
_applicationInBackground = NO;
ChromeBrowserState* browserState =
_browserLauncher.interfaceProvider.mainInterface.browserState;
if (browserState) {
AuthenticationServiceFactory::GetForBrowserState(browserState)
->OnApplicationWillEnterForeground();
}
crash_keys::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:self.startupInformation
connectedScenes:self.connectedScenes];
[memoryHelper resetForegroundMemoryWarningCount];
// If the current browser state is not OTR, check for cookie loss.
ChromeBrowserState* currentBrowserState =
_browserLauncher.interfaceProvider.currentInterface.browserState;
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);
}
if (currentBrowserState) {
// Send the "Chrome Opened" event to the feature_engagement::Tracker on a
// warm start.
feature_engagement::TrackerFactory::GetForBrowserState(currentBrowserState)
->NotifyEvent(feature_engagement::events::kChromeOpened);
}
base::RecordAction(base::UserMetricsAction("MobileWillEnterForeground"));
}
- (void)resumeSessionWithTabOpener:(id<TabOpening>)tabOpener
tabSwitcher:(id<TabSwitching>)tabSwitcher
connectionInformation:
(id<ConnectionInformation>)connectionInformation {
DCHECK(!IsSceneStartupSupported());
DCHECK([_browserLauncher browserInitializationStage] ==
INITIALIZATION_STAGE_FOREGROUND);
_sessionStartTime = base::TimeTicks::Now();
id<BrowserInterface> currentInterface =
_browserLauncher.interfaceProvider.currentInterface;
CommandDispatcher* dispatcher =
currentInterface.browser->GetCommandDispatcher();
if ([connectionInformation startupParameters]) {
[UserActivityHandler
handleStartupParametersWithTabOpener:tabOpener
connectionInformation:connectionInformation
startupInformation:self.startupInformation
browserState:currentInterface.browserState];
} else if ([tabOpener shouldOpenNTPTabOnActivationOfBrowser:currentInterface
.browser]) {
// Opens an NTP if needed.
// 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.
if (![tabSwitcher openNewTabFromTabSwitcher]) {
OpenNewTabCommand* command =
[OpenNewTabCommand commandWithIncognito:currentInterface.incognito];
[HandlerForProtocol(dispatcher, ApplicationCommands)
openURLInNewTab:command];
}
} else {
[HandlerForProtocol(dispatcher, HelpCommands) showHelpBubbleIfEligible];
}
IOSProfileSessionDurationsService* psdService =
IOSProfileSessionDurationsServiceFactory::GetForBrowserState(
currentInterface.browserState);
if (psdService)
psdService->OnSessionStarted(_sessionStartTime);
[MetricsMediator logStartupDuration:self.startupInformation
connectionInformation:connectionInformation];
}
- (void)applicationWillTerminate:(UIApplication*)application {
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;
// Cancel any in-flight distribution notifications.
CHECK(ios::GetChromeBrowserProvider());
ios::GetChromeBrowserProvider()
->GetAppDistributionProvider()
->CancelDistributionNotifications();
// Halt the tabs, so any outstanding requests get cleaned up, without actually
// closing the tabs. Set the BVC to inactive to cancel all the dialogs.
// Don't do this if there are no scenes, since there's no defined interface
// provider (and no tabs)
// TODO(crbug.com/1113097): Factor out this check by not having app layer
// logic use interface providers.
BOOL scenesAreAvailable = [self connectedScenes].count > 0;
if (scenesAreAvailable && [_browserLauncher browserInitializationStage] >=
INITIALIZATION_STAGE_FOREGROUND) {
_browserLauncher.interfaceProvider.currentInterface.userInteractionEnabled =
NO;
}
// Trigger UI teardown on iOS 12.
if (!IsSceneStartupSupported()) {
self.mainSceneState.activationLevel = SceneActivationLevelUnattached;
}
[self.startupInformation stopChromeMain];
}
- (void)application:(UIApplication*)application
didDiscardSceneSessions:(NSSet<UISceneSession*>*)sceneSessions
API_AVAILABLE(ios(13)) {
NSMutableArray<NSString*>* sessionIDs =
[NSMutableArray arrayWithCapacity:sceneSessions.count];
for (UISceneSession* session in sceneSessions) {
[sessionIDs addObject:session.persistentIdentifier];
}
ChromeBrowserState* browserState =
_browserLauncher.interfaceProvider.mainInterface.browserState;
BrowsingDataRemoverFactory::GetForBrowserState(browserState)
->RemoveSessionsData(sessionIDs);
ChromeBrowserState* incognitoBrowserState =
_browserLauncher.interfaceProvider.incognitoInterface.browserState;
BrowsingDataRemoverFactory::GetForBrowserState(incognitoBrowserState)
->RemoveSessionsData(sessionIDs);
}
- (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 [self.startupInformation isColdStart] to NO in anticipation of the next
// time the app becomes active.
[self.startupInformation setIsColdStart:NO];
id<BrowserInterface> currentInterface =
_browserLauncher.interfaceProvider.currentInterface;
base::TimeDelta duration = base::TimeTicks::Now() - _sessionStartTime;
UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration", duration);
UMA_HISTOGRAM_CUSTOM_TIMES("Session.TotalDurationMax1Day", duration,
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromHours(24), 50);
// Record session metrics (currentInterface.browserState may be null during
// tests).
if (currentInterface.browserState) {
ChromeBrowserState* mainChromeBrowserState =
currentInterface.browserState->GetOriginalChromeBrowserState();
SessionMetrics::FromBrowserState(mainChromeBrowserState)
->RecordAndClearSessionMetrics(
MetricsToRecordFlags::kOpenedTabCount |
MetricsToRecordFlags::kClosedTabCount |
MetricsToRecordFlags::kActivatedTabCount);
if (mainChromeBrowserState->HasOffTheRecordChromeBrowserState()) {
ChromeBrowserState* otrChromeBrowserState =
mainChromeBrowserState->GetOffTheRecordChromeBrowserState();
SessionMetrics::FromBrowserState(otrChromeBrowserState)
->RecordAndClearSessionMetrics(MetricsToRecordFlags::kNoMetrics);
}
}
if (currentInterface.browserState) {
IOSProfileSessionDurationsService* psdService =
IOSProfileSessionDurationsServiceFactory::GetForBrowserState(
currentInterface.browserState);
if (psdService)
psdService->OnSessionEnded(duration);
}
}
- (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;
}
- (void)launchFromURLHandled:(BOOL)URLHandled {
self.shouldPerformAdditionalDelegateHandling = !URLHandled;
}
- (void)addObserver:(id<SceneStateObserver>)observer {
[self.observers addObserver:observer];
}
- (void)removeObserver:(id<SceneStateObserver>)observer {
[self.observers removeObserver:observer];
}
- (void)addAgent:(id<AppStateAgent>)agent {
DCHECK(agent);
[self.agents addObject:agent];
[agent setAppState:self];
}
#pragma mark - Multiwindow-related
- (SceneState*)foregroundActiveScene {
for (SceneState* sceneState in self.connectedScenes) {
if (sceneState.activationLevel == SceneActivationLevelForegroundActive) {
return sceneState;
}
}
return nil;
}
- (NSArray<SceneState*>*)connectedScenes {
if (IsSceneStartupSupported()) {
if (@available(iOS 13, *)) {
NSMutableArray* sceneStates = [[NSMutableArray alloc] init];
NSSet* connectedScenes =
[UIApplication sharedApplication].connectedScenes;
for (UIWindowScene* scene in connectedScenes) {
if (![scene.delegate isKindOfClass:[SceneDelegate class]]) {
// This might happen in tests.
// TODO(crbug.com/1113097): This shouldn't be needed.
[sceneStates addObject:[[SceneState alloc] initWithAppState:self]];
continue;
}
SceneDelegate* sceneDelegate =
base::mac::ObjCCastStrict<SceneDelegate>(scene.delegate);
[sceneStates addObject:sceneDelegate.sceneState];
}
return sceneStates;
}
} else if (self.mainSceneState) {
return @[ self.mainSceneState ];
}
// This can happen if the app is terminating before any scenes are set up.
return @[];
}
- (void)setLastTappedWindow:(UIWindow*)window {
if (_lastTappedWindow == window) {
return;
}
_lastTappedWindow = window;
[self.observers appState:self lastTappedWindowChanged:window];
}
#pragma mark - SafeModeCoordinatorDelegate Implementation
- (void)coordinatorDidExitSafeMode:(nonnull SafeModeCoordinator*)coordinator {
[self stopSafeMode];
[_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND];
[self.observers appStateDidExitSafeMode:self];
[_mainApplicationDelegate
applicationDidBecomeActive:[UIApplication sharedApplication]];
}
#pragma mark - Internal methods.
- (void)startSafeMode {
if (!IsSceneStartupSupported()) {
self.mainSceneState.activationLevel = SceneActivationLevelForegroundActive;
}
DCHECK(self.foregroundActiveScene);
DCHECK(!_safeModeBlocker);
SafeModeCoordinator* safeModeCoordinator = [[SafeModeCoordinator alloc]
initWithWindow:self.foregroundActiveScene.window];
self.safeModeCoordinator = safeModeCoordinator;
[self.safeModeCoordinator setDelegate:self];
// Activate the main window, which will prompt the views to load.
[self.foregroundActiveScene.window makeKeyAndVisible];
[self.safeModeCoordinator start];
if (IsMultipleScenesSupported()) {
_safeModeBlocker =
std::make_unique<ScopedUIBlocker>(self.foregroundActiveScene);
}
}
- (void)stopSafeMode {
if (_safeModeBlocker) {
_safeModeBlocker.reset();
}
self.safeModeCoordinator = nil;
self.inSafeMode = NO;
}
- (void)initializeUI {
_userInteracted = YES;
[self saveLaunchDetailsToDefaults];
if ([SafeModeCoordinator shouldStart]) {
self.inSafeMode = YES;
if (!IsMultiwindowSupported()) {
// Start safe mode immediately. Otherwise it should only start when a
// scene is connected and activates to allow displaying the safe mode UI.
[self startSafeMode];
}
return;
}
// Don't add code here. Add it in MainController's
// -startUpBrowserForegroundInitialization.
DCHECK([self.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];
}
#pragma mark - UIBlockerManager
- (void)incrementBlockingUICounterForTarget:(id<UIBlockerTarget>)target {
DCHECK(self.uiBlockerTarget == nil || target == self.uiBlockerTarget)
<< "Another scene is already showing a blocking UI!";
self.blockingUICounter++;
if (!self.uiBlockerTarget) {
self.uiBlockerTarget = target;
}
}
- (void)decrementBlockingUICounterForTarget:(id<UIBlockerTarget>)target {
DCHECK(self.blockingUICounter > 0 && self.uiBlockerTarget == target);
self.blockingUICounter--;
if (self.blockingUICounter == 0) {
self.uiBlockerTarget = nil;
}
}
- (id<UIBlockerTarget>)currentUIBlocker {
return self.uiBlockerTarget;
}
- (void)sceneState:(SceneState*)sceneState
transitionedToActivationLevel:(SceneActivationLevel)level {
if (level >= SceneActivationLevelForegroundActive) {
if (!self.firstSceneHasActivated) {
self.firstSceneHasActivated = YES;
[self.observers appState:self firstSceneActivated:sceneState];
if (self.isInSafeMode) {
// Safe mode can only be started when there's a window, so the actual
// safe mode has been postponed until now.
[self startSafeMode];
}
}
sceneState.presentingModalOverlay =
(self.uiBlockerTarget != nil) && (self.uiBlockerTarget != sceneState);
}
}
- (void)sceneWillConnect:(NSNotification*)notification {
DCHECK(IsSceneStartupSupported());
if (@available(iOS 13, *)) {
UIWindowScene* scene =
base::mac::ObjCCastStrict<UIWindowScene>(notification.object);
SceneDelegate* sceneDelegate =
base::mac::ObjCCastStrict<SceneDelegate>(scene.delegate);
SceneState* sceneState = sceneDelegate.sceneState;
DCHECK(sceneState);
[self.observers appState:self sceneConnected:sceneState];
}
}
@end