/* * 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 #import #import #import #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 _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()); Vector clientsToNotify; copyToVector(_clients, clientsToNotify); for (auto* client : clientsToNotify) 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&& invalidationCallback) : m_weakFactory(this) , 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; m_invalidationCallback(); } void ProcessAssertion::setState(AssertionState assertionState) { if (m_assertionState == assertionState) return; RELEASE_LOG(ProcessSuspension, "%p - ProcessAssertion::setState(%u)", this, static_cast(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&&) : 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)