32fcf3609b958ea1be6ed435a117365c9f38db4f
[WebKit-https.git] / Source / WebKit / UIProcess / ios / ProcessAssertionIOS.mm
1 /*
2  * Copyright (C) 2014-2019 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_FAMILY)
30
31 #import "AssertionServicesSPI.h"
32 #import "Logging.h"
33 #import "WebProcessPool.h"
34 #import <UIKit/UIApplication.h>
35 #import <wtf/HashMap.h>
36 #import <wtf/RunLoop.h>
37 #import <wtf/Vector.h>
38
39 using WebKit::ProcessAndUIAssertion;
40
41 // This gives some time to our child processes to process the ProcessWillSuspendImminently IPC but makes sure we release
42 // the background task before the UIKit timeout (We get killed if we do not release the background task within 5 seconds
43 // on the expiration handler getting called).
44 static const Seconds releaseBackgroundTaskAfterExpirationDelay { 2_s };
45
46 @interface WKProcessAssertionBackgroundTaskManager : NSObject
47
48 + (WKProcessAssertionBackgroundTaskManager *)shared;
49
50 - (void)addAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion;
51 - (void)removeAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion;
52
53 @end
54
55 @implementation WKProcessAssertionBackgroundTaskManager
56 {
57     UIBackgroundTaskIdentifier _backgroundTask;
58     HashSet<ProcessAndUIAssertion*> _assertionsNeedingBackgroundTask;
59     BOOL _applicationIsBackgrounded;
60     dispatch_block_t _pendingTaskReleaseTask;
61 }
62
63 + (WKProcessAssertionBackgroundTaskManager *)shared
64 {
65     static WKProcessAssertionBackgroundTaskManager *shared = [WKProcessAssertionBackgroundTaskManager new];
66     return shared;
67 }
68
69 - (instancetype)init
70 {
71     self = [super init];
72     if (!self)
73         return nil;
74
75     _backgroundTask = UIBackgroundTaskInvalid;
76
77     [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *) {
78         _applicationIsBackgrounded = NO;
79         [self _cancelPendingReleaseTask];
80         [self _updateBackgroundTask];
81     }];
82
83     [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *) {
84         _applicationIsBackgrounded = YES;
85         
86         if (_backgroundTask == UIBackgroundTaskInvalid)
87             WebKit::WebProcessPool::applicationIsAboutToSuspend();
88     }];
89
90     return self;
91 }
92
93 - (void)dealloc
94 {
95     [self _releaseBackgroundTask];
96     [super dealloc];
97 }
98
99 - (void)addAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion
100 {
101     _assertionsNeedingBackgroundTask.add(&assertion);
102     [self _updateBackgroundTask];
103 }
104
105 - (void)removeAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion
106 {
107     _assertionsNeedingBackgroundTask.remove(&assertion);
108     [self _updateBackgroundTask];
109 }
110
111 - (void)_notifyAssertionsOfImminentSuspension
112 {
113     ASSERT(RunLoop::isMain());
114
115     for (auto* assertion : copyToVector(_assertionsNeedingBackgroundTask))
116         assertion->uiAssertionWillExpireImminently();
117 }
118
119
120 - (void)_scheduleReleaseTask
121 {
122     ASSERT(!_pendingTaskReleaseTask);
123     if (_pendingTaskReleaseTask)
124         return;
125
126     RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _scheduleReleaseTask because the expiration handler has been called", self);
127     _pendingTaskReleaseTask = dispatch_block_create((dispatch_block_flags_t)0, ^{
128         _pendingTaskReleaseTask = nil;
129         [self _releaseBackgroundTask];
130     });
131     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, releaseBackgroundTaskAfterExpirationDelay.value() * NSEC_PER_SEC), dispatch_get_main_queue(), _pendingTaskReleaseTask);
132 #if !__has_feature(objc_arc)
133     // dispatch_async() does a Block_copy() / Block_release() on behalf of the caller.
134     Block_release(_pendingTaskReleaseTask);
135 #endif
136 }
137
138 - (void)_cancelPendingReleaseTask
139 {
140     if (!_pendingTaskReleaseTask)
141         return;
142
143     RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _cancelPendingReleaseTask because the application is foreground again", self);
144     dispatch_block_cancel(_pendingTaskReleaseTask);
145     _pendingTaskReleaseTask = nil;
146 }
147
148 - (void)_updateBackgroundTask
149 {
150     if (!_assertionsNeedingBackgroundTask.isEmpty() && _backgroundTask == UIBackgroundTaskInvalid) {
151         if (_applicationIsBackgrounded) {
152             RELEASE_LOG_ERROR(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager: Ignored request to start a new background task because the application is already in the background", self);
153             return;
154         }
155         RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - beginBackgroundTaskWithName", self);
156         _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"com.apple.WebKit.ProcessAssertion" expirationHandler:^{
157             RELEASE_LOG_ERROR(ProcessSuspension, "Background task expired while holding WebKit ProcessAssertion (isMainThread? %d).", RunLoop::isMain());
158             if (!_applicationIsBackgrounded) {
159                 // We've received the invalidation warning after the app has become foreground again. In this case, we should not
160                 // warn clients of imminent suspension. To be safe (avoid potential killing), we end the task right away and call
161                 // _updateBackgroundTask asynchronously to start a new task if necessary.
162                 [self _releaseBackgroundTask];
163                 dispatch_async(dispatch_get_main_queue(), ^{
164                     [self _updateBackgroundTask];
165                 });
166                 return;
167             }
168
169             // The expiration handler gets called on a non-main thread when the underlying assertion could not be taken (rdar://problem/27278419).
170             if (RunLoop::isMain())
171                 [self _notifyAssertionsOfImminentSuspension];
172             else {
173                 dispatch_sync(dispatch_get_main_queue(), ^{
174                     [self _notifyAssertionsOfImminentSuspension];
175                 });
176             }
177
178             [self _scheduleReleaseTask];
179         }];
180     } else if (_assertionsNeedingBackgroundTask.isEmpty())
181         [self _releaseBackgroundTask];
182 }
183
184 - (void)_releaseBackgroundTask
185 {
186     if (_backgroundTask == UIBackgroundTaskInvalid)
187         return;
188
189     RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - endBackgroundTask", self);
190     if (_applicationIsBackgrounded)
191         WebKit::WebProcessPool::applicationIsAboutToSuspend();
192     [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
193     _backgroundTask = UIBackgroundTaskInvalid;
194 }
195
196 @end
197
198 namespace WebKit {
199
200 const BKSProcessAssertionFlags suspendedTabFlags = (BKSProcessAssertionAllowIdleSleep);
201 const BKSProcessAssertionFlags backgroundTabFlags = (BKSProcessAssertionPreventTaskSuspend);
202 const BKSProcessAssertionFlags foregroundTabFlags = (BKSProcessAssertionPreventTaskSuspend | BKSProcessAssertionWantsForegroundResourcePriority | BKSProcessAssertionPreventTaskThrottleDown);
203
204 static BKSProcessAssertionFlags flagsForState(AssertionState assertionState)
205 {
206     switch (assertionState) {
207     case AssertionState::Suspended:
208         return suspendedTabFlags;
209     case AssertionState::Background:
210     case AssertionState::UnboundedNetworking:
211         return backgroundTabFlags;
212     case AssertionState::Foreground:
213         return foregroundTabFlags;
214     }
215 }
216
217 static AssertionReason reasonForState(AssertionState assertionState)
218 {
219     switch (assertionState) {
220     case AssertionState::UnboundedNetworking:
221         return AssertionReason::FinishTaskUnbounded;
222     case AssertionState::Suspended:
223     case AssertionState::Background:
224     case AssertionState::Foreground:
225         return AssertionReason::Extension;
226     }
227 }
228
229 static BKSProcessAssertionReason toBKSProcessAssertionReason(AssertionReason reason)
230 {
231     switch (reason) {
232     case AssertionReason::Extension:
233         return BKSProcessAssertionReasonExtension;
234     case AssertionReason::FinishTask:
235         return BKSProcessAssertionReasonFinishTask;
236     case AssertionReason::FinishTaskUnbounded:
237         return BKSProcessAssertionReasonFinishTaskUnbounded;
238     case AssertionReason::MediaPlayback:
239         return BKSProcessAssertionReasonMediaPlayback;
240     }
241 }
242
243 ProcessAssertion::ProcessAssertion(pid_t pid, const String& name, AssertionState assertionState)
244     : ProcessAssertion(pid, name, assertionState, reasonForState(assertionState))
245 {
246 }
247
248 ProcessAssertion::ProcessAssertion(pid_t pid, const String& name, AssertionState assertionState, AssertionReason assertionReason)
249     : m_assertionState(assertionState)
250 {
251     auto weakThis = makeWeakPtr(*this);
252     BKSProcessAssertionAcquisitionHandler handler = ^(BOOL acquired) {
253         if (!acquired) {
254             RELEASE_LOG_ERROR(ProcessSuspension, " %p - ProcessAssertion() PID %d Unable to acquire assertion for process with PID %d", this, getpid(), pid);
255             dispatch_async(dispatch_get_main_queue(), ^{
256                 if (weakThis)
257                     processAssertionWasInvalidated();
258             });
259         }
260     };
261     RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion() PID %d acquiring assertion for process with PID %d, name '%s'", this, getpid(), pid, name.utf8().data());
262     
263     m_assertion = adoptNS([[BKSProcessAssertion alloc] initWithPID:pid flags:flagsForState(assertionState) reason:toBKSProcessAssertionReason(assertionReason) name:(NSString *)name withHandler:handler]);
264     m_assertion.get().invalidationHandler = ^() {
265         dispatch_async(dispatch_get_main_queue(), ^{
266             RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion() Process assertion for process with PID %d was invalidated", this, pid);
267             if (weakThis)
268                 processAssertionWasInvalidated();
269         });
270     };
271 }
272
273 ProcessAssertion::~ProcessAssertion()
274 {
275     m_assertion.get().invalidationHandler = nil;
276
277     RELEASE_LOG(ProcessSuspension, "%p - ~ProcessAssertion() Releasing process assertion", this);
278     [m_assertion invalidate];
279 }
280
281 void ProcessAssertion::processAssertionWasInvalidated()
282 {
283     ASSERT(RunLoop::isMain());
284     RELEASE_LOG_ERROR(ProcessSuspension, "%p - ProcessAssertion::processAssertionWasInvalidated()", this);
285
286     m_validity = Validity::No;
287 }
288
289 void ProcessAssertion::setState(AssertionState assertionState)
290 {
291     if (m_assertionState == assertionState)
292         return;
293
294     RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion::setState(%u) previousState: %u", this, static_cast<unsigned>(assertionState), static_cast<unsigned>(m_assertionState));
295     m_assertionState = assertionState;
296     [m_assertion setFlags:flagsForState(assertionState)];
297 }
298
299 void ProcessAndUIAssertion::updateRunInBackgroundCount()
300 {
301     bool shouldHoldBackgroundTask = validity() != Validity::No && state() != AssertionState::Suspended;
302     if (m_isHoldingBackgroundTask == shouldHoldBackgroundTask)
303         return;
304
305     if (shouldHoldBackgroundTask)
306         [[WKProcessAssertionBackgroundTaskManager shared] addAssertionNeedingBackgroundTask:*this];
307     else
308         [[WKProcessAssertionBackgroundTaskManager shared] removeAssertionNeedingBackgroundTask:*this];
309
310     m_isHoldingBackgroundTask = shouldHoldBackgroundTask;
311 }
312
313 ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, const String& reason, AssertionState assertionState)
314     : ProcessAssertion(pid, reason, assertionState)
315 {
316     updateRunInBackgroundCount();
317 }
318
319 ProcessAndUIAssertion::~ProcessAndUIAssertion()
320 {
321     if (m_isHoldingBackgroundTask)
322         [[WKProcessAssertionBackgroundTaskManager shared] removeAssertionNeedingBackgroundTask:*this];
323 }
324
325 void ProcessAndUIAssertion::setState(AssertionState assertionState)
326 {
327     ProcessAssertion::setState(assertionState);
328     updateRunInBackgroundCount();
329 }
330
331 void ProcessAndUIAssertion::uiAssertionWillExpireImminently()
332 {
333     if (auto* client = this->client())
334         client->uiAssertionWillExpireImminently();
335 }
336
337 void ProcessAndUIAssertion::processAssertionWasInvalidated()
338 {
339     ProcessAssertion::processAssertionWasInvalidated();
340     updateRunInBackgroundCount();
341 }
342
343 } // namespace WebKit
344
345 #endif // PLATFORM(IOS_FAMILY)