Setup EWS queues for JSConly 32bits ARMv7 and MIPSel
[WebKit-https.git] / Source / WebKit / UIProcess / ios / ProcessAssertionIOS.mm
index 810c6f3..37a6982 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-2019 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #import "config.h"
 #import "ProcessAssertion.h"
 
-#if PLATFORM(IOS)
+#if PLATFORM(IOS_FAMILY)
 
 #import "AssertionServicesSPI.h"
 #import "Logging.h"
+#import "WebProcessPool.h"
 #import <UIKit/UIApplication.h>
-#import <wtf/HashSet.h>
+#import <wtf/HashMap.h>
 #import <wtf/RunLoop.h>
 #import <wtf/Vector.h>
 
-#if !PLATFORM(IOS_SIMULATOR)
+using WebKit::ProcessAndUIAssertion;
 
-using WebKit::ProcessAssertionClient;
+// This gives some time to our child processes to process the ProcessWillSuspendImminently IPC but makes sure we release
+// the background task before the UIKit timeout (We get killed if we do not release the background task within 5 seconds
+// on the expiration handler getting called).
+static const Seconds releaseBackgroundTaskAfterExpirationDelay { 2_s };
 
 @interface WKProcessAssertionBackgroundTaskManager : NSObject
 
 + (WKProcessAssertionBackgroundTaskManager *)shared;
 
-- (void)incrementNeedsToRunInBackgroundCount;
-- (void)decrementNeedsToRunInBackgroundCount;
-
-- (void)addClient:(ProcessAssertionClient&)client;
-- (void)removeClient:(ProcessAssertionClient&)client;
+- (void)addAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion;
+- (void)removeAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion;
 
 @end
 
 @implementation WKProcessAssertionBackgroundTaskManager
 {
-    unsigned _needsToRunInBackgroundCount;
     UIBackgroundTaskIdentifier _backgroundTask;
-    HashSet<ProcessAssertionClient*> _clients;
+    HashSet<ProcessAndUIAssertion*> _assertionsNeedingBackgroundTask;
+    BOOL _applicationIsBackgrounded;
+    dispatch_block_t _pendingTaskReleaseTask;
 }
 
 + (WKProcessAssertionBackgroundTaskManager *)shared
@@ -72,72 +74,123 @@ using WebKit::ProcessAssertionClient;
 
     _backgroundTask = UIBackgroundTaskInvalid;
 
