blob: 33dee93022bddad96b65f98bf22e03dd8d97a4eb [file] [log] [blame]
/*
* Copyright (C) 2017 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 "SWClientConnection.h"
#if ENABLE(SERVICE_WORKER)
#include "Document.h"
#include "ExceptionData.h"
#include "MessageEvent.h"
#include "SWContextManager.h"
#include "ServiceWorkerContainer.h"
#include "ServiceWorkerGlobalScope.h"
#include "ServiceWorkerJobData.h"
#include "ServiceWorkerRegistration.h"
#include "SharedWorkerContextManager.h"
#include "SharedWorkerThread.h"
#include "SharedWorkerThreadProxy.h"
#include "Worker.h"
#include "WorkerFetchResult.h"
#include <wtf/CrossThreadCopier.h>
namespace WebCore {
static bool dispatchToContextThreadIfNecessary(const ServiceWorkerOrClientIdentifier& contextIdentifier, Function<void(ScriptExecutionContext&)>&& task)
{
RELEASE_ASSERT(isMainThread());
return switchOn(contextIdentifier, [&] (ScriptExecutionContextIdentifier identifier) {
return ScriptExecutionContext::postTaskTo(identifier, WTFMove(task));
}, [&](ServiceWorkerIdentifier identifier) {
return SWContextManager::singleton().postTaskToServiceWorker(identifier, WTFMove(task));
});
}
SWClientConnection::SWClientConnection() = default;
SWClientConnection::~SWClientConnection() = default;
void SWClientConnection::scheduleJob(ServiceWorkerOrClientIdentifier contextIdentifier, const ServiceWorkerJobData& jobData)
{
auto addResult = m_scheduledJobSources.add(jobData.identifier().jobIdentifier, contextIdentifier);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
scheduleJobInServer(jobData);
}
bool SWClientConnection::postTaskForJob(ServiceWorkerJobIdentifier jobIdentifier, IsJobComplete isJobComplete, Function<void(ServiceWorkerJob&)>&& task)
{
ASSERT(isMainThread());
auto iterator = m_scheduledJobSources.find(jobIdentifier);
if (iterator == m_scheduledJobSources.end()) {
LOG_ERROR("Job %s was not found", jobIdentifier.loggingString().utf8().data());
return false;
}
auto isPosted = dispatchToContextThreadIfNecessary(iterator->value, [jobIdentifier, task = WTFMove(task)] (ScriptExecutionContext& context) mutable {
if (auto* container = context.serviceWorkerContainer()) {
if (auto* job = container->job(jobIdentifier))
task(*job);
}
});
if (isJobComplete == IsJobComplete::Yes)
m_scheduledJobSources.remove(iterator);
return isPosted;
}
void SWClientConnection::jobRejectedInServer(ServiceWorkerJobIdentifier jobIdentifier, ExceptionData&& exceptionData)
{
postTaskForJob(jobIdentifier, IsJobComplete::Yes, [exceptionData = WTFMove(exceptionData).isolatedCopy()] (auto& job) mutable {
job.failedWithException(WTFMove(exceptionData).toException());
});
}
void SWClientConnection::registrationJobResolvedInServer(ServiceWorkerJobIdentifier jobIdentifier, ServiceWorkerRegistrationData&& registrationData, ShouldNotifyWhenResolved shouldNotifyWhenResolved)
{
auto registrationKey = registrationData.key;
bool isPosted = postTaskForJob(jobIdentifier, IsJobComplete::Yes, [registrationData = WTFMove(registrationData).isolatedCopy(), shouldNotifyWhenResolved] (auto& job) mutable {
job.resolvedWithRegistration(WTFMove(registrationData), shouldNotifyWhenResolved);
});
if (!isPosted && shouldNotifyWhenResolved == ShouldNotifyWhenResolved::Yes)
didResolveRegistrationPromise(registrationKey);
}
void SWClientConnection::startScriptFetchForServer(ServiceWorkerJobIdentifier jobIdentifier, ServiceWorkerRegistrationKey&& registrationKey, FetchOptions::Cache cachePolicy)
{
bool isPosted = postTaskForJob(jobIdentifier, IsJobComplete::No, [cachePolicy] (auto& job) {
job.startScriptFetch(cachePolicy);
});
if (!isPosted)
finishFetchingScriptInServer({ serverConnectionIdentifier(), jobIdentifier }, WTFMove(registrationKey), workerFetchError(ResourceError { errorDomainWebKitInternal, 0, { }, makeString("Failed to fetch script for service worker with scope ", registrationKey.scope().string()) }));
}
class RefreshImportedScriptsCallbackHandler {
WTF_MAKE_FAST_ALLOCATED;
public:
explicit RefreshImportedScriptsCallbackHandler(ServiceWorkerJob::RefreshImportedScriptsCallback&& callback)
: m_callback(WTFMove(callback))
{
}
~RefreshImportedScriptsCallbackHandler()
{
if (!m_callback)
return;
callOnMainThread([callback = WTFMove(m_callback)] () mutable {
callback({ });
});
}
void call(Vector<std::pair<URL, ScriptBuffer>>&& scripts)
{
callOnMainThread([callback = WTFMove(m_callback), scripts = crossThreadCopy(WTFMove(scripts))] () mutable {
callback(WTFMove(scripts));
});
}
private:
ServiceWorkerJob::RefreshImportedScriptsCallback m_callback;
};
void SWClientConnection::refreshImportedScripts(ServiceWorkerJobIdentifier jobIdentifier, FetchOptions::Cache cachePolicy, Vector<URL>&& urls, ServiceWorkerJob::RefreshImportedScriptsCallback&& callback)
{
ASSERT(isMainThread());
auto handler = makeUnique<RefreshImportedScriptsCallbackHandler>(WTFMove(callback));
postTaskForJob(jobIdentifier, IsJobComplete::No, [cachePolicy, urls = crossThreadCopy(WTFMove(urls)), handler = WTFMove(handler)] (auto& job) mutable {
job.refreshImportedScripts(urls, cachePolicy, [handler = WTFMove(handler)] (auto&& result) {
handler->call(WTFMove(result));
});
});
}
static void postMessageToContainer(ScriptExecutionContext& context, MessageWithMessagePorts&& message, ServiceWorkerData&& sourceData, String&& sourceOrigin)
{
if (auto* container = context.ensureServiceWorkerContainer())
container->postMessage(WTFMove(message), WTFMove(sourceData), WTFMove(sourceOrigin));
}
void SWClientConnection::postMessageToServiceWorkerClient(ScriptExecutionContextIdentifier destinationContextIdentifier, MessageWithMessagePorts&& message, ServiceWorkerData&& sourceData, String&& sourceOrigin)
{
ASSERT(isMainThread());
if (auto* destinationDocument = Document::allDocumentsMap().get(destinationContextIdentifier)) {
postMessageToContainer(*destinationDocument, WTFMove(message), WTFMove(sourceData), WTFMove(sourceOrigin));
return;
}
if (auto* worker = Worker::byIdentifier(destinationContextIdentifier)) {
worker->postTaskToWorkerGlobalScope([message = WTFMove(message), sourceData = WTFMove(sourceData).isolatedCopy(), sourceOrigin = WTFMove(sourceOrigin).isolatedCopy()] (auto& context) mutable {
postMessageToContainer(context, WTFMove(message), WTFMove(sourceData), WTFMove(sourceOrigin));
});
return;
}
if (auto* sharedWorker = SharedWorkerThreadProxy::byIdentifier(destinationContextIdentifier)) {
sharedWorker->thread().runLoop().postTask([message = WTFMove(message), sourceData = WTFMove(sourceData).isolatedCopy(), sourceOrigin = WTFMove(sourceOrigin).isolatedCopy()] (auto& context) mutable {
postMessageToContainer(context, WTFMove(message), WTFMove(sourceData), WTFMove(sourceOrigin));
});
}
}
static void forAllWorkers(const Function<Function<void(ScriptExecutionContext&)>()>& callback)
{
SWContextManager::singleton().forEachServiceWorker(callback);
Worker::forEachWorker(callback);
SharedWorkerContextManager::singleton().forEachSharedWorker(callback);
}
void SWClientConnection::updateRegistrationState(ServiceWorkerRegistrationIdentifier identifier, ServiceWorkerRegistrationState state, const std::optional<ServiceWorkerData>& serviceWorkerData)
{
ASSERT(isMainThread());
for (auto* document : Document::allDocuments()) {
if (auto* container = document->serviceWorkerContainer())
container->updateRegistrationState(identifier, state, serviceWorkerData);
}
forAllWorkers([identifier, state, &serviceWorkerData] {
return [identifier, state, serviceWorkerData = crossThreadCopy(serviceWorkerData)] (auto& context) mutable {
if (auto* container = context.serviceWorkerContainer())
container->updateRegistrationState(identifier, state, WTFMove(serviceWorkerData));
};
});
}
void SWClientConnection::updateWorkerState(ServiceWorkerIdentifier identifier, ServiceWorkerState state)
{
ASSERT(isMainThread());
for (auto* document : Document::allDocuments()) {
if (auto* container = document->serviceWorkerContainer())
container->updateWorkerState(identifier, state);
}
forAllWorkers([identifier, state] {
return [identifier, state] (auto& context) {
if (auto* container = context.serviceWorkerContainer())
container->updateWorkerState(identifier, state);
};
});
}
void SWClientConnection::fireUpdateFoundEvent(ServiceWorkerRegistrationIdentifier identifier)
{
ASSERT(isMainThread());
for (auto* document : Document::allDocuments()) {
if (auto* container = document->serviceWorkerContainer())
container->queueTaskToFireUpdateFoundEvent(identifier);
}
forAllWorkers([identifier] {
return [identifier] (auto& context) {
if (auto* container = context.serviceWorkerContainer())
container->queueTaskToFireUpdateFoundEvent(identifier);
};
});
}
void SWClientConnection::setRegistrationLastUpdateTime(ServiceWorkerRegistrationIdentifier identifier, WallTime lastUpdateTime)
{
ASSERT(isMainThread());
for (auto* document : Document::allDocuments()) {
if (auto* container = document->serviceWorkerContainer()) {
if (auto* registration = container->registration(identifier))
registration->setLastUpdateTime(lastUpdateTime);
}
}
forAllWorkers([identifier, lastUpdateTime] {
return [identifier, lastUpdateTime] (auto& context) {
if (auto* container = context.serviceWorkerContainer()) {
if (auto* registration = container->registration(identifier))
registration->setLastUpdateTime(lastUpdateTime);
}
};
});
}
void SWClientConnection::setRegistrationUpdateViaCache(ServiceWorkerRegistrationIdentifier identifier, ServiceWorkerUpdateViaCache updateViaCache)
{
ASSERT(isMainThread());
for (auto* document : Document::allDocuments()) {
if (auto* container = document->serviceWorkerContainer()) {
if (auto* registration = container->registration(identifier))
registration->setUpdateViaCache(updateViaCache);
}
}
forAllWorkers([identifier, updateViaCache] {
return [identifier, updateViaCache] (auto& context) {
if (auto* container = context.serviceWorkerContainer()) {
if (auto* registration = container->registration(identifier))
registration->setUpdateViaCache(updateViaCache);
}
};
});
}
static void updateController(ScriptExecutionContext& context, ServiceWorkerData&& newController)
{
context.setActiveServiceWorker(ServiceWorker::getOrCreate(context, WTFMove(newController)));
if (auto* container = context.serviceWorkerContainer())
container->queueTaskToDispatchControllerChangeEvent();
}
void SWClientConnection::notifyClientsOfControllerChange(const HashSet<ScriptExecutionContextIdentifier>& contextIdentifiers, ServiceWorkerData&& newController)
{
ASSERT(isMainThread());
ASSERT(!contextIdentifiers.isEmpty());
for (auto& clientIdentifier : contextIdentifiers) {
if (auto* document = Document::allDocumentsMap().get(clientIdentifier)) {
updateController(*document, ServiceWorkerData { newController });
continue;
}
if (auto* worker = Worker::byIdentifier(clientIdentifier)) {
worker->postTaskToWorkerGlobalScope([newController = newController.isolatedCopy()] (auto& context) mutable {
updateController(context, WTFMove(newController));
});
continue;
}
if (auto* sharedWorker = SharedWorkerThreadProxy::byIdentifier(clientIdentifier)) {
sharedWorker->thread().runLoop().postTask([newController = newController.isolatedCopy()] (auto& context) mutable {
updateController(context, WTFMove(newController));
});
continue;
}
}
}
void SWClientConnection::clearPendingJobs()
{
ASSERT(isMainThread());
auto jobSources = WTFMove(m_scheduledJobSources);
for (auto& keyValue : jobSources) {
dispatchToContextThreadIfNecessary(keyValue.value, [identifier = keyValue.key] (auto& context) {
if (auto* container = context.serviceWorkerContainer()) {
if (auto* job = container->job(identifier))
job->failedWithException(Exception { TypeError, "Internal error"_s });
}
});
}
}
void SWClientConnection::registerServiceWorkerClients()
{
for (auto* document : Document::allDocuments())
document->updateServiceWorkerClientData();
SharedWorkerContextManager::singleton().forEachSharedWorker([] { return [] (auto& context) { context.updateServiceWorkerClientData(); }; });
Worker::forEachWorker([] { return [] (auto& context) { context.updateServiceWorkerClientData(); }; });
}
} // namespace WebCore
#endif // ENABLE(SERVICE_WORKER)