// Copyright 2012 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. #if !defined(NDEBUG) #import "ios/chrome/app/UIApplication+ExitsOnSuspend.h" #include "base/ios/block_types.h" #include "base/logging.h" #include "base/threading/thread_restrictions.h" NSString* const kExitsOnSuspend = @"EnableExitsOnSuspend"; namespace { int backgroundTasksCount = 0; UIBackgroundTaskIdentifier countTaskIdentifier = UIBackgroundTaskInvalid; // Perform a block on the main thread. Asynchronously if the thread is not the // main thread, synchronously if the thread is the main thread. void ExecuteBlockOnMainThread(ProceduralBlock block) { if ([NSThread isMainThread]) block(); else dispatch_async(dispatch_get_main_queue(), block); } } // namespace // Category defining interposing methods. These methods keep a tally of the // background tasks count. @implementation UIApplication (BackgroundTasksCounter) // Method to replace -beginBackgroundTaskWithExpirationHandler:. The original // method is called within. - (UIBackgroundTaskIdentifier) cr_interpose_beginBackgroundTaskWithExpirationHandler: (ProceduralBlock)handler { UIBackgroundTaskIdentifier identifier = [self cr_interpose_beginBackgroundTaskWithExpirationHandler:handler]; if (identifier != UIBackgroundTaskInvalid) { ExecuteBlockOnMainThread(^{ backgroundTasksCount++; }); } return identifier; } // Method to replace -endBackgroundTask:. The original method is called within. - (void)cr_interpose_endBackgroundTask:(UIBackgroundTaskIdentifier)identifier { if (identifier != UIBackgroundTaskInvalid) ExecuteBlockOnMainThread(^{ backgroundTasksCount--; }); [self cr_interpose_endBackgroundTask:identifier]; } @end @interface UIApplication (ExitsOnSuspend_Private) // Terminate the app immediately. exit(0) is used. - (void)cr_terminateImmediately; // Terminate the app via -cr_terminateImmediately when the background tasks // count is one. The remaining task is the count observation task. - (void)cr_terminateWhenCountIsOne; @end @implementation UIApplication (ExitsOnSuspend) - (void)cr_terminateWhenDoneWithBackgroundTasks { // Add a background task for the count observation. DCHECK(countTaskIdentifier == UIBackgroundTaskInvalid); countTaskIdentifier = [self beginBackgroundTaskWithExpirationHandler:^{ // If we get to the end of the 10 minutes, exit. [self cr_terminateImmediately]; }]; [self cr_terminateWhenCountIsOne]; } - (void)cr_cancelTermination { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector( cr_terminateWhenCountIsOne) object:nil]; // Cancel the count observation background task. [self endBackgroundTask:countTaskIdentifier]; countTaskIdentifier = UIBackgroundTaskInvalid; } #pragma mark - Private - (void)cr_terminateImmediately { DVLOG(1) << "App exited when suspended after running background tasks."; // exit(0) will trigger at_exit handlers. Some need to be run on a IOAllowed // thread, such as file_util::MemoryMappedFile::CloseHandles(). base::ThreadRestrictions::SetIOAllowed(true); exit(0); } - (void)cr_terminateWhenCountIsOne { if (backgroundTasksCount <= 1) [self cr_terminateImmediately]; [self performSelector:_cmd withObject:nil afterDelay:1]; } @end #endif // !defined(NDEBUG)