blob: 613610de9dd48b48da57b5f0bab6b2144324a298 [file] [log] [blame]
/*
* Copyright (C) 2014 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "ProcessAssertion.h"
#if PLATFORM(IOS)
#import "AssertionServicesSPI.h"
#import "Logging.h"
#import <UIKit/UIApplication.h>
#import <wtf/HashSet.h>
#import <wtf/RunLoop.h>
#import <wtf/Vector.h>
#if !PLATFORM(IOS_SIMULATOR)
using WebKit::ProcessAssertionClient;
@interface WKProcessAssertionBackgroundTaskManager : NSObject
+ (WKProcessAssertionBackgroundTaskManager *)shared;
- (void)incrementNeedsToRunInBackgroundCount;
- (void)decrementNeedsToRunInBackgroundCount;
- (void)addClient:(ProcessAssertionClient&)client;
- (void)removeClient:(ProcessAssertionClient&)client;
@end
@implementation WKProcessAssertionBackgroundTaskManager
{
unsigned _needsToRunInBackgroundCount;
UIBackgroundTaskIdentifier _backgroundTask;
HashSet<ProcessAssertionClient*> _clients;
}
+ (WKProcessAssertionBackgroundTaskManager *)shared
{
static WKProcessAssertionBackgroundTaskManager *shared = [WKProcessAssertionBackgroundTaskManager new];
return shared;
}
- (instancetype)init
{
self = [super init];
if (!self)
return nil;
_backgroundTask = UIBackgroundTaskInvalid;
return self;
}
- (void)dealloc
{
if (_backgroundTask != UIBackgroundTaskInvalid)
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
[super dealloc];
}
- (void)addClient:(ProcessAssertionClient&)client
{
_clients.add(&client);
}
- (void)removeClient:(ProcessAssertionClient&)client
{
_clients.remove(&client);
}
- (void)_notifyClientsOfImminentSuspension
{
ASSERT(RunLoop::isMain());
for (auto* client : copyToVector(_clients))
client->assertionWillExpireImminently();
}
- (void)_updateBackgroundTask
{
if (_needsToRunInBackgroundCount && _backgroundTask == UIBackgroundTaskInvalid) {
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());
// 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];
else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _notifyClientsOfImminentSuspension];
});
}
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}];
}
if (!_needsToRunInBackgroundCount && _backgroundTask != UIBackgroundTaskInvalid) {
RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - endBackgroundTask", self);
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}
}
- (void)incrementNeedsToRunInBackgroundCount
{
++_needsToRunInBackgroundCount;
[self _updateBackgroundTask];
}
- (void)decrementNeedsToRunInBackgroundCount
{
--_needsToRunInBackgroundCount;
[self _updateBackgroundTask];
}
@end
namespace WebKit {
const BKSProcessAssertionFlags suspendedTabFlags = (BKSProcessAssertionAllowIdleSleep);
const BKSProcessAssertionFlags backgroundTabFlags = (BKSProcessAssertionAllowIdleSleep | BKSProcessAssertionPreventTaskSuspend);
const BKSProcessAssertionFlags foregroundTabFlags = (BKSProcessAssertionAllowIdleSleep | BKSProcessAssertionPreventTaskSuspend | BKSProcessAssertionWantsForegroundResourcePriority | BKSProcessAssertionPreventTaskThrottleDown);
static BKSProcessAssertionFlags flagsForState(AssertionState assertionState)
{
switch (assertionState) {
case AssertionState::Suspended:
return suspendedTabFlags;
case AssertionState::Background:
return backgroundTabFlags;
case AssertionState::Foreground:
return foregroundTabFlags;
}
}
ProcessAssertion::ProcessAssertion(pid_t pid, AssertionState assertionState, Function<void()>&& invalidationCallback)
: m_invalidationCallback(WTFMove(invalidationCallback))
, m_assertionState(assertionState)
{
auto weakThis = createWeakPtr();
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();
dispatch_async(dispatch_get_main_queue(), ^{
if (weakThis)
markAsInvalidated();
});
}
};
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]);
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();
});
};
}
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()
{
ASSERT(RunLoop::isMain());
m_validity = Validity::No;
if (m_invalidationCallback)
m_invalidationCallback();
}
void ProcessAssertion::setState(AssertionState assertionState)
{
if (m_assertionState == assertionState)
return;
RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion::setState(%u)", this, static_cast<unsigned>(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];
}
m_isHoldingBackgroundAssertion = shouldHoldBackgroundAssertion;
}
ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, AssertionState assertionState)
: ProcessAssertion(pid, assertionState, [this] { updateRunInBackgroundCount(); })
{
updateRunInBackgroundCount();
}
ProcessAndUIAssertion::~ProcessAndUIAssertion()
{
if (m_isHoldingBackgroundAssertion)
[[WKProcessAssertionBackgroundTaskManager shared] decrementNeedsToRunInBackgroundCount];
}
void ProcessAndUIAssertion::setState(AssertionState assertionState)
{
ProcessAssertion::setState(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 ProcessAssertion::setState(AssertionState assertionState)
{
m_assertionState = assertionState;
}
ProcessAndUIAssertion::ProcessAndUIAssertion(pid_t pid, AssertionState assertionState)
: ProcessAssertion(pid, assertionState)
{
}
ProcessAndUIAssertion::~ProcessAndUIAssertion()
{
}
void ProcessAndUIAssertion::setState(AssertionState assertionState)
{
ProcessAssertion::setState(assertionState);
}
void ProcessAndUIAssertion::setClient(ProcessAssertionClient& newClient)
{
ProcessAssertion::setClient(newClient);
}
} // namespace WebKit
#endif // PLATFORM(IOS_SIMULATOR)
#endif // PLATFORM(IOS)