1/*
2 * Copyright (C) 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "ProcessAssertion.h"
28
29#if PLATFORM(IOS)
30
31#import <AssertionServices/BKSProcessAssertion.h>
32#import <UIKit/UIApplication.h>
33
34#if !PLATFORM(IOS_SIMULATOR)
35
36@interface WKProcessAssertionBackgroundTaskManager : NSObject
37
38+ (WKProcessAssertionBackgroundTaskManager *)shared;
39
40- (void)incrementNeedsToRunInBackgroundCount;
41- (void)decrementNeedsToRunInBackgroundCount;
42
43@end
44
45@implementation WKProcessAssertionBackgroundTaskManager
46{
47    unsigned _needsToRunInBackgroundCount;
48    BOOL _appIsBackground;
49    UIBackgroundTaskIdentifier _backgroundTask;
50}
51
52+ (WKProcessAssertionBackgroundTaskManager *)shared
53{
54    static WKProcessAssertionBackgroundTaskManager *shared = [WKProcessAssertionBackgroundTaskManager new];
55    return shared;
56}
57
58- (instancetype)init
59{
60    self = [super init];
61    if (!self)
62        return nil;
63
64    _appIsBackground = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground;
65    _backgroundTask = UIBackgroundTaskInvalid;
66
67    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
68    [center addObserver:self selector:@selector(_applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
69    [center addObserver:self selector:@selector(_applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
70
71    return self;
72}
73
74- (void)dealloc
75{
76    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
77    [center removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
78    [center removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
79
80    if (_backgroundTask != UIBackgroundTaskInvalid)
81        [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
82
83    [super dealloc];
84}
85
86- (void)_updateBackgroundTask
87{
88    bool shouldHoldTask = _needsToRunInBackgroundCount && _appIsBackground;
89
90    if (shouldHoldTask && _backgroundTask == UIBackgroundTaskInvalid) {
91        _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"com.apple.WebKit.ProcessAssertion" expirationHandler:^{
92            NSLog(@"Background task expired while holding WebKit ProcessAssertion.");
93            [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
94            _backgroundTask = UIBackgroundTaskInvalid;
95        }];
96    }
97
98    if (!shouldHoldTask && _backgroundTask != UIBackgroundTaskInvalid) {
99        [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
100        _backgroundTask = UIBackgroundTaskInvalid;
101    }
102}
103
104- (void)_applicationWillEnterForeground:(NSNotification *)notification
105{
106    _appIsBackground = NO;
107    [self _updateBackgroundTask];
108}
109
110- (void)_applicationDidEnterBackground:(NSNotification *)notification
111{
112    _appIsBackground = YES;
113    [self _updateBackgroundTask];
114}
115
116- (void)incrementNeedsToRunInBackgroundCount
117{
118    ++_needsToRunInBackgroundCount;
119    [self _updateBackgroundTask];
120}
121
122- (void)decrementNeedsToRunInBackgroundCount
123{
124    --_needsToRunInBackgroundCount;
125    [self _updateBackgroundTask];
126}
127
128@end
129
130namespace WebKit {
131
132const BKSProcessAssertionFlags suspendedTabFlags = (BKSProcessAssertionAllowIdleSleep);
133const BKSProcessAssertionFlags backgroundTabFlags = (BKSProcessAssertionAllowIdleSleep | BKSProcessAssertionPreventTaskSuspend | BKSProcessAssertionAllowSuspendOnSleep);
134const BKSProcessAssertionFlags foregroundTabFlags = (BKSProcessAssertionAllowIdleSleep | BKSProcessAssertionPreventTaskSuspend | BKSProcessAssertionAllowSuspendOnSleep | BKSProcessAssertionWantsForegroundResourcePriority | BKSProcessAssertionPreventTaskThrottleDown);
135
136static BKSProcessAssertionFlags flagsForState(AssertionState assertionState)
137{
138    switch (assertionState) {
139    case AssertionState::Suspended:
140        return suspendedTabFlags;
141    case AssertionState::Background:
142        return backgroundTabFlags;
143    case AssertionState::Foreground:
144        return foregroundTabFlags;
145    }
146}
147
148ProcessAssertion::ProcessAssertion(pid_t pid, AssertionState assertionState)
149{
150    m_assertionState = assertionState;
151
152    BKSProcessAssertionAcquisitionHandler handler = ^(BOOL acquired) {
153        if (!acquired) {
154            LOG_ERROR("Unable to acquire assertion for process %d", pid);
155            ASSERT_NOT_REACHED();
156        }
157    };
158    m_assertion = adoptNS([[BKSProcessAssertion alloc] initWithPID:pid flags:flagsForState(assertionState) reason:BKSProcessAssertionReasonExtension name:@"Web content visible" withHandler:handler]);
159}
160
161void ProcessAssertion::setState(AssertionState assertionState)
162{
163    if (m_assertionState == assertionState)
164        return;
165
166    m_assertionState = assertionState;
167    [m_assertion setFlags:flagsForState(assertionState)];
168}
169
170ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, AssertionState assertionState)
171    : ProcessAssertion(pid, assertionState)
172{
173    if (assertionState != AssertionState::Suspended)
174        [[WKProcessAssertionBackgroundTaskManager shared] incrementNeedsToRunInBackgroundCount];
175}
176
177ProcessAndUIAssertion::~ProcessAndUIAssertion()
178{
179    if (state() != AssertionState::Suspended)
180        [[WKProcessAssertionBackgroundTaskManager shared] decrementNeedsToRunInBackgroundCount];
181}
182
183void ProcessAndUIAssertion::setState(AssertionState assertionState)
184{
185    if ((state() == AssertionState::Suspended) && (assertionState != AssertionState::Suspended))
186        [[WKProcessAssertionBackgroundTaskManager shared] incrementNeedsToRunInBackgroundCount];
187    if ((state() != AssertionState::Suspended) && (assertionState == AssertionState::Suspended))
188        [[WKProcessAssertionBackgroundTaskManager shared] decrementNeedsToRunInBackgroundCount];
189
190    ProcessAssertion::setState(assertionState);
191}
192
193} // namespace WebKit
194
195#else // PLATFORM(IOS_SIMULATOR)
196
197namespace WebKit {
198
199ProcessAssertion::ProcessAssertion(pid_t, AssertionState assertionState)
200    : m_assertionState(assertionState)
201{
202}
203
204void ProcessAssertion::setState(AssertionState assertionState)
205{
206    m_assertionState = assertionState;
207}
208
209ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, AssertionState assertionState)
210    : ProcessAssertion(pid, assertionState)
211{
212}
213
214ProcessAndUIAssertion::~ProcessAndUIAssertion()
215{
216}
217
218void ProcessAndUIAssertion::setState(AssertionState assertionState)
219{
220    ProcessAssertion::setState(assertionState);
221}
222
223} // namespace WebKit
224
225#endif // PLATFORM(IOS_SIMULATOR)
226
227#endif // PLATFORM(IOS)
228