+    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *) {
+        _applicationIsBackgrounded = NO;
+        [self _cancelPendingReleaseTask];
+        [self _updateBackgroundTask];
+    }];
+
+    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *) {
+        _applicationIsBackgrounded = YES;
+        
+        if (_backgroundTask == UIBackgroundTaskInvalid)
+            WebKit::WebProcessPool::notifyProcessPoolsApplicationIsAboutToSuspend();
+    }];
+
     return self;
 }
 
 - (void)dealloc
 {
-    if (_backgroundTask != UIBackgroundTaskInvalid)
-        [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
-
+    [self _releaseBackgroundTask];
     [super dealloc];
 }
 
-- (void)addClient:(ProcessAssertionClient&)client
+- (void)addAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion
 {
-    _clients.add(&client);
+    _assertionsNeedingBackgroundTask.add(&assertion);
+    [self _updateBackgroundTask];
 }
 
-- (void)removeClient:(ProcessAssertionClient&)client
+- (void)removeAssertionNeedingBackgroundTask:(ProcessAndUIAssertion&)assertion
 {
-    _clients.remove(&client);
+    _assertionsNeedingBackgroundTask.remove(&assertion);
+    [self _updateBackgroundTask];
 }
 
-- (void)_notifyClientsOfImminentSuspension
+- (void)_notifyAssertionsOfImminentSuspension
 {
     ASSERT(RunLoop::isMain());
-    Vector<ProcessAssertionClient*> clientsToNotify;
-    copyToVector(_clients, clientsToNotify);
-    for (auto* client : clientsToNotify)
-        client->assertionWillExpireImminently();
+
+    for (auto* assertion : copyToVector(_assertionsNeedingBackgroundTask))
+        assertion->uiAssertionWillExpireImminently();
+}
+
+
+- (void)_scheduleReleaseTask
+{
+    ASSERT(!_pendingTaskReleaseTask);
+    if (_pendingTaskReleaseTask)
+        return;
+
+    RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _scheduleReleaseTask because the expiration handler has been called", self);
+    _pendingTaskReleaseTask = dispatch_block_create((dispatch_block_flags_t)0, ^{
+        _pendingTaskReleaseTask = nil;
+        [self _releaseBackgroundTask];
+    });
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, releaseBackgroundTaskAfterExpirationDelay.value() * NSEC_PER_SEC), dispatch_get_main_queue(), _pendingTaskReleaseTask);
+#if !__has_feature(objc_arc)
+    // dispatch_async() does a Block_copy() / Block_release() on behalf of the caller.
+    Block_release(_pendingTaskReleaseTask);
+#endif
+}
+
+- (void)_cancelPendingReleaseTask
+{
+    if (!_pendingTaskReleaseTask)
+        return;
+
+    RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _cancelPendingReleaseTask because the application is foreground again", self);
+    dispatch_block_cancel(_pendingTaskReleaseTask);
+    _pendingTaskReleaseTask = nil;
 }
 
 - (void)_updateBackgroundTask
 {
-    if (_needsToRunInBackgroundCount && _backgroundTask == UIBackgroundTaskInvalid) {
+    if (!_assertionsNeedingBackgroundTask.isEmpty() && _backgroundTask == UIBackgroundTaskInvalid) {
+        if (_applicationIsBackgrounded) {
+            RELEASE_LOG_ERROR(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager: Ignored request to start a new background task because the application is already in the background", self);
+            return;
+        }
         RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - beginBackgroundTaskWithName", self);
         _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"com.apple.WebKit.ProcessAssertion" expirationHandler:^{
             RELEASE_LOG_ERROR(ProcessSuspension, "Background task expired while holding WebKit ProcessAssertion (isMainThread? %d).", RunLoop::isMain());
+            if (!_applicationIsBackgrounded) {
+                // We've received the invalidation warning after the app has become foreground again. In this case, we should not
+                // warn clients of imminent suspension. To be safe (avoid potential killing), we end the task right away and call
+                // _updateBackgroundTask asynchronously to start a new task if necessary.
+                [self _releaseBackgroundTask];
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [self _updateBackgroundTask];
+                });
+                return;
+            }
+
             // The expiration handler gets called on a non-main thread when the underlying assertion could not be taken (rdar://problem/27278419).
             if (RunLoop::isMain())
-                [self _notifyClientsOfImminentSuspension];
+                [self _notifyAssertionsOfImminentSuspension];
             else {
                 dispatch_sync(dispatch_get_main_queue(), ^{
-                    [self _notifyClientsOfImminentSuspension];
+                    [self _notifyAssertionsOfImminentSuspension];
                 });
             }
-            [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
-            _backgroundTask = UIBackgroundTaskInvalid;
-        }];
-    }
 
-    if (!_needsToRunInBackgroundCount && _backgroundTask != UIBackgroundTaskInvalid) {
-        RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - endBackgroundTask", self);
-        [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
-        _backgroundTask = UIBackgroundTaskInvalid;
-    }
+            [self _scheduleReleaseTask];
+        }];
+    } else if (_assertionsNeedingBackgroundTask.isEmpty())
+        [self _releaseBackgroundTask];
 }
 
-- (void)incrementNeedsToRunInBackgroundCount
+- (void)_releaseBackgroundTask
 {
-    ++_needsToRunInBackgroundCount;
-    [self _updateBackgroundTask];
-}
+    if (_backgroundTask == UIBackgroundTaskInvalid)
+        return;
 
