0d2178e7c4c00e708afe51585942824d43e275d5
[WebKit-https.git] / Source / WebCore / workers / service / server / SWServerRegistration.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 "SWServerRegistration.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 SWServerRegistration::SWServerRegistration(SWServer& server, const ServiceWorkerRegistrationKey& key)
43     : m_jobTimer(*this, &SWServerRegistration::startNextJob)
44     , m_server(server)
45     , m_registrationKey(key)
46 {
47 }
48
49 SWServerRegistration::~SWServerRegistration()
50 {
51     ASSERT(m_jobQueue.isEmpty());
52 }
53
54 void SWServerRegistration::enqueueJob(const ServiceWorkerJobData& jobData)
55 {
56     // FIXME: Per the spec, check if this job is equivalent to the last job on the queue.
57     // If it is, stack it along with that job.
58
59     m_jobQueue.append(jobData);
60
61     if (m_currentJob)
62         return;
63
64     if (!m_jobTimer.isActive())
65         m_jobTimer.startOneShot(0_s);
66 }
67
68 void SWServerRegistration::scriptFetchFinished(SWServer::Connection& connection, const ServiceWorkerFetchResult& result)
69 {
70     ASSERT(m_currentJob && m_currentJob->identifier() == result.jobIdentifier);
71
72     if (!result.scriptError.isNull()) {
73         rejectCurrentJob(ExceptionData { UnknownError, makeString("Script URL ", m_currentJob->scriptURL.string(), " fetch resulted in error: ", result.scriptError.localizedDescription()) });
74         
75         // If newestWorker is null, invoke Clear Registration algorithm passing this registration as its argument.
76         // FIXME: We don't have "clear registration" yet.
77
78         return;
79     }
80
81     m_lastUpdateTime = currentTime();
82     
83     // FIXME: If the script data matches byte-for-byte with the existing newestWorker,
84     // then resolve and finish the job without doing anything further.
85
86     // FIXME: Support the proper worker type (classic vs module)
87     m_server.createWorker(connection, m_registrationKey, m_currentJob->scriptURL, result.script, WorkerType::Classic);
88 }
89
90 void SWServerRegistration::scriptContextFailedToStart(SWServer::Connection&, const String& workerID, const String& message)
91 {
92     UNUSED_PARAM(workerID);
93
94     rejectCurrentJob(ExceptionData { UnknownError, message });
95 }
96
97 void SWServerRegistration::scriptContextStarted(SWServer::Connection&, uint64_t identifier, const String& workerID)
98 {
99     UNUSED_PARAM(workerID);
100     resolveCurrentJob(ServiceWorkerRegistrationData { m_registrationKey, identifier, m_scopeURL, m_updateViaCache.value_or(ServiceWorkerUpdateViaCache::Imports) });
101 }
102
103 void SWServerRegistration::startNextJob()
104 {
105     ASSERT(isMainThread());
106     ASSERT(!m_currentJob);
107     ASSERT(!m_jobQueue.isEmpty());
108
109     m_currentJob = std::make_unique<ServiceWorkerJobData>(m_jobQueue.takeFirst().isolatedCopy());
110
111     switch (m_currentJob->type) {
112     case ServiceWorkerJobType::Register:
113         m_server.postTask(createCrossThreadTask(*this, &SWServerRegistration::runRegisterJob, *m_currentJob));
114         return;
115     }
116
117     RELEASE_ASSERT_NOT_REACHED();
118 }
119
120 bool SWServerRegistration::isEmpty()
121 {
122     ASSERT(!isMainThread());
123
124     // Having or not-having an m_updateViaCache flag is currently
125     // the signal as to whether or not this is an empty (i.e. "new") registration.
126     // There will be a more explicit signal in the near future.
127     return !m_updateViaCache;
128 }
129
130 SWServerWorker* SWServerRegistration::getNewestWorker()
131 {
132     ASSERT(!isMainThread());
133     if (m_installingWorker)
134         return m_installingWorker.get();
135     if (m_waitingWorker)
136         return m_waitingWorker.get();
137
138     return m_activeWorker.get();
139 }
140
141 void SWServerRegistration::runRegisterJob(const ServiceWorkerJobData& job)
142 {
143     ASSERT(!isMainThread());
144     ASSERT(job.type == ServiceWorkerJobType::Register);
145
146     if (!shouldTreatAsPotentiallyTrustworthy(job.scriptURL))
147         return rejectWithExceptionOnMainThread(ExceptionData { SecurityError, ASCIILiteral("Script URL is not potentially trustworthy") });
148
149     // If the origin of job’s script url is not job’s referrer's origin, then:
150     if (!protocolHostAndPortAreEqual(job.scriptURL, job.clientCreationURL))
151         return rejectWithExceptionOnMainThread(ExceptionData { SecurityError, ASCIILiteral("Script origin does not match the registering client's origin") });
152
153     // If the origin of job’s scope url is not job’s referrer's origin, then:
154     if (!protocolHostAndPortAreEqual(job.scopeURL, job.clientCreationURL))
155         return rejectWithExceptionOnMainThread(ExceptionData { SecurityError, ASCIILiteral("Scope origin does not match the registering client's origin") });
156
157     // If registration is not null (in our parlance "empty"), then:
158     if (!isEmpty()) {
159         ASSERT(m_updateViaCache);
160
161         m_uninstalling = false;
162         auto* newestWorker = getNewestWorker();
163         if (newestWorker && equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()) && job.registrationOptions.updateViaCache == *m_updateViaCache) {
164             resolveWithRegistrationOnMainThread();
165             return;
166         }
167     } else {
168         m_scopeURL = job.scopeURL.isolatedCopy();
169         m_scopeURL.removeFragmentIdentifier();
170         m_updateViaCache = job.registrationOptions.updateViaCache;
171     }
172
173     runUpdateJob(job);
174 }
175
176 void SWServerRegistration::runUpdateJob(const ServiceWorkerJobData& job)
177 {
178     // If registration is null (in our parlance "empty") or registration’s uninstalling flag is set, then:
179     if (isEmpty())
180         return rejectWithExceptionOnMainThread(ExceptionData { TypeError, ASCIILiteral("Cannot update a null/nonexistent service worker registration") });
181     if (m_uninstalling)
182         return rejectWithExceptionOnMainThread(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker registration that is uninstalling") });
183
184     // If job’s job type is update, and newestWorker’s script url does not equal job’s script url with the exclude fragments flag set, then:
185     auto* newestWorker = getNewestWorker();
186     if (newestWorker && !equalIgnoringFragmentIdentifier(job.scriptURL, newestWorker->scriptURL()))
187         return rejectWithExceptionOnMainThread(ExceptionData { TypeError, ASCIILiteral("Cannot update a service worker with a requested script URL whose newest worker has a different script URL") });
188
189     startScriptFetchFromMainThread();
190 }
191
192 void SWServerRegistration::rejectWithExceptionOnMainThread(const ExceptionData& exception)
193 {
194     ASSERT(!isMainThread());
195     m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::rejectCurrentJob, exception));
196 }
197
198 void SWServerRegistration::resolveWithRegistrationOnMainThread()
199 {
200     ASSERT(!isMainThread());
201     m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::resolveCurrentJob, data()));
202 }
203
204 void SWServerRegistration::startScriptFetchFromMainThread()
205 {
206     ASSERT(!isMainThread());
207     m_server.postTaskReply(createCrossThreadTask(*this, &SWServerRegistration::startScriptFetchForCurrentJob));
208 }
209
210 void SWServerRegistration::rejectCurrentJob(const ExceptionData& exceptionData)
211 {
212     ASSERT(isMainThread());
213     ASSERT(m_currentJob);
214
215     m_server.rejectJob(*m_currentJob, exceptionData);
216
217     finishCurrentJob();
218 }
219
220 void SWServerRegistration::resolveCurrentJob(const ServiceWorkerRegistrationData& data)
221 {
222     ASSERT(isMainThread());
223     ASSERT(m_currentJob);
224
225     m_server.resolveJob(*m_currentJob, data);
226
227     finishCurrentJob();
228 }
229
230 void SWServerRegistration::startScriptFetchForCurrentJob()
231 {
232     ASSERT(isMainThread());
233     ASSERT(m_currentJob);
234
235     m_server.startScriptFetch(*m_currentJob);
236 }
237
238 void SWServerRegistration::finishCurrentJob()
239 {
240     ASSERT(m_currentJob);
241     ASSERT(!m_jobTimer.isActive());
242
243     m_currentJob = nullptr;
244     if (m_jobQueue.isEmpty())
245         return;
246
247     startNextJob();
248 }
249
250 ServiceWorkerRegistrationData SWServerRegistration::data() const
251 {
252     return { m_registrationKey, identifier(), m_scopeURL, m_updateViaCache.value_or(ServiceWorkerUpdateViaCache::Imports) };
253 }
254
255
256 } // namespace WebCore
257
258 #endif // ENABLE(SERVICE_WORKER)