Implement "UpdateWorkerState" and use it
[WebKit-https.git] / Source / WebCore / workers / service / server / SWServerJobQueue.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 "SWServerJobQueue.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 SWServerJobQueue::SWServerJobQueue(SWServer& server, const ServiceWorkerRegistrationKey& key)
43     : m_jobTimer(*this, &SWServerJobQueue::runNextJobSynchronously)
44     , m_server(server)
45     , m_registrationKey(key)
46 {
47 }
48
49 SWServerJobQueue::~SWServerJobQueue()
50 {
51 }
52
53 void SWServerJobQueue::scriptFetchFinished(SWServer::Connection& connection, const ServiceWorkerFetchResult& result)
54 {
55     auto& job = firstJob();
56     ASSERT(job.identifier() == result.jobIdentifier);
57
58     auto* registration = m_server.getRegistration(m_registrationKey);
59     ASSERT(registration);
60
61     auto* newestWorker = registration->getNewestWorker();
62
63     if (!result.scriptError.isNull()) {
64         // Invoke Reject Job Promise with job and TypeError.
65         m_server.rejectJob(firstJob(), ExceptionData { TypeError, makeString("Script URL ", job.scriptURL.string(), " fetch resulted in error: ", result.scriptError.localizedDescription()) });
66
67         // If newestWorker is null, invoke Clear Registration algorithm passing registration as its argument.
68         if (!newestWorker)
69             clearRegistration(*registration);
70
71         // Invoke Finish Job with job and abort these steps.
72         finishCurrentJob();
73         return;
74     }
75
76     // FIXME: If newestWorker is not null, newestWorker's script url equals job's script url with the exclude fragments
77     // flag set, and script's source text is a byte-for-byte match with newestWorker's script resource's source
78     // text, then:
79     // - Invoke Resolve Job Promise with job and registration.
80     // - Invoke Finish Job with job and abort these steps.
81
82     // FIXME: Support the proper worker type (classic vs module)
83     m_server.updateWorker(connection, m_registrationKey, job.scriptURL, result.script, WorkerType::Classic);
84 }
85
86 void SWServerJobQueue::scriptContextFailedToStart(SWServer::Connection&, ServiceWorkerIdentifier identifier, const String& message)
87 {
88     auto* registration = m_server.getRegistration(m_registrationKey);
89     ASSERT(registration);
90
91     // FIXME: Install has failed. Run the install failed substeps
92     // Run the Update Worker State algorithm passing registration's installing worker and redundant as the arguments.
93     // Run the Update Registration State algorithm passing registration, "installing" and null as the arguments.
94
95     // If newestWorker is null, invoke Clear Registration algorithm passing registration as its argument.
96     if (!registration->getNewestWorker())
97         clearRegistration(*registration);
98
99     UNUSED_PARAM(identifier);
100     UNUSED_PARAM(message);
101 }
102
103 void SWServerJobQueue::scriptContextStarted(SWServer::Connection& connection, ServiceWorkerIdentifier identifier)
104 {
105     auto* registration = m_server.getRegistration(m_registrationKey);
106     ASSERT(registration);
107
108     install(*registration, connection, identifier);
109 }
110
111 // https://w3c.github.io/ServiceWorker/#install
112 void SWServerJobQueue::install(SWServerRegistration& registration, SWServer::Connection& connection, ServiceWorkerIdentifier installingWorker)
113 {
114     // The Install algorithm should never be invoked with a null worker.
115     auto* worker = m_server.workerByID(installingWorker);
116     RELEASE_ASSERT(worker);
117
118     registration.updateRegistrationState(ServiceWorkerRegistrationState::Installing, worker);
119     registration.updateWorkerState(*worker, ServiceWorkerState::Installing);
120
121     // Invoke Resolve Job Promise with job and registration.
122     m_server.resolveRegistrationJob(firstJob(), registration.data());
123
124     // Queue a task to fire an event named updatefound at all the ServiceWorkerRegistration objects
125     // for all the service worker clients whose creation URL matches registration's scope url and
126     // all the service workers whose containing service worker registration is registration.
127     registration.fireUpdateFoundEvent(firstJob().connectionIdentifier());
128
129     // Queue a task to fire the InstallEvent.
130     m_server.fireInstallEvent(connection, installingWorker);
131 }
132
133 // https://w3c.github.io/ServiceWorker/#install
134 void SWServerJobQueue::didFinishInstall(SWServer::Connection&, ServiceWorkerIdentifier, bool wasSuccessful)
135 {
136     auto* registration = m_server.getRegistration(m_registrationKey);
137     ASSERT(registration);
138
139     if (!wasSuccessful) {
140         // FIXME: Run the Update Worker State algorithm passing registration's installing worker and redundant as the arguments.
141         // FIXME: Run the Update Registration State algorithm passing registration, "installing" and null as the arguments.
142
143         // If newestWorker is null, invoke Clear Registration algorithm passing registration as its argument.
144         if (!registration->getNewestWorker())
145             clearRegistration(*registration);
146         // Invoke Finish Job with job and abort these steps.
147         finishCurrentJob();
148         return;
149     }
150
151     // FIXME: Implement real post 'install' event steps of the Install algorithm (steps 14+).
152     registration->firePostInstallEvents(firstJob().connectionIdentifier());
153
154     finishCurrentJob();
155 }
156
157 // https://w3c.github.io/ServiceWorker/#run-job
158 void SWServerJobQueue::runNextJob()
159 {
160     ASSERT(!m_jobQueue.isEmpty());
161     ASSERT(!m_jobTimer.isActive());
162     m_jobTimer.startOneShot(0_s);
163 }
164
165 void SWServerJobQueue::runNextJobSynchronously()
166 {
167     auto& job = firstJob();
168     switch (job.type) {
169     case ServiceWorkerJobType::Register:
170         runRegisterJob(job);
171         return;
172     case ServiceWorkerJobType::Unregister:
173         runUnregisterJob(job);
174         return;
175     case ServiceWorkerJobType::Update:
176         runUpdateJob(job);
177         return;
178     }
179
180     RELEASE_ASSERT_NOT_REACHED();
181 }
182
183 // https://w3c.github.io/ServiceWorker/#register-algorithm
184 void SWServerJobQueue::runRegisterJob(const ServiceWorkerJobData& job)
185 {
186     ASSERT(job.type == ServiceWorkerJobType::Register);
187
188     if (!shouldTreatAsPotentiallyTrustworthy(job.scriptURL))
189         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Script URL is not potentially trustworthy") });
190
191     // If the origin of job's script url is not job's referrer's origin, then:
192     if (!protocolHostAndPortAreEqual(job.scriptURL, job.clientCreationURL))
193         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Script origin does not match the registering client's origin") });
194
195     // If the origin of job's scope url is not job's referrer's origin, then:
196     if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
197         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Scope origin does not match the registering client's origin") });
198
199     // If registration is not null (in our parlance "empty"), then:
200     if (auto* registration = m_server.getRegistration(m_registrationKey)) {
201         registration->setIsUninstalling(false);
202         auto* newestWorker = registration->getNewestWorker();
203         if (newestWorker && equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()) && job.registrationOptions.updateViaCache == registration->updateViaCache()) {
204             m_server.resolveRegistrationJob(firstJob(), registration->data());
205             finishCurrentJob();
206             return;
207         }
208     } else {
209         auto newRegistration = std::make_unique<SWServerRegistration>(m_server, m_registrationKey, job.registrationOptions.updateViaCache, job.scopeURL, job.scriptURL);
210         m_server.addRegistration(WTFMove(newRegistration));
211     }
212
213     runUpdateJob(job);
214 }
215
216 // https://w3c.github.io/ServiceWorker/#unregister-algorithm
217 void SWServerJobQueue::runUnregisterJob(const ServiceWorkerJobData& job)
218 {
219     // If the origin of job's scope url is not job's client's origin, then:
220     if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
221         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Origin of scope URL does not match the client's origin") });
222
223     // Let registration be the result of running "Get Registration" algorithm passing job's scope url as the argument.
224     auto* registration = m_server.getRegistration(m_registrationKey);
225
226     // If registration is null, then:
227     if (!registration || registration->isUninstalling()) {
228         // Invoke Resolve Job Promise with job and false.
229         m_server.resolveUnregistrationJob(firstJob(), m_registrationKey, false);
230         finishCurrentJob();
231         return;
232     }
233
234     // Set registration's uninstalling flag.
235     registration->setIsUninstalling(true);
236
237     // Invoke Resolve Job Promise with job and true.
238     m_server.resolveUnregistrationJob(firstJob(), m_registrationKey, true);
239
240     // Invoke Try Clear Registration with registration.
241     tryClearRegistration(*registration);
242     finishCurrentJob();
243 }
244
245 // https://w3c.github.io/ServiceWorker/#try-clear-registration-algorithm
246 void SWServerJobQueue::tryClearRegistration(SWServerRegistration& registration)
247 {
248     // FIXME: Make sure that the registration has no service worker client.
249
250     // FIXME: The specification has more complex logic here. We currently clear registrations
251     // too aggressively.
252     clearRegistration(registration);
253 }
254
255 // https://w3c.github.io/ServiceWorker/#clear-registration
256 void SWServerJobQueue::clearRegistration(SWServerRegistration& registration)
257 {
258     // FIXME: Update / terminate the registration's service workers.
259     m_server.removeRegistration(registration.key());
260 }
261
262 // https://w3c.github.io/ServiceWorker/#update-algorithm
263 void SWServerJobQueue::runUpdateJob(const ServiceWorkerJobData& job)
264 {
265     // Let registration be the result of running the Get Registration algorithm passing job's scope url as the argument.
266     auto* registration = m_server.getRegistration(m_registrationKey);
267
268     // If registration is null (in our parlance "empty") or registration's uninstalling flag is set, then:
269     if (!registration)
270         return rejectCurrentJob(ExceptionData { TypeError, ASCIILiteral("Cannot update a null/nonexistent service worker registration") });
271     if (registration->isUninstalling())
272         return rejectCurrentJob(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker registration that is uninstalling") });
273
274     // Let newestWorker be the result of running Get Newest Worker algorithm passing registration as the argument.
275     auto* newestWorker = registration->getNewestWorker();
276
277     // If job's type is update, and newestWorker's script url does not equal job's script url with the exclude fragments flag set, then:
278     if (job.type == ServiceWorkerJobType::Update && newestWorker && !equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()))
279         return rejectCurrentJob(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker with a requested script URL whose newest worker has a different script URL") });
280
281     m_server.startScriptFetch(job);
282 }
283
284 void SWServerJobQueue::rejectCurrentJob(const ExceptionData& exceptionData)
285 {
286     m_server.rejectJob(firstJob(), exceptionData);
287
288     finishCurrentJob();
289 }
290
291 // https://w3c.github.io/ServiceWorker/#finish-job
292 void SWServerJobQueue::finishCurrentJob()
293 {
294     ASSERT(!m_jobTimer.isActive());
295
296     m_jobQueue.removeFirst();
297     if (!m_jobQueue.isEmpty())
298         runNextJob();
299 }
300
301 } // namespace WebCore
302
303 #endif // ENABLE(SERVICE_WORKER)