-- (void)decrementNeedsToRunInBackgroundCount
-{
-    --_needsToRunInBackgroundCount;
-    [self _updateBackgroundTask];
+    RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - endBackgroundTask", self);
+    if (_applicationIsBackgrounded)
+        WebKit::WebProcessPool::notifyProcessPoolsApplicationIsAboutToSuspend();
+    [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
+    _backgroundTask = UIBackgroundTaskInvalid;
 }
 
 @end
@@ -145,8 +198,8 @@ using WebKit::ProcessAssertionClient;
 namespace WebKit {
 
 const BKSProcessAssertionFlags suspendedTabFlags = (BKSProcessAssertionAllowIdleSleep);
-const BKSProcessAssertionFlags backgroundTabFlags = (BKSProcessAssertionAllowIdleSleep | BKSProcessAssertionPreventTaskSuspend);
-const BKSProcessAssertionFlags foregroundTabFlags = (BKSProcessAssertionAllowIdleSleep | BKSProcessAssertionPreventTaskSuspend | BKSProcessAssertionWantsForegroundResourcePriority | BKSProcessAssertionPreventTaskThrottleDown);
+const BKSProcessAssertionFlags backgroundTabFlags = (BKSProcessAssertionPreventTaskSuspend);
+const BKSProcessAssertionFlags foregroundTabFlags = (BKSProcessAssertionPreventTaskSuspend | BKSProcessAssertionWantsForegroundResourcePriority | BKSProcessAssertionPreventTaskThrottleDown);
 
 static BKSProcessAssertionFlags flagsForState(AssertionState assertionState)
 {
@@ -154,34 +207,65 @@ static BKSProcessAssertionFlags flagsForState(AssertionState assertionState)
     case AssertionState::Suspended:
         return suspendedTabFlags;
     case AssertionState::Background:
+    case AssertionState::UnboundedNetworking:
         return backgroundTabFlags;
     case AssertionState::Foreground:
         return foregroundTabFlags;
     }
 }
 
-ProcessAssertion::ProcessAssertion(pid_t pid, AssertionState assertionState, Function<void()>&& invalidationCallback)
-    : m_invalidationCallback(WTFMove(invalidationCallback))
-    , m_assertionState(assertionState)
+static AssertionReason reasonForState(AssertionState assertionState)
+{
+    switch (assertionState) {
+    case AssertionState::UnboundedNetworking:
+        return AssertionReason::FinishTaskUnbounded;
+    case AssertionState::Suspended:
+    case AssertionState::Background:
+    case AssertionState::Foreground:
+        return AssertionReason::Extension;
+    }
+}
+
+static BKSProcessAssertionReason toBKSProcessAssertionReason(AssertionReason reason)
+{
+    switch (reason) {
+    case AssertionReason::Extension:
+        return BKSProcessAssertionReasonExtension;
+    case AssertionReason::FinishTask:
+        return BKSProcessAssertionReasonFinishTask;
+    case AssertionReason::FinishTaskUnbounded:
+        return BKSProcessAssertionReasonFinishTaskUnbounded;
+    case AssertionReason::MediaPlayback:
+        return BKSProcessAssertionReasonMediaPlayback;
+    }
+}
+
+ProcessAssertion::ProcessAssertion(pid_t pid, const String& name, AssertionState assertionState)
+    : ProcessAssertion(pid, name, assertionState, reasonForState(assertionState))
+{
+}
+
+ProcessAssertion::ProcessAssertion(pid_t pid, const String& name, AssertionState assertionState, AssertionReason assertionReason)
+    : m_assertionState(assertionState)
 {
-    auto weakThis = createWeakPtr();
+    auto weakThis = makeWeakPtr(*this);
     BKSProcessAssertionAcquisitionHandler handler = ^(BOOL acquired) {
         if (!acquired) {
-            RELEASE_LOG_ERROR(ProcessSuspension, " %p - ProcessAssertion() Unable to acquire assertion for process with PID %d", this, pid);
-            ASSERT_NOT_REACHED();
+            RELEASE_LOG_ERROR(ProcessSuspension, " %p - ProcessAssertion() PID %d Unable to acquire assertion for process with PID %d", this, getpid(), pid);
             dispatch_async(dispatch_get_main_queue(), ^{
                 if (weakThis)
-                    markAsInvalidated();
+                    processAssertionWasInvalidated();
             });
         }
     };
-    RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion() Acquiring assertion for process with PID %d", this, pid);
-    m_assertion = adoptNS([[BKSProcessAssertion alloc] initWithPID:pid flags:flagsForState(assertionState) reason:BKSProcessAssertionReasonExtension name:@"Web content visible" withHandler:handler]);
+    RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion() PID %d acquiring assertion for process with PID %d, name '%s'", this, getpid(), pid, name.utf8().data());
+    
+    m_assertion = adoptNS([[BKSProcessAssertion alloc] initWithPID:pid flags:flagsForState(assertionState) reason:toBKSProcessAssertionReason(assertionReason) name:(NSString *)name withHandler:handler]);
     m_assertion.get().invalidationHandler = ^() {
         dispatch_async(dispatch_get_main_queue(), ^{
             RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion() Process assertion for process with PID %d was invalidated", this, pid);
             if (weakThis)
-                markAsInvalidated();
+                processAssertionWasInvalidated();
         });
     };
 }
@@ -190,20 +274,16 @@ ProcessAssertion::~ProcessAssertion()
 {
     m_assertion.get().invalidationHandler = nil;
 
-    if (ProcessAssertionClient* client = this->client())
-        [[WKProcessAssertionBackgroundTaskManager shared] removeClient:*client];
-
     RELEASE_LOG(ProcessSuspension, "%p - ~ProcessAssertion() Releasing process assertion", this);
     [m_assertion invalidate];
 }
 
-void ProcessAssertion::markAsInvalidated()
+void ProcessAssertion::processAssertionWasInvalidated()
 {
     ASSERT(RunLoop::isMain());
+    RELEASE_LOG_ERROR(ProcessSuspension, "%p - ProcessAssertion::processAssertionWasInvalidated()", this);
 
     m_validity = Validity::No;
-    if (m_invalidationCallback)
-        m_invalidationCallback();
 }
 
 void ProcessAssertion::setState(AssertionState assertionState)
@@ -211,36 +291,35 @@ void ProcessAssertion::setState(AssertionState assertionState)
     if (m_assertionState == assertionState)
         return;
 
-    RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion::setState(%u)", this, static_cast<unsigned>(assertionState));
+    RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion::setState(%u) previousState: %u", this, static_cast<unsigned>(assertionState), static_cast<unsigned>(m_assertionState));
     m_assertionState = assertionState;
     [m_assertion setFlags:flagsForState(assertionState)];
 }
 
 void ProcessAndUIAssertion::updateRunInBackgroundCount()
 {
-    bool shouldHoldBackgroundAssertion = validity() != Validity::No && state() != AssertionState::Suspended;
-
-    if (shouldHoldBackgroundAssertion) {
-        if (!m_isHoldingBackgroundAssertion)
-            [[WKProcessAssertionBackgroundTaskManager shared] incrementNeedsToRunInBackgroundCount];
-    } else {
-        if (m_isHoldingBackgroundAssertion)
-            [[WKProcessAssertionBackgroundTaskManager shared] decrementNeedsToRunInBackgroundCount];
-    }
+    bool shouldHoldBackgroundTask = validity() != Validity::No && state() != AssertionState::Suspended;
+    if (m_isHoldingBackgroundTask == shouldHoldBackgroundTask)
+        return;
 
-    m_isHoldingBackgroundAssertion = shouldHoldBackgroundAssertion;
+    if (shouldHoldBackgroundTask)
+        [[WKProcessAssertionBackgroundTaskManager shared] addAssertionNeedingBackgroundTask:*this];
+    else
+        [[WKProcessAssertionBackgroundTaskManager shared] removeAssertionNeedingBackgroundTask:*this];
+
+    m_isHoldingBackgroundTask = shouldHoldBackgroundTask;
 }
 
-ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, AssertionState assertionState)
-    : ProcessAssertion(pid, assertionState, [this] { updateRunInBackgroundCount(); })
+ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, const String& reason, AssertionState assertionState)
+    : ProcessAssertion(pid, reason, assertionState)
 {
     updateRunInBackgroundCount();
 }
 
 ProcessAndUIAssertion::~ProcessAndUIAssertion()
 {
-    if (m_isHoldingBackgroundAssertion)
-        [[WKProcessAssertionBackgroundTaskManager shared] decrementNeedsToRunInBackgroundCount];
+    if (m_isHoldingBackgroundTask)
+        [[WKProcessAssertionBackgroundTaskManager shared] removeAssertionNeedingBackgroundTask:*this];
 }
 
 void ProcessAndUIAssertion::setState(AssertionState assertionState)
@@ -249,55 +328,18 @@ void ProcessAndUIAssertion::setState(AssertionState assertionState)
     updateRunInBackgroundCount();
 }
 
-void ProcessAndUIAssertion::setClient(ProcessAssertionClient& newClient)
-{
-    [[WKProcessAssertionBackgroundTaskManager shared] addClient:newClient];
-    if (ProcessAssertionClient* oldClient = this->client())
-        [[WKProcessAssertionBackgroundTaskManager shared] removeClient:*oldClient];
-    ProcessAssertion::setClient(newClient);
-}
-
-} // namespace WebKit
-
-#else // PLATFORM(IOS_SIMULATOR)
-
-namespace WebKit {
-
-ProcessAssertion::ProcessAssertion(pid_t, AssertionState assertionState, Function<void()>&&)
-    : m_assertionState(assertionState)
-{
-}
-
-ProcessAssertion::~ProcessAssertion()
+void ProcessAndUIAssertion::uiAssertionWillExpireImminently()
 {
+    if (auto* client = this->client())
+        client->uiAssertionWillExpireImminently();
 }
 
-void ProcessAssertion::setState(AssertionState assertionState)
-{
-    m_assertionState = assertionState;
-}
-
-ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, AssertionState assertionState)
-    : ProcessAssertion(pid, assertionState)
-{
-}
-
-ProcessAndUIAssertion::~ProcessAndUIAssertion()
+void ProcessAndUIAssertion::processAssertionWasInvalidated()
 {
-}
-
-void ProcessAndUIAssertion::setState(AssertionState assertionState)
-{
-    ProcessAssertion::setState(assertionState);
-}
-
-void ProcessAndUIAssertion::setClient(ProcessAssertionClient& newClient)
-{
-    ProcessAssertion::setClient(newClient);
+    ProcessAssertion::processAssertionWasInvalidated();
+    updateRunInBackgroundCount();
 }
 
 } // namespace WebKit
 
-#endif // PLATFORM(IOS_SIMULATOR)
-
-#endif // PLATFORM(IOS)
+#endif // PLATFORM(IOS_FAMILY)