db3b81ffad118aad40229d369a9bccf2a0811319
[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 "SchemeRegistry.h"
35 #include "SecurityOrigin.h"
36 #include "ServiceWorkerFetchResult.h"
37 #include "ServiceWorkerRegistrationData.h"
38 #include "ServiceWorkerUpdateViaCache.h"
39 #include "WorkerType.h"
40
41 namespace WebCore {
42
43 SWServerJobQueue::SWServerJobQueue(SWServer& server, const ServiceWorkerRegistrationKey& key)
44     : m_jobTimer(*this, &SWServerJobQueue::runNextJobSynchronously)
45     , m_server(server)
46     , m_registrationKey(key)
47 {
48 }
49
50 SWServerJobQueue::~SWServerJobQueue()
51 {
52 }
53
54 bool SWServerJobQueue::isCurrentlyProcessingJob(const ServiceWorkerJobDataIdentifier& jobDataIdentifier) const
55 {
56     return !m_jobQueue.isEmpty() && firstJob().identifier() == jobDataIdentifier;
57 }
58
59 void SWServerJobQueue::scriptFetchFinished(SWServer::Connection& connection, const ServiceWorkerFetchResult& result)
60 {
61     if (!isCurrentlyProcessingJob(result.jobDataIdentifier))
62         return;
63
64     auto& job = firstJob();
65
66     auto* registration = m_server.getRegistration(m_registrationKey);
67     ASSERT(registration);
68
69     auto* newestWorker = registration->getNewestWorker();
70
71     if (!result.scriptError.isNull()) {
72         // Invoke Reject Job Promise with job and TypeError.
73         m_server.rejectJob(job, ExceptionData { TypeError, makeString("Script URL ", job.scriptURL.string(), " fetch resulted in error: ", result.scriptError.localizedDescription()) });
74
75         // If newestWorker is null, invoke Clear Registration algorithm passing registration as its argument.
76         if (!newestWorker)
77             registration->clear();
78
79         // Invoke Finish Job with job and abort these steps.
80         finishCurrentJob();
81         return;
82     }
83
84     // If newestWorker is not null, newestWorker's script url equals job's script url with the exclude fragments
85     // flag set, and script's source text is a byte-for-byte match with newestWorker's script resource's source
86     // text, then:
87     if (newestWorker && equalIgnoringFragmentIdentifier(newestWorker->scriptURL(), job.scriptURL) && result.script == newestWorker->script()) {
88         // FIXME: for non classic scripts, check the script’s module record's [[ECMAScriptCode]].
89
90         // Invoke Resolve Job Promise with job and registration.
91         m_server.resolveRegistrationJob(job, registration->data(), ShouldNotifyWhenResolved::No);
92
93         // Invoke Finish Job with job and abort these steps.
94         finishCurrentJob();
95         return;
96     }
97
98     // FIXME: Support the proper worker type (classic vs module)
99     m_server.updateWorker(connection, job.identifier(), *registration, job.scriptURL, result.script, result.contentSecurityPolicy, WorkerType::Classic);
100 }
101
102 // https://w3c.github.io/ServiceWorker/#update-algorithm
103 void SWServerJobQueue::scriptContextFailedToStart(const ServiceWorkerJobDataIdentifier& jobDataIdentifier, ServiceWorkerIdentifier, const String& message)
104 {
105     if (!isCurrentlyProcessingJob(jobDataIdentifier))
106         return;
107
108     // If an uncaught runtime script error occurs during the above step, then:
109     auto* registration = m_server.getRegistration(m_registrationKey);
110     ASSERT(registration);
111
112     ASSERT(registration->preInstallationWorker());
113     registration->preInstallationWorker()->terminate();
114     registration->setPreInstallationWorker(nullptr);
115
116     // Invoke Reject Job Promise with job and TypeError.
117     m_server.rejectJob(firstJob(), { TypeError, message });
118
119     // If newestWorker is null, invoke Clear Registration algorithm passing registration as its argument.
120     if (!registration->getNewestWorker())
121         registration->clear();
122
123     // Invoke Finish Job with job and abort these steps.
124     finishCurrentJob();
125 }
126
127 void SWServerJobQueue::scriptContextStarted(const ServiceWorkerJobDataIdentifier& jobDataIdentifier, ServiceWorkerIdentifier identifier)
128 {
129     if (!isCurrentlyProcessingJob(jobDataIdentifier))
130         return;
131
132     auto* registration = m_server.getRegistration(m_registrationKey);
133     ASSERT(registration);
134
135     install(*registration, identifier);
136 }
137
138 // https://w3c.github.io/ServiceWorker/#install
139 void SWServerJobQueue::install(SWServerRegistration& registration, ServiceWorkerIdentifier installingWorker)
140 {
141     // The Install algorithm should never be invoked with a null worker.
142     auto* worker = m_server.workerByID(installingWorker);
143     RELEASE_ASSERT(worker);
144
145     ASSERT(registration.preInstallationWorker() == worker);
146     registration.setPreInstallationWorker(nullptr);
147
148     registration.updateRegistrationState(ServiceWorkerRegistrationState::Installing, worker);
149     registration.updateWorkerState(*worker, ServiceWorkerState::Installing);
150
151     // Invoke Resolve Job Promise with job and registration.
152     m_server.resolveRegistrationJob(firstJob(), registration.data(), ShouldNotifyWhenResolved::Yes);
153 }
154
155 // https://w3c.github.io/ServiceWorker/#install (after resolving promise).
156 void SWServerJobQueue::didResolveRegistrationPromise()
157 {
158     auto* registration = m_server.getRegistration(m_registrationKey);
159     ASSERT(registration);
160     ASSERT(registration->installingWorker());
161
162     // Queue a task to fire an event named updatefound at all the ServiceWorkerRegistration objects
163     // for all the service worker clients whose creation URL matches registration's scope url and
164     // all the service workers whose containing service worker registration is registration.
165     registration->fireUpdateFoundEvent();
166
167     // Queue a task to fire the InstallEvent.
168     ASSERT(registration->installingWorker());
169     m_server.fireInstallEvent(*registration->installingWorker());
170 }
171
172 // https://w3c.github.io/ServiceWorker/#install
173 void SWServerJobQueue::didFinishInstall(const ServiceWorkerJobDataIdentifier& jobDataIdentifier, ServiceWorkerIdentifier identifier, bool wasSuccessful)
174 {
175     if (!isCurrentlyProcessingJob(jobDataIdentifier))
176         return;
177
178     auto* registration = m_server.getRegistration(m_registrationKey);
179     ASSERT(registration);
180     ASSERT(registration->installingWorker());
181     ASSERT(registration->installingWorker()->identifier() == identifier);
182
183     if (!wasSuccessful) {
184         auto* worker = m_server.workerByID(identifier);
185         RELEASE_ASSERT(worker);
186
187         // Run the Update Worker State algorithm passing registration's installing worker and redundant as the arguments.
188         registration->updateWorkerState(*worker, ServiceWorkerState::Redundant);
189         // Run the Update Registration State algorithm passing registration, "installing" and null as the arguments.
190         registration->updateRegistrationState(ServiceWorkerRegistrationState::Installing, nullptr);
191
192         // If newestWorker is null, invoke Clear Registration algorithm passing registration as its argument.
193         if (!registration->getNewestWorker())
194             registration->clear();
195
196         // Invoke Finish Job with job and abort these steps.
197         finishCurrentJob();
198         return;
199     }
200
201     if (auto* waitingWorker = registration->waitingWorker()) {
202         waitingWorker->terminate();
203         registration->updateWorkerState(*waitingWorker, ServiceWorkerState::Redundant);
204     }
205
206     auto* installing = registration->installingWorker();
207     ASSERT(installing);
208
209     registration->updateRegistrationState(ServiceWorkerRegistrationState::Waiting, installing);
210     registration->updateRegistrationState(ServiceWorkerRegistrationState::Installing, nullptr);
211     registration->updateWorkerState(*installing, ServiceWorkerState::Installed);
212
213     finishCurrentJob();
214
215     // FIXME: Wait for all the tasks queued by Update Worker State invoked in this algorithm have executed.
216     registration->tryActivate();
217 }
218
219 // https://w3c.github.io/ServiceWorker/#run-job
220 void SWServerJobQueue::runNextJob()
221 {
222     ASSERT(!m_jobQueue.isEmpty());
223     ASSERT(!m_jobTimer.isActive());
224     m_jobTimer.startOneShot(0_s);
225 }
226
227 void SWServerJobQueue::runNextJobSynchronously()
228 {
229     auto& job = firstJob();
230     switch (job.type) {
231     case ServiceWorkerJobType::Register:
232         runRegisterJob(job);
233         return;
234     case ServiceWorkerJobType::Unregister:
235         runUnregisterJob(job);
236         return;
237     case ServiceWorkerJobType::Update:
238         runUpdateJob(job);
239         return;
240     }
241
242     RELEASE_ASSERT_NOT_REACHED();
243 }
244
245 // https://w3c.github.io/ServiceWorker/#register-algorithm
246 void SWServerJobQueue::runRegisterJob(const ServiceWorkerJobData& job)
247 {
248     ASSERT(job.type == ServiceWorkerJobType::Register);
249
250     if (!shouldTreatAsPotentiallyTrustworthy(job.scriptURL) && !SchemeRegistry::isServiceWorkerContainerCustomScheme(job.scriptURL.protocol().toStringWithoutCopying()))
251         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Script URL is not potentially trustworthy") });
252
253     // If the origin of job's script url is not job's referrer's origin, then:
254     if (!protocolHostAndPortAreEqual(job.scriptURL, job.clientCreationURL))
255         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Script origin does not match the registering client's origin") });
256
257     // If the origin of job's scope url is not job's referrer's origin, then:
258     if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
259         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Scope origin does not match the registering client's origin") });
260
261     // If registration is not null (in our parlance "empty"), then:
262     if (auto* registration = m_server.getRegistration(m_registrationKey)) {
263         registration->setIsUninstalling(false);
264         auto* newestWorker = registration->getNewestWorker();
265         if (newestWorker && equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()) && job.registrationOptions.updateViaCache == registration->updateViaCache()) {
266             m_server.resolveRegistrationJob(job, registration->data(), ShouldNotifyWhenResolved::No);
267             finishCurrentJob();
268             return;
269         }
270         // This is not specified yet (https://github.com/w3c/ServiceWorker/issues/1189).
271         if (registration->updateViaCache() != job.registrationOptions.updateViaCache)
272             registration->setUpdateViaCache(job.registrationOptions.updateViaCache);
273     } else {
274         auto newRegistration = std::make_unique<SWServerRegistration>(m_server, m_registrationKey, job.registrationOptions.updateViaCache, job.scopeURL, job.scriptURL);
275         m_server.addRegistration(WTFMove(newRegistration));
276     }
277
278     runUpdateJob(job);
279 }
280
281 // https://w3c.github.io/ServiceWorker/#unregister-algorithm
282 void SWServerJobQueue::runUnregisterJob(const ServiceWorkerJobData& job)
283 {
284     // If the origin of job's scope url is not job's client's origin, then:
285     if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
286         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Origin of scope URL does not match the client's origin") });
287
288     // Let registration be the result of running "Get Registration" algorithm passing job's scope url as the argument.
289     auto* registration = m_server.getRegistration(m_registrationKey);
290
291     // If registration is null, then:
292     if (!registration || registration->isUninstalling()) {
293         // Invoke Resolve Job Promise with job and false.
294         m_server.resolveUnregistrationJob(job, m_registrationKey, false);
295         finishCurrentJob();
296         return;
297     }
298
299     // Set registration's uninstalling flag.
300     registration->setIsUninstalling(true);
301
302     // Invoke Resolve Job Promise with job and true.
303     m_server.resolveUnregistrationJob(job, m_registrationKey, true);
304
305     // Invoke Try Clear Registration with registration.
306     registration->tryClear();
307     finishCurrentJob();
308 }
309
310 // https://w3c.github.io/ServiceWorker/#update-algorithm
311 void SWServerJobQueue::runUpdateJob(const ServiceWorkerJobData& job)
312 {
313     // Let registration be the result of running the Get Registration algorithm passing job's scope url as the argument.
314     auto* registration = m_server.getRegistration(m_registrationKey);
315
316     // If registration is null (in our parlance "empty") or registration's uninstalling flag is set, then:
317     if (!registration)
318         return rejectCurrentJob(ExceptionData { TypeError, ASCIILiteral("Cannot update a null/nonexistent service worker registration") });
319     if (registration->isUninstalling())
320         return rejectCurrentJob(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker registration that is uninstalling") });
321
322     // Let newestWorker be the result of running Get Newest Worker algorithm passing registration as the argument.
323     auto* newestWorker = registration->getNewestWorker();
324
325     // 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:
326     if (job.type == ServiceWorkerJobType::Update && newestWorker && !equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()))
327         return rejectCurrentJob(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker with a requested script URL whose newest worker has a different script URL") });
328
329     FetchOptions::Cache cachePolicy = FetchOptions::Cache::Default;
330     // Set request's cache mode to "no-cache" if any of the following are true:
331     // - registration's update via cache mode is not "all".
332     // - job's force bypass cache flag is set.
333     // - newestWorker is not null, and registration's last update check time is not null and the time difference in seconds calculated by the
334     //   current time minus registration's last update check time is greater than 86400.
335     if (registration->updateViaCache() != ServiceWorkerUpdateViaCache::All
336         || (newestWorker && registration->lastUpdateTime() && (WallTime::now() - registration->lastUpdateTime()) > 86400_s)) {
337         cachePolicy = FetchOptions::Cache::NoCache;
338     }
339     m_server.startScriptFetch(job, cachePolicy);
340 }
341
342 void SWServerJobQueue::rejectCurrentJob(const ExceptionData& exceptionData)
343 {
344     m_server.rejectJob(firstJob(), exceptionData);
345
346     finishCurrentJob();
347 }
348
349 // https://w3c.github.io/ServiceWorker/#finish-job
350 void SWServerJobQueue::finishCurrentJob()
351 {
352     ASSERT(!m_jobTimer.isActive());
353
354     m_jobQueue.removeFirst();
355     if (!m_jobQueue.isEmpty())
356         runNextJob();
357 }
358
359 } // namespace WebCore
360
361 #endif // ENABLE(SERVICE_WORKER)