OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/app/application_delegate/app_state.h" |
| 6 |
| 7 #include "base/critical_closure.h" |
| 8 #import "base/mac/bind_objc_block.h" |
| 9 #import "base/mac/scoped_nsobject.h" |
| 10 #include "components/metrics/metrics_service.h" |
| 11 #import "ios/chrome/app/application_delegate/app_navigation.h" |
| 12 #import "ios/chrome/app/application_delegate/browser_launcher.h" |
| 13 #import "ios/chrome/app/application_delegate/memory_warning_helper.h" |
| 14 #import "ios/chrome/app/application_delegate/metrics_mediator.h" |
| 15 #import "ios/chrome/app/application_delegate/startup_information.h" |
| 16 #import "ios/chrome/app/application_delegate/tab_opening.h" |
| 17 #import "ios/chrome/app/application_delegate/tab_switching.h" |
| 18 #import "ios/chrome/app/application_delegate/user_activity_handler.h" |
| 19 #import "ios/chrome/app/deferred_initialization_runner.h" |
| 20 #import "ios/chrome/app/safe_mode/safe_mode_coordinator.h" |
| 21 #import "ios/chrome/app/safe_mode_crashing_modules_config.h" |
| 22 #include "ios/chrome/browser/application_context.h" |
| 23 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 24 #include "ios/chrome/browser/chrome_constants.h" |
| 25 #include "ios/chrome/browser/crash_loop_detection_util.h" |
| 26 #include "ios/chrome/browser/crash_report/breakpad_helper.h" |
| 27 #import "ios/chrome/browser/crash_report/crash_report_background_uploader.h" |
| 28 #import "ios/chrome/browser/device_sharing/device_sharing_manager.h" |
| 29 #import "ios/chrome/browser/geolocation/omnibox_geolocation_config.h" |
| 30 #import "ios/chrome/browser/metrics/previous_session_info.h" |
| 31 #import "ios/chrome/browser/ui/authentication/signed_in_accounts_view_controller
.h" |
| 32 #include "ios/chrome/browser/ui/background_generator.h" |
| 33 #import "ios/chrome/browser/ui/browser_view_controller.h" |
| 34 #import "ios/chrome/browser/ui/main/browser_view_information.h" |
| 35 #include "ios/net/cookies/cookie_store_ios.h" |
| 36 #include "ios/net/cookies/system_cookie_util.h" |
| 37 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 38 #include "ios/public/provider/chrome/browser/distribution/app_distribution_provi
der.h" |
| 39 #import "ios/public/provider/chrome/browser/user_feedback/user_feedback_provider
.h" |
| 40 #include "ios/web/net/request_tracker_impl.h" |
| 41 #include "net/url_request/url_request_context.h" |
| 42 |
| 43 namespace { |
| 44 // Helper method to post |closure| on the UI thread. |
| 45 void PostTaskOnUIThread(const base::Closure& closure) { |
| 46 web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, closure); |
| 47 } |
| 48 NSString* const kStartupAttemptReset = @"StartupAttempReset"; |
| 49 } // namespace |
| 50 |
| 51 @interface AppState ()<SafeModeCoordinatorDelegate> { |
| 52 // Container for startup information. |
| 53 base::WeakNSProtocol<id<StartupInformation>> _startupInformation; |
| 54 // Browser launcher to launch browser in different states. |
| 55 base::WeakNSProtocol<id<BrowserLauncher>> _browserLauncher; |
| 56 // UIApplicationDelegate for the application. |
| 57 base::WeakNSObject<MainApplicationDelegate> _mainApplicationDelegate; |
| 58 // Window for the application. |
| 59 base::WeakNSObject<UIWindow> _window; |
| 60 |
| 61 // Variables backing properties of same name. |
| 62 base::scoped_nsobject<SafeModeCoordinator> _safeModeCoordinator; |
| 63 |
| 64 // Start of the current session, used for UMA. |
| 65 base::TimeTicks _sessionStartTime; |
| 66 // YES if the app is currently in the process of terminating. |
| 67 BOOL _appIsTerminating; |
| 68 // Indicates if an NTP tab should be opened once the application has become |
| 69 // active. |
| 70 BOOL _shouldOpenNTPTabOnActive; |
| 71 // Interstitial view used to block any incognito tabs after backgrounding. |
| 72 base::scoped_nsobject<UIView> _incognitoBlocker; |
| 73 // Whether the application is currently in the background. |
| 74 // This is a workaround for rdar://22392526 where |
| 75 // -applicationDidEnterBackground: can be called twice. |
| 76 // TODO(crbug.com/546196): Remove this once rdar://22392526 is fixed. |
| 77 BOOL _applicationInBackground; |
| 78 // YES if cookies are currently being flushed to disk. |
| 79 BOOL _savingCookies; |
| 80 } |
| 81 |
| 82 // Safe mode coordinator. If this is non-nil, the app is displaying the safe |
| 83 // mode UI. |
| 84 @property(nonatomic, retain) SafeModeCoordinator* safeModeCoordinator; |
| 85 |
| 86 // Return value for -requiresHandlingAfterLaunchWithOptions that determines if |
| 87 // UIKit should make followup delegate calls such as |
| 88 // -performActionForShortcutItem or -openURL. |
| 89 @property(nonatomic, assign) BOOL shouldPerformAdditionalDelegateHandling; |
| 90 |
| 91 // This method is the first to be called when user launches the application. |
| 92 // Depending on the background tasks history, the state of the application is |
| 93 // either INITIALIZATION_STAGE_BASIC or INITIALIZATION_STAGE_BACKGROUND so this |
| 94 // step cannot be included in the |startUpBrowserToStage:| method. |
| 95 - (void)initializeUI; |
| 96 // Saves the current launch details to user defaults. |
| 97 - (void)saveLaunchDetailsToDefaults; |
| 98 |
| 99 @end |
| 100 |
| 101 @implementation AppState |
| 102 |
| 103 @synthesize shouldPerformAdditionalDelegateHandling = |
| 104 _shouldPerformAdditionalDelegateHandling; |
| 105 @synthesize userInteracted = _userInteracted; |
| 106 |
| 107 - (instancetype)init { |
| 108 NOTREACHED(); |
| 109 return nil; |
| 110 } |
| 111 |
| 112 - (instancetype) |
| 113 initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher |
| 114 startupInformation:(id<StartupInformation>)startupInformation |
| 115 applicationDelegate:(MainApplicationDelegate*)applicationDelegate { |
| 116 self = [super init]; |
| 117 if (self) { |
| 118 _startupInformation.reset(startupInformation); |
| 119 _browserLauncher.reset(browserLauncher); |
| 120 _mainApplicationDelegate.reset(applicationDelegate); |
| 121 } |
| 122 return self; |
| 123 } |
| 124 |
| 125 #pragma mark - Properties implementation |
| 126 |
| 127 - (SafeModeCoordinator*)safeModeCoordinator { |
| 128 return _safeModeCoordinator; |
| 129 } |
| 130 |
| 131 - (void)setSafeModeCoordinator:(SafeModeCoordinator*)safeModeCoordinator { |
| 132 _safeModeCoordinator.reset([safeModeCoordinator retain]); |
| 133 } |
| 134 |
| 135 - (void)setWindow:(UIWindow*)window { |
| 136 _window.reset(window); |
| 137 } |
| 138 |
| 139 - (UIWindow*)window { |
| 140 return _window; |
| 141 } |
| 142 |
| 143 #pragma mark - Public methods. |
| 144 |
| 145 - (void)applicationDidEnterBackground:(UIApplication*)application |
| 146 memoryHelper:(MemoryWarningHelper*)memoryHelper |
| 147 tabSwitcherIsActive:(BOOL)tabSwitcherIsActive { |
| 148 if ([self isInSafeMode]) { |
| 149 // Force a crash when backgrounding and in safe mode, so users don't get |
| 150 // stuck in safe mode. |
| 151 breakpad_helper::SetEnabled(false); |
| 152 exit(0); |
| 153 return; |
| 154 } |
| 155 |
| 156 if (_applicationInBackground) { |
| 157 return; |
| 158 } |
| 159 _applicationInBackground = YES; |
| 160 |
| 161 breakpad_helper::SetCurrentlyInBackground(true); |
| 162 |
| 163 if ([_browserLauncher browserInitializationStage] < |
| 164 INITIALIZATION_STAGE_FOREGROUND) { |
| 165 // The clean-up done in |-applicationDidEnterBackground:| is only valid for |
| 166 // the case when the application is started in foreground, so there is |
| 167 // nothing to clean up as the application was not initialized for foregound. |
| 168 // |
| 169 // From the stack trace of the crash bug http://crbug.com/437307 , it |
| 170 // seems that |-applicationDidEnterBackground:| may be called when the app |
| 171 // is started in background and before the initialization for background |
| 172 // stage is done. Note that the crash bug could not be reproduced though. |
| 173 return; |
| 174 } |
| 175 |
| 176 [MetricsMediator |
| 177 applicationDidEnterBackground:[memoryHelper |
| 178 foregroundMemoryWarningCount]]; |
| 179 |
| 180 [_startupInformation expireFirstUserActionRecorder]; |
| 181 |
| 182 // If the current BVC is incognito, or if we are in the tab switcher and there |
| 183 // are incognito tabs visible, place a full screen view containing the |
| 184 // switcher background to hide any incognito content. |
| 185 if (([[_browserLauncher browserViewInformation] currentBrowserState] && |
| 186 [[_browserLauncher browserViewInformation] currentBrowserState] |
| 187 ->IsOffTheRecord()) || |
| 188 (tabSwitcherIsActive && |
| 189 ![[[_browserLauncher browserViewInformation] otrTabModel] isEmpty])) { |
| 190 // Cover the largest area potentially shown in the app switcher, in case the |
| 191 // screenshot is reused in a different orientation or size class. |
| 192 CGRect screenBounds = [[UIScreen mainScreen] bounds]; |
| 193 CGFloat maxDimension = |
| 194 std::max(CGRectGetWidth(screenBounds), CGRectGetHeight(screenBounds)); |
| 195 _incognitoBlocker.reset([[UIView alloc] |
| 196 initWithFrame:CGRectMake(0, 0, maxDimension, maxDimension)]); |
| 197 InstallBackgroundInView(_incognitoBlocker); |
| 198 [_window addSubview:_incognitoBlocker]; |
| 199 } |
| 200 |
| 201 // Do not save cookies if it is already in progress. |
| 202 if ([[_browserLauncher browserViewInformation] currentBVC].browserState && |
| 203 !_savingCookies) { |
| 204 // Save cookies to disk. The empty critical closure guarantees that the task |
| 205 // will be run before backgrounding. |
| 206 scoped_refptr<net::URLRequestContextGetter> getter = |
| 207 [[_browserLauncher browserViewInformation] currentBVC] |
| 208 .browserState->GetRequestContext(); |
| 209 _savingCookies = YES; |
| 210 base::Closure criticalClosure = base::MakeCriticalClosure(base::BindBlock(^{ |
| 211 DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| 212 _savingCookies = NO; |
| 213 })); |
| 214 web::WebThread::PostTask( |
| 215 web::WebThread::IO, FROM_HERE, base::BindBlock(^{ |
| 216 net::CookieStoreIOS* store = static_cast<net::CookieStoreIOS*>( |
| 217 getter->GetURLRequestContext()->cookie_store()); |
| 218 // FlushStore() runs its callback on any thread. Jump back to UI. |
| 219 store->FlushStore(base::Bind(&PostTaskOnUIThread, criticalClosure)); |
| 220 })); |
| 221 } |
| 222 |
| 223 // Mark the startup as clean if it hasn't already been. |
| 224 [[DeferredInitializationRunner sharedInstance] |
| 225 runBlockIfNecessary:kStartupAttemptReset]; |
| 226 // Set date/time that the background fetch handler was called in the user |
| 227 // defaults. |
| 228 [MetricsMediator logDateInUserDefaults]; |
| 229 // Clear the memory warning flag since the app is now safely in background. |
| 230 [[PreviousSessionInfo sharedInstance] resetMemoryWarningFlag]; |
| 231 |
| 232 // Turn off uploading of crash reports and metrics, in case the method of |
| 233 // communication changes while in the background. |
| 234 [MetricsMediator disableReporting]; |
| 235 |
| 236 GetApplicationContext()->OnAppEnterBackground(); |
| 237 if (![[CrashReportBackgroundUploader sharedInstance] |
| 238 hasPendingCrashReportsToUploadAtStartup]) { |
| 239 [application setMinimumBackgroundFetchInterval: |
| 240 UIApplicationBackgroundFetchIntervalNever]; |
| 241 } |
| 242 } |
| 243 |
| 244 - (void)applicationWillEnterForeground:(UIApplication*)application |
| 245 metricsMediator:(MetricsMediator*)metricsMediator |
| 246 memoryHelper:(MemoryWarningHelper*)memoryHelper |
| 247 tabOpener:(id<TabOpening>)tabOpener |
| 248 appNavigation:(id<AppNavigation>)appNavigation { |
| 249 if ([_browserLauncher browserInitializationStage] < |
| 250 INITIALIZATION_STAGE_FOREGROUND) { |
| 251 // The application has been launched in background and the initialization |
| 252 // is not complete. |
| 253 [self initializeUI]; |
| 254 return; |
| 255 } |
| 256 if ([self isInSafeMode]) |
| 257 return; |
| 258 |
| 259 _applicationInBackground = NO; |
| 260 |
| 261 [_incognitoBlocker removeFromSuperview]; |
| 262 _incognitoBlocker.reset(); |
| 263 |
| 264 breakpad_helper::SetCurrentlyInBackground(false); |
| 265 |
| 266 // Update the state of metrics and crash reporting, as the method of |
| 267 // communication may have changed while the app was in the background. |
| 268 [metricsMediator updateMetricsStateBasedOnPrefsUserTriggered:NO]; |
| 269 |
| 270 // Send any feedback that might be still on temporary storage. |
| 271 ios::GetChromeBrowserProvider()->GetUserFeedbackProvider()->Synchronize(); |
| 272 |
| 273 GetApplicationContext()->OnAppEnterForeground(); |
| 274 |
| 275 [MetricsMediator |
| 276 logLaunchMetricsWithStartupInformation:_startupInformation |
| 277 browserViewInformation:[_browserLauncher |
| 278 browserViewInformation]]; |
| 279 [memoryHelper resetForegroundMemoryWarningCount]; |
| 280 |
| 281 // Check if a NTP tab should be opened; the tab will actually be opened in |
| 282 // |applicationDidBecomeActive| after the application gets prepared to |
| 283 // record user actions. |
| 284 // TODO(crbug.com/623491): opening a tab when the application is launched |
| 285 // without a tab should not be counted as a user action. Revisit the way tab |
| 286 // creation is counted. |
| 287 _shouldOpenNTPTabOnActive = [tabOpener |
| 288 shouldOpenNTPTabOnActivationOfTabModel:[[_browserLauncher |
| 289 browserViewInformation] |
| 290 currentTabModel]]; |
| 291 |
| 292 ios::ChromeBrowserState* currentBrowserState = |
| 293 [[_browserLauncher browserViewInformation] currentBrowserState]; |
| 294 if ([SignedInAccountsViewController |
| 295 shouldBePresentedForBrowserState:currentBrowserState]) { |
| 296 [appNavigation presentSignedInAccountsViewControllerForBrowserState: |
| 297 currentBrowserState]; |
| 298 } |
| 299 |
| 300 // If the current browser state is not OTR, check for cookie loss. |
| 301 if (currentBrowserState && !currentBrowserState->IsOffTheRecord() && |
| 302 currentBrowserState->GetOriginalChromeBrowserState() |
| 303 ->GetStatePath() |
| 304 .BaseName() |
| 305 .value() == kIOSChromeInitialBrowserState) { |
| 306 NSUInteger cookie_count = |
| 307 [[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] count]; |
| 308 UMA_HISTOGRAM_COUNTS_10000("CookieIOS.CookieCountOnForegrounding", |
| 309 cookie_count); |
| 310 net::CheckForCookieLoss(cookie_count, |
| 311 net::COOKIES_APPLICATION_FOREGROUNDED); |
| 312 } |
| 313 } |
| 314 |
| 315 - (void)resumeSessionWithTabOpener:(id<TabOpening>)tabOpener |
| 316 tabSwitcher:(id<TabSwitching>)tabSwitcher { |
| 317 [_incognitoBlocker removeFromSuperview]; |
| 318 _incognitoBlocker.reset(); |
| 319 |
| 320 DCHECK([_browserLauncher browserInitializationStage] == |
| 321 INITIALIZATION_STAGE_FOREGROUND); |
| 322 _sessionStartTime = base::TimeTicks::Now(); |
| 323 [[[_browserLauncher browserViewInformation] mainTabModel] |
| 324 resetSessionMetrics]; |
| 325 |
| 326 if ([_startupInformation startupParameters]) { |
| 327 [UserActivityHandler |
| 328 handleStartupParametersWithTabOpener:tabOpener |
| 329 startupInformation:_startupInformation |
| 330 browserViewInformation:[_browserLauncher |
| 331 browserViewInformation]]; |
| 332 } else if (_shouldOpenNTPTabOnActive) { |
| 333 if (![tabSwitcher openNewTabFromTabSwitcher]) { |
| 334 [[[_browserLauncher browserViewInformation] currentBVC] newTab:nil]; |
| 335 } |
| 336 _shouldOpenNTPTabOnActive = NO; |
| 337 } |
| 338 |
| 339 [MetricsMediator logStartupDuration:_startupInformation]; |
| 340 } |
| 341 |
| 342 - (void)applicationWillTerminate:(UIApplication*)application |
| 343 applicationNavigation:(id<AppNavigation>)appNavigation { |
| 344 if (_appIsTerminating) { |
| 345 // Previous handling of this method spun the runloop, resulting in |
| 346 // recursive calls; this does not appear to happen with the new shutdown |
| 347 // flow, but this is here to ensure that if it can happen, it gets noticed |
| 348 // and fixed. |
| 349 CHECK(false); |
| 350 } |
| 351 _appIsTerminating = YES; |
| 352 |
| 353 // Dismiss any UI that is presented on screen and that is listening for |
| 354 // profile notifications. |
| 355 if ([appNavigation settingsNavigationController]) |
| 356 [appNavigation closeSettingsAnimated:NO completion:nil]; |
| 357 |
| 358 // Clean up the device sharing manager before the main browser state is shut |
| 359 // down. |
| 360 if ([_browserLauncher browserInitializationStage] >= |
| 361 INITIALIZATION_STAGE_FOREGROUND) { |
| 362 [[_browserLauncher browserViewInformation] cleanDeviceSharingManager]; |
| 363 } |
| 364 |
| 365 // Cancel any in-flight distribution notifications. |
| 366 ios::GetChromeBrowserProvider() |
| 367 ->GetAppDistributionProvider() |
| 368 ->CancelDistributionNotifications(); |
| 369 |
| 370 // Halt the tabs, so any outstanding requests get cleaned up, without actually |
| 371 // closing the tabs. |
| 372 if ([_browserLauncher browserInitializationStage] >= |
| 373 INITIALIZATION_STAGE_FOREGROUND) { |
| 374 [[_browserLauncher browserViewInformation] haltAllTabs]; |
| 375 } |
| 376 |
| 377 // TODO(crbug.com/585700): remove this. |
| 378 web::RequestTrackerImpl::BlockUntilTrackersShutdown(); |
| 379 |
| 380 [_startupInformation stopChromeMain]; |
| 381 |
| 382 if (![[CrashReportBackgroundUploader sharedInstance] |
| 383 hasPendingCrashReportsToUploadAtStartup]) { |
| 384 [application setMinimumBackgroundFetchInterval: |
| 385 UIApplicationBackgroundFetchIntervalNever]; |
| 386 } |
| 387 } |
| 388 |
| 389 - (void)willResignActiveTabModel { |
| 390 if ([_browserLauncher browserInitializationStage] < |
| 391 INITIALIZATION_STAGE_FOREGROUND) { |
| 392 // If the application did not pass the foreground initialization stage, |
| 393 // there is no active tab model to resign. |
| 394 return; |
| 395 } |
| 396 |
| 397 // Set [_startupInformation isColdStart] to NO in anticipation of the next |
| 398 // time the app becomes active. |
| 399 [_startupInformation setIsColdStart:NO]; |
| 400 |
| 401 base::TimeDelta duration = base::TimeTicks::Now() - _sessionStartTime; |
| 402 UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration", duration); |
| 403 [[[_browserLauncher browserViewInformation] mainTabModel] |
| 404 recordSessionMetrics]; |
| 405 } |
| 406 |
| 407 - (BOOL)requiresHandlingAfterLaunchWithOptions:(NSDictionary*)launchOptions |
| 408 stateBackground:(BOOL)stateBackground { |
| 409 [_browserLauncher setLaunchOptions:launchOptions]; |
| 410 self.shouldPerformAdditionalDelegateHandling = YES; |
| 411 |
| 412 [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_BASIC]; |
| 413 if (!stateBackground) { |
| 414 [self initializeUI]; |
| 415 } |
| 416 |
| 417 return self.shouldPerformAdditionalDelegateHandling; |
| 418 } |
| 419 |
| 420 - (BOOL)isInSafeMode { |
| 421 return self.safeModeCoordinator != nil; |
| 422 } |
| 423 |
| 424 - (void)launchFromURLHandled:(BOOL)URLHandled { |
| 425 self.shouldPerformAdditionalDelegateHandling = !URLHandled; |
| 426 } |
| 427 |
| 428 #pragma mark - SafeModeCoordinatorDelegate Implementation |
| 429 |
| 430 - (void)coordinatorDidExitSafeMode:(nonnull SafeModeCoordinator*)coordinator { |
| 431 self.safeModeCoordinator = nil; |
| 432 [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND]; |
| 433 [_mainApplicationDelegate |
| 434 applicationDidBecomeActive:[UIApplication sharedApplication]]; |
| 435 } |
| 436 |
| 437 #pragma mark - Internal methods. |
| 438 |
| 439 - (void)initializeUI { |
| 440 _userInteracted = YES; |
| 441 [self saveLaunchDetailsToDefaults]; |
| 442 |
| 443 DCHECK([_window rootViewController] == nil); |
| 444 if ([SafeModeCoordinator shouldStart]) { |
| 445 SafeModeCoordinator* safeModeCoordinator = |
| 446 [[[SafeModeCoordinator alloc] initWithWindow:_window] autorelease]; |
| 447 |
| 448 self.safeModeCoordinator = safeModeCoordinator; |
| 449 [self.safeModeCoordinator setDelegate:self]; |
| 450 |
| 451 // Activate the main window, which will prompt the views to load. |
| 452 [_window makeKeyAndVisible]; |
| 453 |
| 454 [self.safeModeCoordinator start]; |
| 455 return; |
| 456 } |
| 457 |
| 458 // Don't add code here. Add it in MainController's |
| 459 // -startUpBrowserForegroundInitialization. |
| 460 DCHECK([_startupInformation isColdStart]); |
| 461 [_browserLauncher startUpBrowserToStage:INITIALIZATION_STAGE_FOREGROUND]; |
| 462 } |
| 463 |
| 464 - (void)saveLaunchDetailsToDefaults { |
| 465 // Reset the failure count on first launch, increment it on other launches. |
| 466 if ([[PreviousSessionInfo sharedInstance] isFirstSessionAfterUpgrade]) |
| 467 crash_util::ResetFailedStartupAttemptCount(); |
| 468 else |
| 469 crash_util::IncrementFailedStartupAttemptCount(false); |
| 470 |
| 471 // The startup failure count *must* be synchronized now, since the crashes it |
| 472 // is trying to count are during startup. |
| 473 // -[PreviousSessionInfo beginRecordingCurrentSession] calls |synchronize| on |
| 474 // the user defaults, so leverage that to prevent calling it twice. |
| 475 |
| 476 // Start recording info about this session. |
| 477 [[PreviousSessionInfo sharedInstance] beginRecordingCurrentSession]; |
| 478 } |
| 479 |
| 480 @end |
| 481 |
| 482 @implementation AppState (Testing) |
| 483 |
| 484 - (instancetype) |
| 485 initWithBrowserLauncher:(id<BrowserLauncher>)browserLauncher |
| 486 startupInformation:(id<StartupInformation>)startupInformation |
| 487 applicationDelegate:(MainApplicationDelegate*)applicationDelegate |
| 488 window:(UIWindow*)window |
| 489 shouldOpenNTP:(BOOL)shouldOpenNTP { |
| 490 self = [self initWithBrowserLauncher:browserLauncher |
| 491 startupInformation:startupInformation |
| 492 applicationDelegate:applicationDelegate]; |
| 493 if (self) { |
| 494 _shouldOpenNTPTabOnActive = shouldOpenNTP; |
| 495 } |
| 496 return self; |
| 497 } |
| 498 |
| 499 @end |
OLD | NEW |