SWServerJobQueue.cpp [plain text]
#include "config.h"
#include "SWServerJobQueue.h"
#if ENABLE(SERVICE_WORKER)
#include "ExceptionData.h"
#include "SWServer.h"
#include "SWServerWorker.h"
#include "SchemeRegistry.h"
#include "SecurityOrigin.h"
#include "ServiceWorkerFetchResult.h"
#include "ServiceWorkerRegistrationData.h"
#include "ServiceWorkerUpdateViaCache.h"
#include "WorkerType.h"
namespace WebCore {
SWServerJobQueue::SWServerJobQueue(SWServer& server, const ServiceWorkerRegistrationKey& key)
: m_jobTimer(*this, &SWServerJobQueue::runNextJobSynchronously)
, m_server(server)
, m_registrationKey(key)
{
}
SWServerJobQueue::~SWServerJobQueue()
{
}
bool SWServerJobQueue::isCurrentlyProcessingJob(const ServiceWorkerJobDataIdentifier& jobDataIdentifier) const
{
return !m_jobQueue.isEmpty() && firstJob().identifier() == jobDataIdentifier;
}
void SWServerJobQueue::scriptFetchFinished(SWServer::Connection& connection, const ServiceWorkerFetchResult& result)
{
if (!isCurrentlyProcessingJob(result.jobDataIdentifier))
return;
auto& job = firstJob();
auto* registration = m_server.getRegistration(m_registrationKey);
if (!registration)
return;
auto* newestWorker = registration->getNewestWorker();
if (!result.scriptError.isNull()) {
m_server.rejectJob(job, ExceptionData { TypeError, makeString("Script URL ", job.scriptURL.string(), " fetch resulted in error: ", result.scriptError.localizedDescription()) });
if (!newestWorker)
registration->clear();
finishCurrentJob();
return;
}
registration->setLastUpdateTime(WallTime::now());
if (newestWorker && equalIgnoringFragmentIdentifier(newestWorker->scriptURL(), job.scriptURL) && result.script == newestWorker->script()) {
m_server.resolveRegistrationJob(job, registration->data(), ShouldNotifyWhenResolved::No);
finishCurrentJob();
return;
}
m_server.updateWorker(connection, job.identifier(), *registration, job.scriptURL, result.script, result.contentSecurityPolicy, result.referrerPolicy, WorkerType::Classic, { });
}
void SWServerJobQueue::scriptContextFailedToStart(const ServiceWorkerJobDataIdentifier& jobDataIdentifier, ServiceWorkerIdentifier, const String& message)
{
if (!isCurrentlyProcessingJob(jobDataIdentifier))
return;
auto* registration = m_server.getRegistration(m_registrationKey);
ASSERT(registration);
ASSERT(registration->preInstallationWorker());
registration->preInstallationWorker()->terminate();
registration->setPreInstallationWorker(nullptr);
m_server.rejectJob(firstJob(), { TypeError, message });
if (!registration->getNewestWorker())
registration->clear();
finishCurrentJob();
}
void SWServerJobQueue::scriptContextStarted(const ServiceWorkerJobDataIdentifier& jobDataIdentifier, ServiceWorkerIdentifier identifier)
{
if (!isCurrentlyProcessingJob(jobDataIdentifier))
return;
auto* registration = m_server.getRegistration(m_registrationKey);
ASSERT(registration);
install(*registration, identifier);
}
void SWServerJobQueue::install(SWServerRegistration& registration, ServiceWorkerIdentifier installingWorker)
{
auto* worker = m_server.workerByID(installingWorker);
RELEASE_ASSERT(worker);
ASSERT(registration.preInstallationWorker() == worker);
registration.setPreInstallationWorker(nullptr);
registration.updateRegistrationState(ServiceWorkerRegistrationState::Installing, worker);
registration.updateWorkerState(*worker, ServiceWorkerState::Installing);
m_server.resolveRegistrationJob(firstJob(), registration.data(), ShouldNotifyWhenResolved::Yes);
}
void SWServerJobQueue::didResolveRegistrationPromise()
{
auto* registration = m_server.getRegistration(m_registrationKey);
ASSERT(registration);
ASSERT(registration->installingWorker());
RELEASE_LOG(ServiceWorker, "%p - SWServerJobQueue::didResolveRegistrationPromise: Registration ID: %llu. Now proceeding with install", this, registration->identifier().toUInt64());
registration->fireUpdateFoundEvent();
ASSERT(registration->installingWorker());
m_server.fireInstallEvent(*registration->installingWorker());
}
void SWServerJobQueue::didFinishInstall(const ServiceWorkerJobDataIdentifier& jobDataIdentifier, ServiceWorkerIdentifier identifier, bool wasSuccessful)
{
if (!isCurrentlyProcessingJob(jobDataIdentifier))
return;
auto* registration = m_server.getRegistration(m_registrationKey);
ASSERT(registration);
ASSERT(registration->installingWorker());
ASSERT(registration->installingWorker()->identifier() == identifier);
if (!wasSuccessful) {
RefPtr<SWServerWorker> worker = m_server.workerByID(identifier);
RELEASE_ASSERT(worker);
worker->terminate();
registration->updateRegistrationState(ServiceWorkerRegistrationState::Installing, nullptr);
registration->updateWorkerState(*worker, ServiceWorkerState::Redundant);
if (!registration->getNewestWorker())
registration->clear();
finishCurrentJob();
return;
}
if (auto* waitingWorker = registration->waitingWorker()) {
waitingWorker->terminate();
registration->updateWorkerState(*waitingWorker, ServiceWorkerState::Redundant);
}
auto* installing = registration->installingWorker();
ASSERT(installing);
registration->updateRegistrationState(ServiceWorkerRegistrationState::Waiting, installing);
registration->updateRegistrationState(ServiceWorkerRegistrationState::Installing, nullptr);
registration->updateWorkerState(*installing, ServiceWorkerState::Installed);
finishCurrentJob();
registration->tryActivate();
}
void SWServerJobQueue::runNextJob()
{
ASSERT(!m_jobQueue.isEmpty());
ASSERT(!m_jobTimer.isActive());
m_jobTimer.startOneShot(0_s);
}
void SWServerJobQueue::runNextJobSynchronously()
{
ASSERT(!m_jobQueue.isEmpty());
if (m_jobQueue.isEmpty())
return;
auto& job = firstJob();
switch (job.type) {
case ServiceWorkerJobType::Register:
runRegisterJob(job);
return;
case ServiceWorkerJobType::Unregister:
runUnregisterJob(job);
return;
case ServiceWorkerJobType::Update:
runUpdateJob(job);
return;
}
ASSERT_NOT_REACHED();
}
void SWServerJobQueue::runRegisterJob(const ServiceWorkerJobData& job)
{
ASSERT(job.type == ServiceWorkerJobType::Register);
if (!shouldTreatAsPotentiallyTrustworthy(job.scriptURL) && !SchemeRegistry::isServiceWorkerContainerCustomScheme(job.scriptURL.protocol().toStringWithoutCopying()))
return rejectCurrentJob(ExceptionData { SecurityError, "Script URL is not potentially trustworthy"_s });
if (!protocolHostAndPortAreEqual(job.scriptURL, job.clientCreationURL))
return rejectCurrentJob(ExceptionData { SecurityError, "Script origin does not match the registering client's origin"_s });
if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
return rejectCurrentJob(ExceptionData { SecurityError, "Scope origin does not match the registering client's origin"_s });
if (auto* registration = m_server.getRegistration(m_registrationKey)) {
registration->setIsUninstalling(false);
auto* newestWorker = registration->getNewestWorker();
if (newestWorker && equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()) && job.registrationOptions.updateViaCache == registration->updateViaCache()) {
RELEASE_LOG(ServiceWorker, "%p - SWServerJobQueue::runRegisterJob: Found directly reusable registration %llu for job %s (DONE)", this, registration->identifier().toUInt64(), job.identifier().loggingString().utf8().data());
m_server.resolveRegistrationJob(job, registration->data(), ShouldNotifyWhenResolved::No);
finishCurrentJob();
return;
}
if (registration->updateViaCache() != job.registrationOptions.updateViaCache)
registration->setUpdateViaCache(job.registrationOptions.updateViaCache);
RELEASE_LOG(ServiceWorker, "%p - SWServerJobQueue::runRegisterJob: Found registration %llu for job %s but it needs updating", this, registration->identifier().toUInt64(), job.identifier().loggingString().utf8().data());
} else {
auto newRegistration = std::make_unique<SWServerRegistration>(m_server, m_registrationKey, job.registrationOptions.updateViaCache, job.scopeURL, job.scriptURL);
m_server.addRegistration(WTFMove(newRegistration));
RELEASE_LOG(ServiceWorker, "%p - SWServerJobQueue::runRegisterJob: No existing registration for job %s, constructing a new one.", this, job.identifier().loggingString().utf8().data());
}
runUpdateJob(job);
}
void SWServerJobQueue::runUnregisterJob(const ServiceWorkerJobData& job)
{
if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
return rejectCurrentJob(ExceptionData { SecurityError, "Origin of scope URL does not match the client's origin"_s });
auto* registration = m_server.getRegistration(m_registrationKey);
if (!registration || registration->isUninstalling()) {
m_server.resolveUnregistrationJob(job, m_registrationKey, false);
finishCurrentJob();
return;
}
registration->setIsUninstalling(true);
m_server.resolveUnregistrationJob(job, m_registrationKey, true);
registration->tryClear();
finishCurrentJob();
}
void SWServerJobQueue::runUpdateJob(const ServiceWorkerJobData& job)
{
auto* registration = m_server.getRegistration(m_registrationKey);
if (!registration)
return rejectCurrentJob(ExceptionData { TypeError, "Cannot update a null/nonexistent service worker registration"_s });
if (registration->isUninstalling())
return rejectCurrentJob(ExceptionData { TypeError, "Cannot update a service worker registration that is uninstalling"_s });
auto* newestWorker = registration->getNewestWorker();
if (job.type == ServiceWorkerJobType::Update && newestWorker && !equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()))
return rejectCurrentJob(ExceptionData { TypeError, "Cannot update a service worker with a requested script URL whose newest worker has a different script URL"_s });
FetchOptions::Cache cachePolicy = FetchOptions::Cache::Default;
if (registration->updateViaCache() != ServiceWorkerUpdateViaCache::All
|| (newestWorker && registration->lastUpdateTime() && (WallTime::now() - registration->lastUpdateTime()) > 86400_s)) {
cachePolicy = FetchOptions::Cache::NoCache;
}
m_server.startScriptFetch(job, cachePolicy);
}
void SWServerJobQueue::rejectCurrentJob(const ExceptionData& exceptionData)
{
m_server.rejectJob(firstJob(), exceptionData);
finishCurrentJob();
}
void SWServerJobQueue::finishCurrentJob()
{
ASSERT(!m_jobTimer.isActive());
m_jobQueue.removeFirst();
if (!m_jobQueue.isEmpty())
runNextJob();
}
void SWServerJobQueue::removeAllJobsMatching(const WTF::Function<bool(ServiceWorkerJobData&)>& matches)
{
bool isFirst = true;
bool didRemoveFirstJob = false;
m_jobQueue.removeAllMatching([&](auto& job) {
bool shouldRemove = matches(job);
if (isFirst) {
isFirst = false;
if (shouldRemove)
didRemoveFirstJob = true;
}
return shouldRemove;
});
if (m_jobTimer.isActive()) {
if (m_jobQueue.isEmpty())
m_jobTimer.stop();
} else if (didRemoveFirstJob && !m_jobQueue.isEmpty())
runNextJob();
}
void SWServerJobQueue::cancelJobsFromConnection(SWServerConnectionIdentifier connectionIdentifier)
{
removeAllJobsMatching([connectionIdentifier](auto& job) {
return job.identifier().connectionIdentifier == connectionIdentifier;
});
}
void SWServerJobQueue::cancelJobsFromServiceWorker(ServiceWorkerIdentifier serviceWorkerIdentifier)
{
removeAllJobsMatching([serviceWorkerIdentifier](auto& job) {
return WTF::holds_alternative<ServiceWorkerIdentifier>(job.sourceContext) && WTF::get<ServiceWorkerIdentifier>(job.sourceContext) == serviceWorkerIdentifier;
});
}
}
#endif // ENABLE(SERVICE_WORKER)