[Service Workers] Add support for "install" event
[WebKit-https.git] / Source / WebCore / workers / service / server / SWServer.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 "SWServer.h"
28
29 #if ENABLE(SERVICE_WORKER)
30
31 #include "ExceptionCode.h"
32 #include "ExceptionData.h"
33 #include "Logging.h"
34 #include "SWServerJobQueue.h"
35 #include "SWServerRegistration.h"
36 #include "SWServerWorker.h"
37 #include "SecurityOrigin.h"
38 #include "ServiceWorkerContextData.h"
39 #include "ServiceWorkerFetchResult.h"
40 #include "ServiceWorkerJobData.h"
41 #include <wtf/text/WTFString.h>
42
43 namespace WebCore {
44
45 static ServiceWorkerIdentifier generateServiceWorkerIdentifier()
46 {
47     static uint64_t identifier = 0;
48     return makeObjectIdentifier<ServiceWorkerIdentifierType>(++identifier);
49 }
50
51 SWServer::Connection::Connection(SWServer& server, uint64_t identifier)
52     : Identified(identifier)
53     , m_server(server)
54 {
55     m_server.registerConnection(*this);
56 }
57
58 SWServer::Connection::~Connection()
59 {
60     m_server.unregisterConnection(*this);
61 }
62
63 SWServer::~SWServer()
64 {
65     RELEASE_ASSERT(m_connections.isEmpty());
66     RELEASE_ASSERT(m_registrations.isEmpty());
67     RELEASE_ASSERT(m_jobQueues.isEmpty());
68
69     ASSERT(m_taskQueue.isEmpty());
70     ASSERT(m_taskReplyQueue.isEmpty());
71
72     // For a SWServer to be cleanly shut down its thread must have finished and gone away.
73     // At this stage in development of the feature that actually never happens.
74     // But once it does start happening, this ASSERT will catch us doing it wrong.
75     Locker<Lock> locker(m_taskThreadLock);
76     ASSERT(!m_taskThread);
77 }
78
79 SWServerRegistration* SWServer::getRegistration(const ServiceWorkerRegistrationKey& registrationKey)
80 {
81     return m_registrations.get(registrationKey);
82 }
83
84 void SWServer::addRegistration(std::unique_ptr<SWServerRegistration>&& registration)
85 {
86     auto key = registration->key();
87     m_registrations.add(key, WTFMove(registration));
88 }
89
90 void SWServer::removeRegistration(const ServiceWorkerRegistrationKey& registrationKey)
91 {
92     m_registrations.remove(registrationKey);
93 }
94
95 void SWServer::clear()
96 {
97     m_jobQueues.clear();
98     m_registrations.clear();
99     // FIXME: We should probably ask service workers to terminate.
100 }
101
102 void SWServer::Connection::scheduleJobInServer(const ServiceWorkerJobData& jobData)
103 {
104     LOG(ServiceWorker, "Scheduling ServiceWorker job %" PRIu64 "-%" PRIu64 " in server", jobData.connectionIdentifier(), jobData.identifier());
105     ASSERT(identifier() == jobData.connectionIdentifier());
106
107     m_server.scheduleJob(jobData);
108 }
109
110 void SWServer::Connection::finishFetchingScriptInServer(const ServiceWorkerFetchResult& result)
111 {
112     m_server.scriptFetchFinished(*this, result);
113 }
114
115 void SWServer::Connection::didFinishInstall(const ServiceWorkerRegistrationKey& key, ServiceWorkerIdentifier serviceWorkerIdentifier, bool wasSuccessful)
116 {
117     m_server.didFinishInstall(*this, key, serviceWorkerIdentifier, wasSuccessful);
118 }
119
120 void SWServer::Connection::addServiceWorkerRegistrationInServer(const ServiceWorkerRegistrationKey& key, uint64_t clientRegistrationIdentifier)
121 {
122     m_server.addClientServiceWorkerRegistration(*this, key, clientRegistrationIdentifier);
123 }
124
125 void SWServer::Connection::removeServiceWorkerRegistrationInServer(const ServiceWorkerRegistrationKey& key, uint64_t clientRegistrationIdentifier)
126 {
127     m_server.removeClientServiceWorkerRegistration(*this, key, clientRegistrationIdentifier);
128 }
129
130 void SWServer::Connection::scriptContextFailedToStart(const ServiceWorkerRegistrationKey& registrationKey, ServiceWorkerIdentifier identifier, const String& message)
131 {
132     m_server.scriptContextFailedToStart(*this, registrationKey, identifier, message);
133 }
134
135 void SWServer::Connection::scriptContextStarted(const ServiceWorkerRegistrationKey& registrationKey, ServiceWorkerIdentifier identifier)
136 {
137     m_server.scriptContextStarted(*this, registrationKey, identifier);
138 }
139
140 SWServer::SWServer()
141 {
142     m_taskThread = Thread::create(ASCIILiteral("ServiceWorker Task Thread"), [this] {
143         taskThreadEntryPoint();
144     });
145 }
146
147 // https://w3c.github.io/ServiceWorker/#schedule-job-algorithm
148 void SWServer::scheduleJob(const ServiceWorkerJobData& jobData)
149 {
150     ASSERT(m_connections.contains(jobData.connectionIdentifier()));
151
152     // FIXME: Per the spec, check if this job is equivalent to the last job on the queue.
153     // If it is, stack it along with that job.
154
155     auto& jobQueue = *m_jobQueues.ensure(jobData.registrationKey(), [this, &jobData] {
156         return std::make_unique<SWServerJobQueue>(*this, jobData.registrationKey());
157     }).iterator->value;
158
159     jobQueue.enqueueJob(jobData);
160     if (jobQueue.size() == 1)
161         jobQueue.runNextJob();
162 }
163
164 void SWServer::rejectJob(const ServiceWorkerJobData& jobData, const ExceptionData& exceptionData)
165 {
166     LOG(ServiceWorker, "Rejected ServiceWorker job %" PRIu64 "-%" PRIu64 " in server", jobData.connectionIdentifier(), jobData.identifier());
167     auto* connection = m_connections.get(jobData.connectionIdentifier());
168     if (!connection)
169         return;
170
171     connection->rejectJobInClient(jobData.identifier(), exceptionData);
172 }
173
174 void SWServer::resolveRegistrationJob(const ServiceWorkerJobData& jobData, const ServiceWorkerRegistrationData& registrationData)
175 {
176     LOG(ServiceWorker, "Resolved ServiceWorker job %" PRIu64 "-%" PRIu64 " in server with registration %" PRIu64, jobData.connectionIdentifier(), jobData.identifier(), registrationData.identifier);
177     auto* connection = m_connections.get(jobData.connectionIdentifier());
178     if (!connection)
179         return;
180
181     connection->resolveRegistrationJobInClient(jobData.identifier(), registrationData);
182 }
183
184 void SWServer::resolveUnregistrationJob(const ServiceWorkerJobData& jobData, const ServiceWorkerRegistrationKey& registrationKey, bool unregistrationResult)
185 {
186     auto* connection = m_connections.get(jobData.connectionIdentifier());
187     if (!connection)
188         return;
189
190     connection->resolveUnregistrationJobInClient(jobData.identifier(), registrationKey, unregistrationResult);
191 }
192
193 void SWServer::startScriptFetch(const ServiceWorkerJobData& jobData)
194 {
195     LOG(ServiceWorker, "Server issuing startScriptFetch for current job %" PRIu64 "-%" PRIu64 " in client", jobData.connectionIdentifier(), jobData.identifier());
196     auto* connection = m_connections.get(jobData.connectionIdentifier());
197     if (!connection)
198         return;
199
200     connection->startScriptFetchInClient(jobData.identifier());
201 }
202
203 void SWServer::scriptFetchFinished(Connection& connection, const ServiceWorkerFetchResult& result)
204 {
205     LOG(ServiceWorker, "Server handling scriptFetchFinished for current job %" PRIu64 "-%" PRIu64 " in client", result.connectionIdentifier, result.jobIdentifier);
206
207     ASSERT(m_connections.contains(result.connectionIdentifier));
208
209     auto jobQueue = m_jobQueues.get(result.registrationKey);
210     if (!jobQueue)
211         return;
212
213     jobQueue->scriptFetchFinished(connection, result);
214 }
215
216 void SWServer::scriptContextFailedToStart(Connection& connection, const ServiceWorkerRegistrationKey& registrationKey, ServiceWorkerIdentifier identifier, const String& message)
217 {
218     ASSERT(m_connections.contains(connection.identifier()));
219     
220     if (auto* jobQueue = m_jobQueues.get(registrationKey))
221         jobQueue->scriptContextFailedToStart(connection, identifier, message);
222 }
223
224 void SWServer::scriptContextStarted(Connection& connection, const ServiceWorkerRegistrationKey& registrationKey, ServiceWorkerIdentifier identifier)
225 {
226     ASSERT(m_connections.contains(connection.identifier()));
227
228     if (auto* jobQueue = m_jobQueues.get(registrationKey))
229         jobQueue->scriptContextStarted(connection, identifier);
230 }
231
232 void SWServer::didFinishInstall(Connection& connection, const ServiceWorkerRegistrationKey& registrationKey, ServiceWorkerIdentifier serviceWorkerIdentifier, bool wasSuccessful)
233 {
234     ASSERT(m_connections.contains(connection.identifier()));
235
236     if (auto* jobQueue = m_jobQueues.get(registrationKey))
237         jobQueue->didFinishInstall(connection, serviceWorkerIdentifier, wasSuccessful);
238 }
239
240 void SWServer::addClientServiceWorkerRegistration(Connection& connection, const ServiceWorkerRegistrationKey& key, uint64_t registrationIdentifier)
241 {
242     auto* registration = m_registrations.get(key);
243     if (!registration) {
244         LOG_ERROR("Request to add client-side ServiceWorkerRegistration to non-existent server-side registration");
245         return;
246     }
247     
248     registration->addClientServiceWorkerRegistration(connection.identifier(), registrationIdentifier);
249 }
250
251 void SWServer::removeClientServiceWorkerRegistration(Connection& connection, const ServiceWorkerRegistrationKey& key, uint64_t registrationIdentifier)
252 {
253     auto* registration = m_registrations.get(key);
254     if (!registration) {
255         LOG_ERROR("Request to remove client-side ServiceWorkerRegistration from non-existent server-side registration");
256         return;
257     }
258     
259     registration->removeClientServiceWorkerRegistration(connection.identifier(), registrationIdentifier);
260 }
261
262 Ref<SWServerWorker> SWServer::updateWorker(Connection& connection, const ServiceWorkerRegistrationKey& registrationKey, const URL& url, const String& script, WorkerType type)
263 {
264     auto serviceWorkerIdentifier = generateServiceWorkerIdentifier();
265     
266     auto result = m_workersByID.add(serviceWorkerIdentifier, SWServerWorker::create(registrationKey, url, script, type, serviceWorkerIdentifier));
267     ASSERT(result.isNewEntry);
268     
269     connection.updateServiceWorkerContext({ registrationKey, serviceWorkerIdentifier, script, url });
270     
271     return result.iterator->value.get();
272 }
273
274 void SWServer::fireInstallEvent(Connection& connection, ServiceWorkerIdentifier serviceWorkerIdentifier)
275 {
276     connection.fireInstallEvent(serviceWorkerIdentifier);
277 }
278
279 void SWServer::taskThreadEntryPoint()
280 {
281     ASSERT(!isMainThread());
282
283     while (!m_taskQueue.isKilled())
284         m_taskQueue.waitForMessage().performTask();
285
286     Locker<Lock> locker(m_taskThreadLock);
287     m_taskThread = nullptr;
288 }
289
290 void SWServer::postTask(CrossThreadTask&& task)
291 {
292     m_taskQueue.append(WTFMove(task));
293 }
294
295 void SWServer::postTaskReply(CrossThreadTask&& task)
296 {
297     m_taskReplyQueue.append(WTFMove(task));
298
299     Locker<Lock> locker(m_mainThreadReplyLock);
300     if (m_mainThreadReplyScheduled)
301         return;
302
303     m_mainThreadReplyScheduled = true;
304     callOnMainThread([this] {
305         handleTaskRepliesOnMainThread();
306     });
307 }
308
309 void SWServer::handleTaskRepliesOnMainThread()
310 {
311     {
312         Locker<Lock> locker(m_mainThreadReplyLock);
313         m_mainThreadReplyScheduled = false;
314     }
315
316     while (auto task = m_taskReplyQueue.tryGetMessage())
317         task->performTask();
318 }
319
320 void SWServer::registerConnection(Connection& connection)
321 {
322     auto result = m_connections.add(connection.identifier(), nullptr);
323     ASSERT(result.isNewEntry);
324     result.iterator->value = &connection;
325 }
326
327 void SWServer::unregisterConnection(Connection& connection)
328 {
329     ASSERT(m_connections.get(connection.identifier()) == &connection);
330     m_connections.remove(connection.identifier());
331 }
332
333 const SWServerRegistration* SWServer::doRegistrationMatching(const SecurityOriginData& topOrigin, const URL& clientURL) const
334 {
335     const SWServerRegistration* selectedRegistration = nullptr;
336     for (auto& registration : m_registrations.values()) {
337         if (!registration->key().isMatching(topOrigin, clientURL))
338             continue;
339         if (!selectedRegistration || selectedRegistration->key().scopeLength() < registration->key().scopeLength())
340             selectedRegistration = registration.get();
341     }
342
343     return (selectedRegistration && !selectedRegistration->isUninstalling()) ? selectedRegistration : nullptr;
344 }
345
346 } // namespace WebCore
347
348 #endif // ENABLE(SERVICE_WORKER)