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