Add release logging to help debug issues related to service workers
[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     RELEASE_LOG(ServiceWorker, "%p - SWServerJobQueue::didResolveRegistrationPromise: Registration ID: %llu. Now proceeding with install", this, registration->identifier().toUInt64());
163
164     // Queue a task to fire an event named updatefound at all the ServiceWorkerRegistration objects
165     // for all the service worker clients whose creation URL matches registration's scope url and
166     // all the service workers whose containing service worker registration is registration.
167     registration->fireUpdateFoundEvent();
168
169     // Queue a task to fire the InstallEvent.
170     ASSERT(registration->installingWorker());
171     m_server.fireInstallEvent(*registration->installingWorker());
172 }
173
174 // https://w3c.github.io/ServiceWorker/#install
175 void SWServerJobQueue::didFinishInstall(const ServiceWorkerJobDataIdentifier& jobDataIdentifier, ServiceWorkerIdentifier identifier, bool wasSuccessful)
176 {
177     if (!isCurrentlyProcessingJob(jobDataIdentifier))
178         return;
179
180     auto* registration = m_server.getRegistration(m_registrationKey);
181     ASSERT(registration);
182     ASSERT(registration->installingWorker());
183     ASSERT(registration->installingWorker()->identifier() == identifier);
184
185     if (!wasSuccessful) {
186         auto* worker = m_server.workerByID(identifier);
187         RELEASE_ASSERT(worker);
188
189         // Run the Update Worker State algorithm passing registration's installing worker and redundant as the arguments.
190         registration->updateWorkerState(*worker, ServiceWorkerState::Redundant);
191         // Run the Update Registration State algorithm passing registration, "installing" and null as the arguments.
192         registration->updateRegistrationState(ServiceWorkerRegistrationState::Installing, nullptr);
193
194         // If newestWorker is null, invoke Clear Registration algorithm passing registration as its argument.
195         if (!registration->getNewestWorker())
196             registration->clear();
197
198         // Invoke Finish Job with job and abort these steps.
199         finishCurrentJob();
200         return;
201     }
202
203     if (auto* waitingWorker = registration->waitingWorker()) {
204         waitingWorker->terminate();
205         registration->updateWorkerState(*waitingWorker, ServiceWorkerState::Redundant);
206     }
207
208     auto* installing = registration->installingWorker();
209     ASSERT(installing);
210
211     registration->updateRegistrationState(ServiceWorkerRegistrationState::Waiting, installing);
212     registration->updateRegistrationState(ServiceWorkerRegistrationState::Installing, nullptr);
213     registration->updateWorkerState(*installing, ServiceWorkerState::Installed);
214
215     finishCurrentJob();
216
217     // FIXME: Wait for all the tasks queued by Update Worker State invoked in this algorithm have executed.
218     registration->tryActivate();
219 }
220
221 // https://w3c.github.io/ServiceWorker/#run-job
222 void SWServerJobQueue::runNextJob()
223 {
224     ASSERT(!m_jobQueue.isEmpty());
225     ASSERT(!m_jobTimer.isActive());
226     m_jobTimer.startOneShot(0_s);
227 }
228
229 void SWServerJobQueue::runNextJobSynchronously()
230 {
231     auto& job = firstJob();
232     switch (job.type) {
233     case ServiceWorkerJobType::Register:
234         runRegisterJob(job);
235         return;
236     case ServiceWorkerJobType::Unregister:
237         runUnregisterJob(job);
238         return;
239     case ServiceWorkerJobType::Update:
240         runUpdateJob(job);
241         return;
242     }
243
244     RELEASE_ASSERT_NOT_REACHED();
245 }
246
247 // https://w3c.github.io/ServiceWorker/#register-algorithm
248 void SWServerJobQueue::runRegisterJob(const ServiceWorkerJobData& job)
249 {
250     ASSERT(job.type == ServiceWorkerJobType::Register);
251
252     if (!shouldTreatAsPotentiallyTrustworthy(job.scriptURL) && !SchemeRegistry::isServiceWorkerContainerCustomScheme(job.scriptURL.protocol().toStringWithoutCopying()))
253         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Script URL is not potentially trustworthy") });
254
255     // If the origin of job's script url is not job's referrer's origin, then:
256     if (!protocolHostAndPortAreEqual(job.scriptURL, job.clientCreationURL))
257         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Script origin does not match the registering client's origin") });
258
259     // If the origin of job's scope url is not job's referrer's origin, then:
260     if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
261         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Scope origin does not match the registering client's origin") });
262
263     // If registration is not null (in our parlance "empty"), then:
264     if (auto* registration = m_server.getRegistration(m_registrationKey)) {
265         registration->setIsUninstalling(false);
266         auto* newestWorker = registration->getNewestWorker();
267         if (newestWorker && equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()) && job.registrationOptions.updateViaCache == registration->updateViaCache()) {
268             RELEASE_LOG(ServiceWorker, "%p - SWServerJobQueue::runRegisterJob: Found directly reusable registration %llu for job %s (DONE)", this, registration->identifier().toUInt64(), job.identifier().loggingString().utf8().data());
269             m_server.resolveRegistrationJob(job, registration->data(), ShouldNotifyWhenResolved::No);
270             finishCurrentJob();
271             return;
272         }
273         // This is not specified yet (https://github.com/w3c/ServiceWorker/issues/1189).
274         if (registration->updateViaCache() != job.registrationOptions.updateViaCache)
275             registration->setUpdateViaCache(job.registrationOptions.updateViaCache);
276         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());
277     } else {
278         auto newRegistration = std::make_unique<SWServerRegistration>(m_server, m_registrationKey, job.registrationOptions.updateViaCache, job.scopeURL, job.scriptURL);
279         m_server.addRegistration(WTFMove(newRegistration));
280
281         RELEASE_LOG(ServiceWorker, "%p - SWServerJobQueue::runRegisterJob: No existing registration for job %s, constructing a new one.", this, job.identifier().loggingString().utf8().data());
282     }
283
284     runUpdateJob(job);
285 }
286
287 // https://w3c.github.io/ServiceWorker/#unregister-algorithm
288 void SWServerJobQueue::runUnregisterJob(const ServiceWorkerJobData& job)
289 {
290     // If the origin of job's scope url is not job's client's origin, then:
291     if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
292         return rejectCurrentJob(ExceptionData { SecurityError, ASCIILiteral("Origin of scope URL does not match the client's origin") });
293
294     // Let registration be the result of running "Get Registration" algorithm passing job's scope url as the argument.
295     auto* registration = m_server.getRegistration(m_registrationKey);
296
297     // If registration is null, then:
298     if (!registration || registration->isUninstalling()) {
299         // Invoke Resolve Job Promise with job and false.
300         m_server.resolveUnregistrationJob(job, m_registrationKey, false);
301         finishCurrentJob();
302         return;
303     }
304
305     // Set registration's uninstalling flag.
306     registration->setIsUninstalling(true);
307
308     // Invoke Resolve Job Promise with job and true.
309     m_server.resolveUnregistrationJob(job, m_registrationKey, true);
310
311     // Invoke Try Clear Registration with registration.
312     registration->tryClear();
313     finishCurrentJob();
314 }
315
316 // https://w3c.github.io/ServiceWorker/#update-algorithm
317 void SWServerJobQueue::runUpdateJob(const ServiceWorkerJobData& job)
318 {
319     // Let registration be the result of running the Get Registration algorithm passing job's scope url as the argument.
320     auto* registration = m_server.getRegistration(m_registrationKey);
321
322     // If registration is null (in our parlance "empty") or registration's uninstalling flag is set, then:
323     if (!registration)
324         return rejectCurrentJob(ExceptionData { TypeError, ASCIILiteral("Cannot update a null/nonexistent service worker registration") });
325     if (registration->isUninstalling())
326         return rejectCurrentJob(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker registration that is uninstalling") });
327
328     // Let newestWorker be the result of running Get Newest Worker algorithm passing registration as the argument.
329     auto* newestWorker = registration->getNewestWorker();
330
331     // 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:
332     if (job.type == ServiceWorkerJobType::Update && newestWorker && !equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()))
333         return rejectCurrentJob(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker with a requested script URL whose newest worker has a different script URL") });
334
335     FetchOptions::Cache cachePolicy = FetchOptions::Cache::Default;
336     // Set request's cache mode to "no-cache" if any of the following are true:
337     // - registration's update via cache mode is not "all".
338     // - job's force bypass cache flag is set.
339     // - newestWorker is not null, and registration's last update check time is not null and the time difference in seconds calculated by the
340     //   current time minus registration's last update check time is greater than 86400.
341     if (registration->updateViaCache() != ServiceWorkerUpdateViaCache::All
342         || (newestWorker && registration->lastUpdateTime() && (WallTime::now() - registration->lastUpdateTime()) > 86400_s)) {
343         cachePolicy = FetchOptions::Cache::NoCache;
344     }
345     m_server.startScriptFetch(job, cachePolicy);
346 }
347
348 void SWServerJobQueue::rejectCurrentJob(const ExceptionData& exceptionData)
349 {
350     m_server.rejectJob(firstJob(), exceptionData);
351
352     finishCurrentJob();
353 }
354
355 // https://w3c.github.io/ServiceWorker/#finish-job
356 void SWServerJobQueue::finishCurrentJob()
357 {
358     ASSERT(!m_jobTimer.isActive());
359
360     m_jobQueue.removeFirst();
361     if (!m_jobQueue.isEmpty())
362         runNextJob();
363 }
364
365 } // namespace WebCore
366
367 #endif // ENABLE(SERVICE_WORKER)