Add support for unregistering a service worker
[WebKit-https.git] / Source / WebCore / workers / service / server / SWServerRegistration.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
26 #include "config.h"
27 #include "SWServerRegistration.h"
28
29 #if ENABLE(SERVICE_WORKER)
30
31 #include "ExceptionData.h"
32 #include "SWServer.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"
39
40 namespace WebCore {
41
42 SWServerRegistration::SWServerRegistration(SWServer& server, const ServiceWorkerRegistrationKey& key)
43     : m_jobTimer(*this, &SWServerRegistration::startNextJob)
44     , m_server(server)
45     , m_registrationKey(key)
46 {
47 }
48
49 SWServerRegistration::~SWServerRegistration()
50 {
51     ASSERT(m_jobQueue.isEmpty());
52 }
53
54 void SWServerRegistration::enqueueJob(const ServiceWorkerJobData& jobData)
55 {
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.
58
59     m_jobQueue.append(jobData);
60
61     if (m_currentJob)
62         return;
63
64     if (!m_jobTimer.isActive())
65         m_jobTimer.startOneShot(0_s);
66 }
67
68 void SWServerRegistration::scriptFetchFinished(SWServer::Connection& connection, const ServiceWorkerFetchResult& result)
69 {
70     ASSERT(m_currentJob && m_currentJob->identifier() == result.jobIdentifier);
71
72     if (!result.scriptError.isNull()) {
73         rejectCurrentJob(ExceptionData { UnknownError, makeString("Script URL ", m_currentJob->scriptURL.string(), " fetch resulted in error: ", result.scriptError.localizedDescription()) });
74         
75         // If newestWorker is null, invoke Clear Registration algorithm passing this registration as its argument.
76         // FIXME: We don't have "clear registration" yet.
77
78         return;
79     }
80
81     m_lastUpdateTime = currentTime();
82     
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.
85
86     // FIXME: Support the proper worker type (classic vs module)
87     m_server.createWorker(connection, m_registrationKey, m_currentJob->scriptURL, result.script, WorkerType::Classic);
88 }
89
90 void SWServerRegistration::scriptContextFailedToStart(SWServer::Connection&, const String& workerID, const String& message)
91 {
92     UNUSED_PARAM(workerID);
93
94     rejectCurrentJob(ExceptionData { UnknownError, message });
95 }
96
97 void SWServerRegistration::scriptContextStarted(SWServer::Connection&, uint64_t identifier, const String& workerID)
98 {
99     UNUSED_PARAM(workerID);
100     resolveCurrentRegistrationJob(ServiceWorkerRegistrationData { m_registrationKey, identifier, m_scopeURL, m_updateViaCache.value_or(ServiceWorkerUpdateViaCache::Imports) });
101 }
102
103 void SWServerRegistration::startNextJob()
104 {
105     ASSERT(isMainThread());
106     ASSERT(!m_currentJob);
107     ASSERT(!m_jobQueue.isEmpty());
108
109     m_currentJob = std::make_unique<ServiceWorkerJobData>(m_jobQueue.takeFirst().isolatedCopy());
110
111     switch (m_currentJob->type) {
112     case ServiceWorkerJobType::Register:
113         m_server.postTask(createCrossThreadTask(*this, &SWServerRegistration::runRegisterJob, *m_currentJob));
114         return;
115     case ServiceWorkerJobType::Unregister:
116         m_server.postTask(createCrossThreadTask(*this, &SWServerRegistration::runUnregisterJob, *m_currentJob));
117         return;
118     }
119
120     RELEASE_ASSERT_NOT_REACHED();
121 }
122
123 bool SWServerRegistration::isEmpty()
124 {
125     ASSERT(!isMainThread());
126
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;
131 }
132
133 SWServerWorker* SWServerRegistration::getNewestWorker()
134 {
135     ASSERT(!isMainThread());
136     if (m_installingWorker)
137         return m_installingWorker.get();
138     if (m_waitingWorker)
139         return m_waitingWorker.get();
140
141     return m_activeWorker.get();
142 }
143
144 void SWServerRegistration::runRegisterJob(const ServiceWorkerJobData& job)
145 {
146     ASSERT(!isMainThread());
147     ASSERT(job.type == ServiceWorkerJobType::Register);
148
149     if (!shouldTreatAsPotentiallyTrustworthy(job.scriptURL))
150         return rejectWithExceptionOnMainThread(ExceptionData { SecurityError, ASCIILiteral("Script URL is not potentially trustworthy") });
151
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") });
155
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") });
159
160     // If registration is not null (in our parlance "empty"), then:
161     if (!isEmpty()) {
162         ASSERT(m_updateViaCache);
163
164         m_uninstalling = false;
165         auto* newestWorker = getNewestWorker();
166         if (newestWorker && equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()) && job.registrationOptions.updateViaCache == *m_updateViaCache) {
167             resolveWithRegistrationOnMainThread();
168             return;
169         }
170     } else {
171         m_scopeURL = job.scopeURL.isolatedCopy();
172         m_scopeURL.removeFragmentIdentifier();
173         m_updateViaCache = job.registrationOptions.updateViaCache;
174     }
175
176     runUpdateJob(job);
177 }
178
179 void SWServerRegistration::runUnregisterJob(const ServiceWorkerJobData& job)
180 {
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") });
184
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);
190         return;
191     }
192
193     // Set registration’s uninstalling flag.
194     m_uninstalling = true;
195
196     // Invoke Resolve Job Promise with job and true.
197     resolveWithUnregistrationResultOnMainThread(true);
198
199     // FIXME: Invoke Try Clear Registration with registration.
200 }
201
202 void SWServerRegistration::runUpdateJob(const ServiceWorkerJobData& job)
203 {
204     // If registration is null (in our parlance "empty") or registration’s uninstalling flag is set, then:
205     if (isEmpty())
206         return rejectWithExceptionOnMainThread(ExceptionData { TypeError, ASCIILiteral("Cannot update a null/nonexistent service worker registration") });
207     if (m_uninstalling)
208         return rejectWithExceptionOnMainThread(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker registration that is uninstalling") });
209
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") });
214
215     startScriptFetchFromMainThread();
216 }
217
218 void SWServerRegistration::rejectWithExceptionOnMainThread(const ExceptionData& exception)
219 {
220     ASSERT(!isMainThread());
221     m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::rejectCurrentJob, exception));
222 }
223
224 void SWServerRegistration::resolveWithRegistrationOnMainThread()
225 {
226     ASSERT(!isMainThread());
227     m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::resolveCurrentRegistrationJob, data()));
228 }
229
230 void SWServerRegistration::resolveWithUnregistrationResultOnMainThread(bool unregistrationResult)
231 {
232     ASSERT(!isMainThread());
233     m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::resolveCurrentUnregistrationJob, unregistrationResult));
234 }
235
236 void SWServerRegistration::startScriptFetchFromMainThread()
237 {
238     ASSERT(!isMainThread());
239     m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::startScriptFetchForCurrentJob));
240 }
241
242 void SWServerRegistration::rejectCurrentJob(const ExceptionData& exceptionData)
243 {
244     ASSERT(isMainThread());
245     ASSERT(m_currentJob);
246
247     m_server.rejectJob(*m_currentJob, exceptionData);
248
249     finishCurrentJob();
250 }
251
252 void SWServerRegistration::resolveCurrentRegistrationJob(const ServiceWorkerRegistrationData& data)
253 {
254     ASSERT(isMainThread());
255     ASSERT(m_currentJob);
256     ASSERT(m_currentJob->type == ServiceWorkerJobType::Register);
257
258     m_server.resolveRegistrationJob(*m_currentJob, data);
259
260     finishCurrentJob();
261 }
262
263 void SWServerRegistration::resolveCurrentUnregistrationJob(bool unregistrationResult)
264 {
265     ASSERT(isMainThread());
266     ASSERT(m_currentJob);
267     ASSERT(m_currentJob->type == ServiceWorkerJobType::Unregister);
268
269     m_server.resolveUnregistrationJob(*m_currentJob, m_registrationKey, unregistrationResult);
270
271     finishCurrentJob();
272 }
273
274 void SWServerRegistration::startScriptFetchForCurrentJob()
275 {
276     ASSERT(isMainThread());
277     ASSERT(m_currentJob);
278
279     m_server.startScriptFetch(*m_currentJob);
280 }
281
282 void SWServerRegistration::finishCurrentJob()
283 {
284     ASSERT(m_currentJob);
285     ASSERT(!m_jobTimer.isActive());
286
287     m_currentJob = nullptr;
288     if (m_jobQueue.isEmpty())
289         return;
290
291     startNextJob();
292 }
293
294 ServiceWorkerRegistrationData SWServerRegistration::data() const
295 {
296     return { m_registrationKey, identifier(), m_scopeURL, m_updateViaCache.value_or(ServiceWorkerUpdateViaCache::Imports) };
297 }
298
299
300 } // namespace WebCore
301
302 #endif // ENABLE(SERVICE_WORKER)