2 * Copyright (C) 2017 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #include "SWServerRegistration.h"
29 #if ENABLE(SERVICE_WORKER)
31 #include "ExceptionData.h"
33 #include "SWServerWorker.h"
34 #include "SecurityOrigin.h"
35 #include "ServiceWorkerFetchResult.h"
36 #include "ServiceWorkerRegistrationData.h"
37 #include "ServiceWorkerUpdateViaCache.h"
38 #include "WorkerType.h"
42 SWServerRegistration::SWServerRegistration(SWServer& server, const ServiceWorkerRegistrationKey& key)
43 : m_jobTimer(*this, &SWServerRegistration::startNextJob)
45 , m_registrationKey(key)
49 SWServerRegistration::~SWServerRegistration()
51 ASSERT(m_jobQueue.isEmpty());
54 void SWServerRegistration::enqueueJob(const ServiceWorkerJobData& jobData)
56 // FIXME: Per the spec, check if this job is equivalent to the last job on the queue.
57 // If it is, stack it along with that job.
59 m_jobQueue.append(jobData);
64 if (!m_jobTimer.isActive())
65 m_jobTimer.startOneShot(0_s);
68 void SWServerRegistration::scriptFetchFinished(SWServer::Connection& connection, const ServiceWorkerFetchResult& result)
70 ASSERT(m_currentJob && m_currentJob->identifier() == result.jobIdentifier);
72 if (!result.scriptError.isNull()) {
73 rejectCurrentJob(ExceptionData { UnknownError, makeString("Script URL ", m_currentJob->scriptURL.string(), " fetch resulted in error: ", result.scriptError.localizedDescription()) });
75 // If newestWorker is null, invoke Clear Registration algorithm passing this registration as its argument.
76 // FIXME: We don't have "clear registration" yet.
81 m_lastUpdateTime = currentTime();
83 // FIXME: If the script data matches byte-for-byte with the existing newestWorker,
84 // then resolve and finish the job without doing anything further.
86 // FIXME: Support the proper worker type (classic vs module)
87 m_server.createWorker(connection, m_registrationKey, m_currentJob->scriptURL, result.script, WorkerType::Classic);
90 void SWServerRegistration::scriptContextFailedToStart(SWServer::Connection&, const String& workerID, const String& message)
92 UNUSED_PARAM(workerID);
94 rejectCurrentJob(ExceptionData { UnknownError, message });
97 void SWServerRegistration::scriptContextStarted(SWServer::Connection&, uint64_t identifier, const String& workerID)
99 UNUSED_PARAM(workerID);
100 resolveCurrentRegistrationJob(ServiceWorkerRegistrationData { m_registrationKey, identifier, m_scopeURL, m_updateViaCache.value_or(ServiceWorkerUpdateViaCache::Imports) });
103 void SWServerRegistration::startNextJob()
105 ASSERT(isMainThread());
106 ASSERT(!m_currentJob);
107 ASSERT(!m_jobQueue.isEmpty());
109 m_currentJob = std::make_unique<ServiceWorkerJobData>(m_jobQueue.takeFirst().isolatedCopy());
111 switch (m_currentJob->type) {
112 case ServiceWorkerJobType::Register:
113 m_server.postTask(createCrossThreadTask(*this, &SWServerRegistration::runRegisterJob, *m_currentJob));
115 case ServiceWorkerJobType::Unregister:
116 m_server.postTask(createCrossThreadTask(*this, &SWServerRegistration::runUnregisterJob, *m_currentJob));
120 RELEASE_ASSERT_NOT_REACHED();
123 bool SWServerRegistration::isEmpty()
125 ASSERT(!isMainThread());
127 // Having or not-having an m_updateViaCache flag is currently
128 // the signal as to whether or not this is an empty (i.e. "new") registration.
129 // There will be a more explicit signal in the near future.
130 return !m_updateViaCache;
133 SWServerWorker* SWServerRegistration::getNewestWorker()
135 ASSERT(!isMainThread());
136 if (m_installingWorker)
137 return m_installingWorker.get();
139 return m_waitingWorker.get();
141 return m_activeWorker.get();
144 void SWServerRegistration::runRegisterJob(const ServiceWorkerJobData& job)
146 ASSERT(!isMainThread());
147 ASSERT(job.type == ServiceWorkerJobType::Register);
149 if (!shouldTreatAsPotentiallyTrustworthy(job.scriptURL))
150 return rejectWithExceptionOnMainThread(ExceptionData { SecurityError, ASCIILiteral("Script URL is not potentially trustworthy") });
152 // If the origin of job’s script url is not job’s referrer's origin, then:
153 if (!protocolHostAndPortAreEqual(job.scriptURL, job.clientCreationURL))
154 return rejectWithExceptionOnMainThread(ExceptionData { SecurityError, ASCIILiteral("Script origin does not match the registering client's origin") });
156 // If the origin of job’s scope url is not job’s referrer's origin, then:
157 if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
158 return rejectWithExceptionOnMainThread(ExceptionData { SecurityError, ASCIILiteral("Scope origin does not match the registering client's origin") });
160 // If registration is not null (in our parlance "empty"), then:
162 ASSERT(m_updateViaCache);
164 m_uninstalling = false;
165 auto* newestWorker = getNewestWorker();
166 if (newestWorker && equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()) && job.registrationOptions.updateViaCache == *m_updateViaCache) {
167 resolveWithRegistrationOnMainThread();
171 m_scopeURL = job.scopeURL.isolatedCopy();
172 m_scopeURL.removeFragmentIdentifier();
173 m_updateViaCache = job.registrationOptions.updateViaCache;
179 void SWServerRegistration::runUnregisterJob(const ServiceWorkerJobData& job)
181 // If the origin of job’s scope url is not job's client's origin, then:
182 if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
183 return rejectWithExceptionOnMainThread(ExceptionData { SecurityError, ASCIILiteral("Origin of scope URL does not match the client's origin") });
185 // Let registration be the result of running "Get Registration" algorithm passing job’s scope url as the argument.
186 // If registration is null, then:
187 if (isEmpty() || m_uninstalling) {
188 // Invoke Resolve Job Promise with job and false.
189 resolveWithUnregistrationResultOnMainThread(false);
193 // Set registration’s uninstalling flag.
194 m_uninstalling = true;
196 // Invoke Resolve Job Promise with job and true.
197 resolveWithUnregistrationResultOnMainThread(true);
199 // FIXME: Invoke Try Clear Registration with registration.
202 void SWServerRegistration::runUpdateJob(const ServiceWorkerJobData& job)
204 // If registration is null (in our parlance "empty") or registration’s uninstalling flag is set, then:
206 return rejectWithExceptionOnMainThread(ExceptionData { TypeError, ASCIILiteral("Cannot update a null/nonexistent service worker registration") });
208 return rejectWithExceptionOnMainThread(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker registration that is uninstalling") });
210 // If job’s job type is update, and newestWorker’s script url does not equal job’s script url with the exclude fragments flag set, then:
211 auto* newestWorker = getNewestWorker();
212 if (newestWorker && !equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()))
213 return rejectWithExceptionOnMainThread(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker with a requested script URL whose newest worker has a different script URL") });
215 startScriptFetchFromMainThread();
218 void SWServerRegistration::rejectWithExceptionOnMainThread(const ExceptionData& exception)
220 ASSERT(!isMainThread());
221 m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::rejectCurrentJob, exception));
224 void SWServerRegistration::resolveWithRegistrationOnMainThread()
226 ASSERT(!isMainThread());
227 m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::resolveCurrentRegistrationJob, data()));
230 void SWServerRegistration::resolveWithUnregistrationResultOnMainThread(bool unregistrationResult)
232 ASSERT(!isMainThread());
233 m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::resolveCurrentUnregistrationJob, unregistrationResult));
236 void SWServerRegistration::startScriptFetchFromMainThread()
238 ASSERT(!isMainThread());
239 m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::startScriptFetchForCurrentJob));
242 void SWServerRegistration::rejectCurrentJob(const ExceptionData& exceptionData)
244 ASSERT(isMainThread());
245 ASSERT(m_currentJob);
247 m_server.rejectJob(*m_currentJob, exceptionData);
252 void SWServerRegistration::resolveCurrentRegistrationJob(const ServiceWorkerRegistrationData& data)
254 ASSERT(isMainThread());
255 ASSERT(m_currentJob);
256 ASSERT(m_currentJob->type == ServiceWorkerJobType::Register);
258 m_server.resolveRegistrationJob(*m_currentJob, data);
263 void SWServerRegistration::resolveCurrentUnregistrationJob(bool unregistrationResult)
265 ASSERT(isMainThread());
266 ASSERT(m_currentJob);
267 ASSERT(m_currentJob->type == ServiceWorkerJobType::Unregister);
269 m_server.resolveUnregistrationJob(*m_currentJob, m_registrationKey, unregistrationResult);
274 void SWServerRegistration::startScriptFetchForCurrentJob()
276 ASSERT(isMainThread());
277 ASSERT(m_currentJob);
279 m_server.startScriptFetch(*m_currentJob);
282 void SWServerRegistration::finishCurrentJob()
284 ASSERT(m_currentJob);
285 ASSERT(!m_jobTimer.isActive());
287 m_currentJob = nullptr;
288 if (m_jobQueue.isEmpty())
294 ServiceWorkerRegistrationData SWServerRegistration::data() const
296 return { m_registrationKey, identifier(), m_scopeURL, m_updateViaCache.value_or(ServiceWorkerUpdateViaCache::Imports) };
300 } // namespace WebCore
302 #endif // ENABLE(SERVICE_WORKER)