blob: 6f46a9417c3dea76f68427801cae3b41727f2628 [file] [log] [blame]
/*
* Copyright (C) 2021, 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.
*/
#include "config.h"
#include "WebLockRegistry.h"
#include "Exception.h"
#include "WebLockManager.h"
#include "WebLockManagerSnapshot.h"
#include <wtf/CompletionHandler.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RobinHoodHashMap.h>
#include <wtf/text/StringConcatenateNumbers.h>
namespace WebCore {
static RefPtr<WebLockRegistry>& sharedRegistry()
{
static MainThreadNeverDestroyed<RefPtr<WebLockRegistry>> registry;
return registry;
}
WebLockRegistry& WebLockRegistry::shared()
{
auto& registry = sharedRegistry();
if (!registry)
registry= LocalWebLockRegistry::create();
return *registry;
}
void WebLockRegistry::setSharedRegistry(Ref<WebLockRegistry>&& registry)
{
ASSERT(!sharedRegistry());
sharedRegistry() = WTFMove(registry);
}
class LocalWebLockRegistry::PerOriginRegistry : public RefCounted<PerOriginRegistry>, public CanMakeWeakPtr<PerOriginRegistry> {
public:
static Ref<PerOriginRegistry> create(LocalWebLockRegistry&, PAL::SessionID, const ClientOrigin&);
~PerOriginRegistry();
struct LockInfo {
Ref<PerOriginRegistry> registry;
WebLockIdentifier lockIdentifier;
ScriptExecutionContextIdentifier clientID;
WebLockMode mode;
Function<void()> lockStolenHandler;
};
void requestLock(WebLockIdentifier, ScriptExecutionContextIdentifier, const String& name, WebLockMode, bool steal, bool ifAvailable, Function<void(bool)>&& grantedHandler, Function<void()>&& lockStolenHandler);
void releaseLock(WebLockIdentifier, const String& name);
void abortLockRequest(WebLockIdentifier, const String& name, CompletionHandler<void(bool)>&&);
void snapshot(CompletionHandler<void(WebLockManagerSnapshot&&)>&&);
void clientsAreGoingAway(const Function<bool(const LockInfo&)>& matchClient);
private:
PerOriginRegistry(LocalWebLockRegistry&, PAL::SessionID, const ClientOrigin&);
struct LockRequest : LockInfo {
String name;
Function<void(bool)> grantedHandler;
};
void processLockRequestQueue(const String& name, Deque<LockRequest>&);
bool isGrantable(const LockRequest&) const;
WeakPtr<LocalWebLockRegistry> m_globalRegistry;
PAL::SessionID m_sessionID;
ClientOrigin m_clientOrigin;
FastRobinHoodHashMap<String, Deque<LockRequest>> m_lockRequestQueueMap;
FastRobinHoodHashMap<String, Vector<LockInfo>> m_heldLocks;
};
LocalWebLockRegistry::LocalWebLockRegistry() = default;
LocalWebLockRegistry::~LocalWebLockRegistry() = default;
auto LocalWebLockRegistry::ensureRegistryForOrigin(PAL::SessionID sessionID, const ClientOrigin& clientOrigin) -> Ref<PerOriginRegistry>
{
if (auto existingRegistry = m_perOriginRegistries.get({ sessionID, clientOrigin }))
return *existingRegistry;
return PerOriginRegistry::create(*this, sessionID, clientOrigin);
}
auto LocalWebLockRegistry::existingRegistryForOrigin(PAL::SessionID sessionID, const ClientOrigin& clientOrigin) const -> RefPtr<PerOriginRegistry>
{
return m_perOriginRegistries.get({ sessionID, clientOrigin }).get();
}
Ref<LocalWebLockRegistry::PerOriginRegistry> LocalWebLockRegistry::PerOriginRegistry::create(LocalWebLockRegistry& globalRegistry, PAL::SessionID sessionID, const ClientOrigin& clientOrigin)
{
return adoptRef(*new PerOriginRegistry(globalRegistry, sessionID, clientOrigin));
}
LocalWebLockRegistry::PerOriginRegistry::PerOriginRegistry(LocalWebLockRegistry& globalRegistry, PAL::SessionID sessionID, const ClientOrigin& clientOrigin)
: m_globalRegistry(globalRegistry)
, m_sessionID(sessionID)
, m_clientOrigin(clientOrigin)
{
globalRegistry.m_perOriginRegistries.add({ sessionID, clientOrigin }, WeakPtr { * this });
}
LocalWebLockRegistry::PerOriginRegistry::~PerOriginRegistry()
{
if (m_globalRegistry)
m_globalRegistry->m_perOriginRegistries.remove({ m_sessionID, m_clientOrigin });
}
void LocalWebLockRegistry::requestLock(PAL::SessionID sessionID, const ClientOrigin& clientOrigin, WebLockIdentifier lockIdentifier, ScriptExecutionContextIdentifier clientID, const String& name, WebLockMode mode, bool steal, bool ifAvailable, Function<void(bool)>&& grantedHandler, Function<void()>&& lockStolenHandler)
{
ensureRegistryForOrigin(sessionID, clientOrigin)->requestLock(lockIdentifier, clientID, name, mode, steal, ifAvailable, WTFMove(grantedHandler), WTFMove(lockStolenHandler));
}
// https://wicg.github.io/web-locks/#request-a-lock
void LocalWebLockRegistry::PerOriginRegistry::requestLock(WebLockIdentifier lockIdentifier, ScriptExecutionContextIdentifier clientID, const String& name, WebLockMode mode, bool steal, bool ifAvailable, Function<void(bool)>&& grantedHandler, Function<void()>&& lockStolenHandler)
{
LockRequest request { { *this, lockIdentifier, clientID, mode, WTFMove(lockStolenHandler) }, name, WTFMove(grantedHandler) };
if (steal) {
auto it = m_heldLocks.find(name);
if (it != m_heldLocks.end()) {
for (auto& lockInfo : it->value)
lockInfo.lockStolenHandler();
m_heldLocks.remove(it);
}
} else if (ifAvailable && !isGrantable(request)) {
request.grantedHandler(false);
return;
}
auto& queue = m_lockRequestQueueMap.ensure(name, [] { return Deque<LockRequest> { }; }).iterator->value;
if (steal)
queue.prepend(WTFMove(request));
else
queue.append(WTFMove(request));
processLockRequestQueue(name, queue);
}
void LocalWebLockRegistry::releaseLock(PAL::SessionID sessionID, const ClientOrigin& clientOrigin, WebLockIdentifier lockIdentifier, ScriptExecutionContextIdentifier, const String& name)
{
if (auto registry = existingRegistryForOrigin(sessionID, clientOrigin))
registry->releaseLock(lockIdentifier, name);
}
// https://wicg.github.io/web-locks/#algorithm-release-lock
void LocalWebLockRegistry::PerOriginRegistry::releaseLock(WebLockIdentifier lockIdentifier, const String& name)
{
auto it = m_heldLocks.find(name);
if (it == m_heldLocks.end())
return;
auto& locksWithName = it->value;
locksWithName.removeFirstMatching([lockIdentifier](auto& lockInfo) { return lockIdentifier == lockInfo.lockIdentifier; });
if (locksWithName.isEmpty())
m_heldLocks.remove(it);
auto queueIterator = m_lockRequestQueueMap.find(name);
if (queueIterator != m_lockRequestQueueMap.end())
processLockRequestQueue(name, queueIterator->value);
}
void LocalWebLockRegistry::abortLockRequest(PAL::SessionID sessionID, const ClientOrigin& clientOrigin, WebLockIdentifier lockIdentifier, ScriptExecutionContextIdentifier, const String& name, CompletionHandler<void(bool)>&& completionHandler)
{
auto registry = existingRegistryForOrigin(sessionID, clientOrigin);
if (!registry)
return completionHandler(false);
registry->abortLockRequest(lockIdentifier, name, WTFMove(completionHandler));
}
// https://wicg.github.io/web-locks/#abort-the-request
void LocalWebLockRegistry::PerOriginRegistry::abortLockRequest(WebLockIdentifier lockIdentifier, const String& name, CompletionHandler<void(bool)>&& completionHandler)
{
auto queueIterator = m_lockRequestQueueMap.find(name);
if (queueIterator == m_lockRequestQueueMap.end())
return completionHandler(false);
auto& queue = queueIterator->value;
auto requestIterator = queue.findIf([lockIdentifier](auto& request) { return request.lockIdentifier == lockIdentifier; });
if (requestIterator == queue.end())
return completionHandler(false);
queue.remove(requestIterator);
if (queue.isEmpty()) {
m_lockRequestQueueMap.remove(queueIterator);
return completionHandler(true);
}
processLockRequestQueue(name, queue);
completionHandler(true);
}
// https://wicg.github.io/web-locks/#grantable
bool LocalWebLockRegistry::PerOriginRegistry::isGrantable(const LockRequest& request) const
{
auto queueIterator = m_lockRequestQueueMap.find(request.name);
if (queueIterator != m_lockRequestQueueMap.end() && &queueIterator->value.first() != &request)
return false;
switch (request.mode) {
case WebLockMode::Exclusive:
return !m_heldLocks.contains(request.name);
case WebLockMode::Shared:
break;
}
auto it = m_heldLocks.find(request.name);
return it == m_heldLocks.end() || it->value.first().mode != WebLockMode::Exclusive;
}
// https://wicg.github.io/web-locks/#process-the-lock-request-queue
void LocalWebLockRegistry::PerOriginRegistry::processLockRequestQueue(const String& name, Deque<LockRequest>& queue)
{
while (!queue.isEmpty()) {
if (!isGrantable(queue.first()))
return;
auto request = queue.takeFirst();
auto& locksForName = m_heldLocks.ensure(request.name, [] { return Vector<LockInfo> { }; }).iterator->value;
auto grantedHandler = WTFMove(request.grantedHandler);
locksForName.append(WTFMove(request));
grantedHandler(true);
}
auto removedQueue = m_lockRequestQueueMap.take(name);
ASSERT_UNUSED(removedQueue, removedQueue.isEmpty());
}
void LocalWebLockRegistry::snapshot(PAL::SessionID sessionID, const ClientOrigin& clientOrigin, CompletionHandler<void(WebLockManager::Snapshot&&)>&& completionHandler)
{
auto registry = existingRegistryForOrigin(sessionID, clientOrigin);
if (!registry)
return completionHandler({ });
registry->snapshot(WTFMove(completionHandler));
}
// https://wicg.github.io/web-locks/#snapshot-the-lock-state
void LocalWebLockRegistry::PerOriginRegistry::snapshot(CompletionHandler<void(WebLockManager::Snapshot&&)>&& completionHandler)
{
WebLockManager::Snapshot snapshot;
for (auto& pair : m_lockRequestQueueMap) {
for (auto& request : pair.value)
snapshot.pending.append({ pair.key, request.mode, request.clientID.toString() });
}
for (auto& pair : m_heldLocks) {
for (auto& lockInfo : pair.value)
snapshot.held.append({ pair.key, lockInfo.mode, lockInfo.clientID.toString() });
}
completionHandler(WTFMove(snapshot));
}
void LocalWebLockRegistry::clientIsGoingAway(PAL::SessionID sessionID, const ClientOrigin& clientOrigin, ScriptExecutionContextIdentifier clientID)
{
if (auto registry = existingRegistryForOrigin(sessionID, clientOrigin))
registry->clientsAreGoingAway([clientID](auto& lockInfo) { return lockInfo.clientID == clientID; });
}
// https://wicg.github.io/web-locks/#agent-integration
void LocalWebLockRegistry::PerOriginRegistry::clientsAreGoingAway(const Function<bool(const LockInfo&)>& matchClient)
{
// FIXME: This is inefficient. We could optimize this by keeping track of which locks map to which clients.
HashSet<String> namesOfQueuesToProcess;
Vector<String> namesWithoutRequests;
for (auto& pair : m_lockRequestQueueMap) {
if (!pair.value.removeAllMatching(matchClient))
continue;
if (pair.value.isEmpty())
namesWithoutRequests.append(pair.key);
else
namesOfQueuesToProcess.add(pair.key);
}
for (auto& name : namesWithoutRequests)
m_lockRequestQueueMap.remove(name);
Vector<String> namesWithoutLocks;
for (auto& pair : m_heldLocks) {
if (!pair.value.removeAllMatching(matchClient))
continue;
if (pair.value.isEmpty())
namesWithoutLocks.append(pair.key);
namesOfQueuesToProcess.add(pair.key);
}
for (auto& name : namesWithoutLocks)
m_heldLocks.remove(name);
for (auto& name : namesOfQueuesToProcess) {
auto queueIterator = m_lockRequestQueueMap.find(name);
if (queueIterator != m_lockRequestQueueMap.end())
processLockRequestQueue(name, queueIterator->value);
}
}
void LocalWebLockRegistry::clientsAreGoingAway(ProcessIdentifier processIdentifier)
{
Vector<std::pair<PAL::SessionID, ClientOrigin>> clientOrigins = copyToVector(m_perOriginRegistries.keys());
for (auto& [sessionID, clientOrigin] : clientOrigins) {
if (auto registry = existingRegistryForOrigin(sessionID, clientOrigin))
registry->clientsAreGoingAway([processIdentifier](auto& lockInfo) { return lockInfo.clientID.processIdentifier() == processIdentifier; });
}
}
} // namespace WebCore