Unreviewed, rolling out r246053.
[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             if (!_applicationIsBackgrounded) {
155                 // We've received the invalidation warning after the app has become foreground again. In this case, we should not
156                 // warn clients of imminent suspension. To be safe (avoid potential killing), we end the task right away and call
157                 // _updateBackgroundTask asynchronously to start a new task if necessary.
158                 [self _releaseBackgroundTask];
159                 dispatch_async(dispatch_get_main_queue(), ^{
160                     [self _updateBackgroundTask];
161                 });
162                 return;
163             }
164
165             // The expiration handler gets called on a non-main thread when the underlying assertion could not be taken (rdar://problem/27278419).
166             if (RunLoop::isMain())
167                 [self _notifyAssertionsOfImminentSuspension];
168             else {
169                 dispatch_sync(dispatch_get_main_queue(), ^{
170                     [self _notifyAssertionsOfImminentSuspension];
171                 });
172             }
173
174             [self _scheduleReleaseTask];
175         }];
176     } else if (_assertionsNeedingBackgroundTask.isEmpty())
177         [self _releaseBackgroundTask];
178 }
179
180 - (void)_releaseBackgroundTask
181 {
182     if (_backgroundTask == UIBackgroundTaskInvalid)
183         return;
184
185     RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - endBackgroundTask", self);
186     [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
187     _backgroundTask = UIBackgroundTaskInvalid;
188 }
189
190 @end
191
192 namespace WebKit {
193
194 const BKSProcessAssertionFlags suspendedTabFlags = (BKSProcessAssertionAllowIdleSleep);
195 const BKSProcessAssertionFlags backgroundTabFlags = (BKSProcessAssertionPreventTaskSuspend);
196 const BKSProcessAssertionFlags foregroundTabFlags = (BKSProcessAssertionPreventTaskSuspend | BKSProcessAssertionWantsForegroundResourcePriority | BKSProcessAssertionPreventTaskThrottleDown);
197
198 static BKSProcessAssertionFlags flagsForState(AssertionState assertionState)
199 {
200     switch (assertionState) {
201     case AssertionState::Suspended:
202         return suspendedTabFlags;
203     case AssertionState::Background:
204     case AssertionState::UnboundedNetworking:
205         return backgroundTabFlags;
206     case AssertionState::Foreground:
207         return foregroundTabFlags;
208     }
209 }
210
211 static AssertionReason reasonForState(AssertionState assertionState)
212 {
213     switch (assertionState) {
214     case AssertionState::UnboundedNetworking:
215         return AssertionReason::FinishTaskUnbounded;
216     case AssertionState::Suspended:
217     case AssertionState::Background:
218     case AssertionState::Foreground:
219         return AssertionReason::Extension;
220     }
221 }
222
223 static BKSProcessAssertionReason toBKSProcessAssertionReason(AssertionReason reason)
224 {
225     switch (reason) {
226     case AssertionReason::Extension:
227         return BKSProcessAssertionReasonExtension;
228     case AssertionReason::FinishTask:
229         return BKSProcessAssertionReasonFinishTask;
230     case AssertionReason::FinishTaskUnbounded:
231         return BKSProcessAssertionReasonFinishTaskUnbounded;
232     case AssertionReason::MediaPlayback:
233         return BKSProcessAssertionReasonMediaPlayback;
234     }
235 }
236
237 ProcessAssertion::ProcessAssertion(pid_t pid, const String& name, AssertionState assertionState)
238     : ProcessAssertion(pid, name, assertionState, reasonForState(assertionState))
239 {
240 }
241
242 ProcessAssertion::ProcessAssertion(pid_t pid, const String& name, AssertionState assertionState, AssertionReason assertionReason)
243     : m_assertionState(assertionState)
244 {
245     auto weakThis = makeWeakPtr(*this);
246     BKSProcessAssertionAcquisitionHandler handler = ^(BOOL acquired) {
247         if (!acquired) {
248             RELEASE_LOG_ERROR(ProcessSuspension, " %p - ProcessAssertion() PID %d Unable to acquire assertion for process with PID %d", this, getpid(), pid);
249             dispatch_async(dispatch_get_main_queue(), ^{
250                 if (weakThis)
251                     processAssertionWasInvalidated();
252             });
253         }
254     };
255     RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion() PID %d acquiring assertion for process with PID %d, name '%s'", this, getpid(), pid, name.utf8().data());
256     
257     m_assertion = adoptNS([[BKSProcessAssertion alloc] initWithPID:pid flags:flagsForState(assertionState) reason:toBKSProcessAssertionReason(assertionReason) name:(NSString *)name withHandler:handler]);
258     m_assertion.get().invalidationHandler = ^() {
259         dispatch_async(dispatch_get_main_queue(), ^{
260             RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion() Process assertion for process with PID %d was invalidated", this, pid);
261             if (weakThis)
262                 processAssertionWasInvalidated();
263         });
264     };
265 }
266
267 ProcessAssertion::~ProcessAssertion()
268 {
269     m_assertion.get().invalidationHandler = nil;
270
271     RELEASE_LOG(ProcessSuspension, "%p - ~ProcessAssertion() Releasing process assertion", this);
272     [m_assertion invalidate];
273 }
274
275 void ProcessAssertion::processAssertionWasInvalidated()
276 {
277     ASSERT(RunLoop::isMain());
278     RELEASE_LOG_ERROR(ProcessSuspension, "%p - ProcessAssertion::processAssertionWasInvalidated()", this);
279
280     m_validity = Validity::No;
281 }
282
283 void ProcessAssertion::setState(AssertionState assertionState)
284 {
285     if (m_assertionState == assertionState)
286         return;
287
288     RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion::setState(%u) previousState: %u", this, static_cast<unsigned>(assertionState), static_cast<unsigned>(m_assertionState));
289     m_assertionState = assertionState;
290     [m_assertion setFlags:flagsForState(assertionState)];
291 }
292
293 void ProcessAndUIAssertion::updateRunInBackgroundCount()
294 {
295     bool shouldHoldBackgroundTask = validity() != Validity::No && state() != AssertionState::Suspended;
296     if (m_isHoldingBackgroundTask == shouldHoldBackgroundTask)
297         return;
298
299     if (shouldHoldBackgroundTask)
300         [[WKProcessAssertionBackgroundTaskManager shared] addAssertionNeedingBackgroundTask:*this];
301     else
302         [[WKProcessAssertionBackgroundTaskManager shared] removeAssertionNeedingBackgroundTask:*this];
303
304     m_isHoldingBackgroundTask = shouldHoldBackgroundTask;
305 }
306
307 ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, const String& reason, AssertionState assertionState)
308     : ProcessAssertion(pid, reason, assertionState)
309 {
310     updateRunInBackgroundCount();
311 }
312
313 ProcessAndUIAssertion::~ProcessAndUIAssertion()
314 {
315     if (m_isHoldingBackgroundTask)
316         [[WKProcessAssertionBackgroundTaskManager shared] removeAssertionNeedingBackgroundTask:*this];
317 }
318
319 void ProcessAndUIAssertion::setState(AssertionState assertionState)
320 {
321     ProcessAssertion::setState(assertionState);
322     updateRunInBackgroundCount();
323 }
324
325 void ProcessAndUIAssertion::uiAssertionWillExpireImminently()
326 {
327     if (auto* client = this->client())
328         client->uiAssertionWillExpireImminently();
329 }
330
331 void ProcessAndUIAssertion::processAssertionWasInvalidated()
332 {
333     ProcessAssertion::processAssertionWasInvalidated();
334     updateRunInBackgroundCount();
335 }
336
337 } // namespace WebKit
338
339 #endif // PLATFORM(IOS_FAMILY)