Implement Service Worker Matching Registration algorithm
[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::addServiceWorkerRegistrationInServer(const ServiceWorkerRegistrationKey& key, uint64_t clientRegistrationIdentifier)
116 {
117     m_server.addClientServiceWorkerRegistration(*this, key, clientRegistrationIdentifier);
118 }
119
120 void SWServer::Connection::removeServiceWorkerRegistrationInServer(const ServiceWorkerRegistrationKey& key, uint64_t clientRegistrationIdentifier)
121 {
122     m_server.removeClientServiceWorkerRegistration(*this, key, clientRegistrationIdentifier);
123 }
124
125 void SWServer::Connection::scriptContextFailedToStart(const ServiceWorkerRegistrationKey& registrationKey, ServiceWorkerIdentifier identifier, const String& message)
126 {
127     m_server.scriptContextFailedToStart(*this, registrationKey, identifier, message);
128 }
129
130 void SWServer::Connection::scriptContextStarted(const ServiceWorkerRegistrationKey& registrationKey, ServiceWorkerIdentifier identifier)
131 {
132     m_server.scriptContextStarted(*this, registrationKey, identifier);
133 }
134
135 SWServer::SWServer()
136 {
137     m_taskThread = Thread::create(ASCIILiteral("ServiceWorker Task Thread"), [this] {
138         taskThreadEntryPoint();
139     });
140 }
141
142 // https://w3c.github.io/ServiceWorker/#schedule-job-algorithm
143 void SWServer::scheduleJob(const ServiceWorkerJobData& jobData)
144 {
145     ASSERT(m_connections.contains(jobData.connectionIdentifier()));
146
147     // FIXME: Per the spec, check if this job is equivalent to the last job on the queue.
148     // If it is, stack it along with that job.
149
150     auto& jobQueue = *m_jobQueues.ensure(jobData.registrationKey(), [this, &jobData] {
151         return std::make_unique<SWServerJobQueue>(*this, jobData.registrationKey());
152     }).iterator->value;
153
154     jobQueue.enqueueJob(jobData);
155     if (jobQueue.size() == 1)
156         jobQueue.runNextJob();
157 }
158
159 void SWServer::rejectJob(const ServiceWorkerJobData& jobData, const ExceptionData& exceptionData)
160 {
161     LOG(ServiceWorker, "Rejected ServiceWorker job %" PRIu64 "-%" PRIu64 " in server", jobData.connectionIdentifier(), jobData.identifier());
162     auto* connection = m_connections.get(jobData.connectionIdentifier());
163     if (!connection)
164         return;
165
166     connection->rejectJobInClient(jobData.identifier(), exceptionData);
167 }
168
169 void SWServer::resolveRegistrationJob(const ServiceWorkerJobData& jobData, const ServiceWorkerRegistrationData& registrationData)
170 {
171     LOG(ServiceWorker, "Resolved ServiceWorker job %" PRIu64 "-%" PRIu64 " in server with registration %" PRIu64, jobData.connectionIdentifier(), jobData.identifier(), registrationData.identifier);
172     auto* connection = m_connections.get(jobData.connectionIdentifier());
173     if (!connection)
174         return;
175
176     connection->resolveRegistrationJobInClient(jobData.identifier(), registrationData);
177 }
178
179 void SWServer::resolveUnregistrationJob(const ServiceWorkerJobData& jobData, const ServiceWorkerRegistrationKey& registrationKey, bool unregistrationResult)
180 {
181     auto* connection = m_connections.get(jobData.connectionIdentifier());
182     if (!connection)
183         return;
184
185     connection->resolveUnregistrationJobInClient(jobData.identifier(), registrationKey, unregistrationResult);
186 }
187
188 void SWServer::startScriptFetch(const ServiceWorkerJobData& jobData)
189 {
190     LOG(ServiceWorker, "Server issuing startScriptFetch for current job %" PRIu64 "-%" PRIu64 " in client", jobData.connectionIdentifier(), jobData.identifier());
191     auto* connection = m_connections.get(jobData.connectionIdentifier());
192     if (!connection)
193         return;
194
195     connection->startScriptFetchInClient(jobData.identifier());
196 }
197
198 void SWServer::scriptFetchFinished(Connection& connection, const ServiceWorkerFetchResult& result)
199 {
200     LOG(ServiceWorker, "Server handling scriptFetchFinished for current job %" PRIu64 "-%" PRIu64 " in client", result.connectionIdentifier, result.jobIdentifier);
201
202     ASSERT(m_connections.contains(result.connectionIdentifier));
203
204     auto jobQueue = m_jobQueues.get(result.registrationKey);
205     if (!jobQueue)
206         return;
207
208     jobQueue->scriptFetchFinished(connection, result);
209 }
210
211 void SWServer::scriptContextFailedToStart(Connection& connection, const ServiceWorkerRegistrationKey& registrationKey, ServiceWorkerIdentifier identifier, const String& message)
212 {
213     ASSERT(m_connections.contains(connection.identifier()));
214     
215     if (auto* jobQueue = m_jobQueues.get(registrationKey))
216         jobQueue->scriptContextFailedToStart(connection, identifier, message);
217 }
218
219 void SWServer::scriptContextStarted(Connection& connection, const ServiceWorkerRegistrationKey& registrationKey, ServiceWorkerIdentifier identifier)
220 {
221     ASSERT(m_connections.contains(connection.identifier()));
222
223     if (auto* jobQueue = m_jobQueues.get(registrationKey))
224         jobQueue->scriptContextStarted(connection, identifier);
225 }
226
227 void SWServer::addClientServiceWorkerRegistration(Connection& connection, const ServiceWorkerRegistrationKey& key, uint64_t registrationIdentifier)
228 {
229     auto* registration = m_registrations.get(key);
230     if (!registration) {
231         LOG_ERROR("Request to add client-side ServiceWorkerRegistration to non-existent server-side registration");
232         return;
233     }
234     
235     registration->addClientServiceWorkerRegistration(connection.identifier(), registrationIdentifier);
236 }
237
238 void SWServer::removeClientServiceWorkerRegistration(Connection& connection, const ServiceWorkerRegistrationKey& key, uint64_t registrationIdentifier)
239 {
240     auto* registration = m_registrations.get(key);
241     if (!registration) {
242         LOG_ERROR("Request to remove client-side ServiceWorkerRegistration from non-existent server-side registration");
243         return;
244     }
245     
246     registration->removeClientServiceWorkerRegistration(connection.identifier(), registrationIdentifier);
247 }
248
249 Ref<SWServerWorker> SWServer::updateWorker(Connection& connection, const ServiceWorkerRegistrationKey& registrationKey, const URL& url, const String& script, WorkerType type)
250 {
251     auto serviceWorkerIdentifier = generateServiceWorkerIdentifier();
252     
253     auto result = m_workersByID.add(serviceWorkerIdentifier, SWServerWorker::create(registrationKey, url, script, type, serviceWorkerIdentifier));
254     ASSERT(result.isNewEntry);
255     
256     connection.updateServiceWorkerContext({ registrationKey, serviceWorkerIdentifier, script, url });
257     
258     return result.iterator->value.get();
259 }
260
261 void SWServer::taskThreadEntryPoint()
262 {
263     ASSERT(!isMainThread());
264
265     while (!m_taskQueue.isKilled())
266         m_taskQueue.waitForMessage().performTask();
267
268     Locker<Lock> locker(m_taskThreadLock);
269     m_taskThread = nullptr;
270 }
271
272 void SWServer::postTask(CrossThreadTask&& task)
273 {
274     m_taskQueue.append(WTFMove(task));
275 }
276
277 void SWServer::postTaskReply(CrossThreadTask&& task)
278 {
279     m_taskReplyQueue.append(WTFMove(task));
280
281     Locker<Lock> locker(m_mainThreadReplyLock);
282     if (m_mainThreadReplyScheduled)
283         return;
284
285     m_mainThreadReplyScheduled = true;
286     callOnMainThread([this] {
287         handleTaskRepliesOnMainThread();
288     });
289 }
290
291 void SWServer::handleTaskRepliesOnMainThread()
292 {
293     {
294         Locker<Lock> locker(m_mainThreadReplyLock);
295         m_mainThreadReplyScheduled = false;
296     }
297
298     while (auto task = m_taskReplyQueue.tryGetMessage())
299         task->performTask();
300 }
301
302 void SWServer::registerConnection(Connection& connection)
303 {
304     auto result = m_connections.add(connection.identifier(), nullptr);
305     ASSERT(result.isNewEntry);
306     result.iterator->value = &connection;
307 }
308
309 void SWServer::unregisterConnection(Connection& connection)
310 {
311     ASSERT(m_connections.get(connection.identifier()) == &connection);
312     m_connections.remove(connection.identifier());
313 }
314
315 const SWServerRegistration* SWServer::doRegistrationMatching(const SecurityOriginData& topOrigin, const URL& clientURL) const
316 {
317     const SWServerRegistration* selectedRegistration = nullptr;
318     for (auto& registration : m_registrations.values()) {
319         if (!registration->key().isMatching(topOrigin, clientURL))
320             continue;
321         if (!selectedRegistration || selectedRegistration->key().scopeLength() < registration->key().scopeLength())
322             selectedRegistration = registration.get();
323     }
324
325     return (selectedRegistration && !selectedRegistration->isUninstalling()) ? selectedRegistration : nullptr;
326 }
327
328 } // namespace WebCore
329
330 #endif // ENABLE(SERVICE_WORKER)