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 "SWServer.h" |
32 | #include "SWServerWorker.h" |
33 | #include "ServiceWorkerTypes.h" |
34 | #include "ServiceWorkerUpdateViaCache.h" |
35 | |
36 | namespace WebCore { |
37 | |
38 | static ServiceWorkerRegistrationIdentifier generateServiceWorkerRegistrationIdentifier() |
39 | { |
40 | return ServiceWorkerRegistrationIdentifier::generate(); |
41 | } |
42 | |
43 | SWServerRegistration::SWServerRegistration(SWServer& server, const ServiceWorkerRegistrationKey& key, ServiceWorkerUpdateViaCache updateViaCache, const URL& scopeURL, const URL& scriptURL) |
44 | : m_identifier(generateServiceWorkerRegistrationIdentifier()) |
45 | , m_registrationKey(key) |
46 | , m_updateViaCache(updateViaCache) |
47 | , m_scopeURL(scopeURL) |
48 | , m_scriptURL(scriptURL) |
49 | , m_server(server) |
50 | , m_creationTime(MonotonicTime::now()) |
51 | { |
52 | m_scopeURL.removeFragmentIdentifier(); |
53 | } |
54 | |
55 | SWServerRegistration::~SWServerRegistration() |
56 | { |
57 | ASSERT(!m_preInstallationWorker || !m_preInstallationWorker->isRunning()); |
58 | ASSERT(!m_installingWorker || !m_installingWorker->isRunning()); |
59 | ASSERT(!m_waitingWorker || !m_waitingWorker->isRunning()); |
60 | ASSERT(!m_activeWorker || !m_activeWorker->isRunning()); |
61 | } |
62 | |
63 | SWServerWorker* SWServerRegistration::getNewestWorker() |
64 | { |
65 | if (m_installingWorker) |
66 | return m_installingWorker.get(); |
67 | if (m_waitingWorker) |
68 | return m_waitingWorker.get(); |
69 | |
70 | return m_activeWorker.get(); |
71 | } |
72 | |
73 | void SWServerRegistration::setPreInstallationWorker(SWServerWorker* worker) |
74 | { |
75 | m_preInstallationWorker = worker; |
76 | } |
77 | |
78 | void SWServerRegistration::updateRegistrationState(ServiceWorkerRegistrationState state, SWServerWorker* worker) |
79 | { |
80 | LOG(ServiceWorker, "(%p) Updating registration state to %i with worker %p" , this, (int)state, worker); |
81 | |
82 | switch (state) { |
83 | case ServiceWorkerRegistrationState::Installing: |
84 | ASSERT(!m_installingWorker || !m_installingWorker->isRunning() || m_waitingWorker == m_installingWorker); |
85 | m_installingWorker = worker; |
86 | break; |
87 | case ServiceWorkerRegistrationState::Waiting: |
88 | ASSERT(!m_waitingWorker || !m_waitingWorker->isRunning() || m_activeWorker == m_waitingWorker); |
89 | m_waitingWorker = worker; |
90 | break; |
91 | case ServiceWorkerRegistrationState::Active: |
92 | ASSERT(!m_activeWorker || !m_activeWorker->isRunning()); |
93 | m_activeWorker = worker; |
94 | break; |
95 | }; |
96 | |
97 | Optional<ServiceWorkerData> serviceWorkerData; |
98 | if (worker) |
99 | serviceWorkerData = worker->data(); |
100 | |
101 | forEachConnection([&](auto& connection) { |
102 | connection.updateRegistrationStateInClient(this->identifier(), state, serviceWorkerData); |
103 | }); |
104 | } |
105 | |
106 | void SWServerRegistration::updateWorkerState(SWServerWorker& worker, ServiceWorkerState state) |
107 | { |
108 | LOG(ServiceWorker, "Updating worker %p state to %i (%p)" , &worker, (int)state, this); |
109 | |
110 | worker.setState(state); |
111 | } |
112 | |
113 | void SWServerRegistration::setUpdateViaCache(ServiceWorkerUpdateViaCache updateViaCache) |
114 | { |
115 | m_updateViaCache = updateViaCache; |
116 | forEachConnection([&](auto& connection) { |
117 | connection.setRegistrationUpdateViaCache(this->identifier(), updateViaCache); |
118 | }); |
119 | } |
120 | |
121 | void SWServerRegistration::setLastUpdateTime(WallTime time) |
122 | { |
123 | m_lastUpdateTime = time; |
124 | forEachConnection([&](auto& connection) { |
125 | connection.setRegistrationLastUpdateTime(this->identifier(), time); |
126 | }); |
127 | } |
128 | |
129 | void SWServerRegistration::fireUpdateFoundEvent() |
130 | { |
131 | forEachConnection([&](auto& connection) { |
132 | connection.fireUpdateFoundEvent(this->identifier()); |
133 | }); |
134 | } |
135 | |
136 | void SWServerRegistration::forEachConnection(const WTF::Function<void(SWServer::Connection&)>& apply) |
137 | { |
138 | for (auto connectionIdentifierWithClients : m_connectionsWithClientRegistrations.values()) { |
139 | if (auto* connection = m_server.connection(connectionIdentifierWithClients)) |
140 | apply(*connection); |
141 | } |
142 | } |
143 | |
144 | ServiceWorkerRegistrationData SWServerRegistration::data() const |
145 | { |
146 | Optional<ServiceWorkerData> installingWorkerData; |
147 | if (m_installingWorker) |
148 | installingWorkerData = m_installingWorker->data(); |
149 | |
150 | Optional<ServiceWorkerData> waitingWorkerData; |
151 | if (m_waitingWorker) |
152 | waitingWorkerData = m_waitingWorker->data(); |
153 | |
154 | Optional<ServiceWorkerData> activeWorkerData; |
155 | if (m_activeWorker) |
156 | activeWorkerData = m_activeWorker->data(); |
157 | |
158 | return { m_registrationKey, identifier(), m_scopeURL, m_updateViaCache, m_lastUpdateTime, WTFMove(installingWorkerData), WTFMove(waitingWorkerData), WTFMove(activeWorkerData) }; |
159 | } |
160 | |
161 | void SWServerRegistration::addClientServiceWorkerRegistration(SWServerConnectionIdentifier connectionIdentifier) |
162 | { |
163 | m_connectionsWithClientRegistrations.add(connectionIdentifier); |
164 | } |
165 | |
166 | void SWServerRegistration::removeClientServiceWorkerRegistration(SWServerConnectionIdentifier connectionIdentifier) |
167 | { |
168 | m_connectionsWithClientRegistrations.remove(connectionIdentifier); |
169 | } |
170 | |
171 | void SWServerRegistration::addClientUsingRegistration(const ServiceWorkerClientIdentifier& clientIdentifier) |
172 | { |
173 | auto addResult = m_clientsUsingRegistration.ensure(clientIdentifier.serverConnectionIdentifier, [] { |
174 | return HashSet<DocumentIdentifier> { }; |
175 | }).iterator->value.add(clientIdentifier.contextIdentifier); |
176 | ASSERT_UNUSED(addResult, addResult.isNewEntry); |
177 | } |
178 | |
179 | void SWServerRegistration::removeClientUsingRegistration(const ServiceWorkerClientIdentifier& clientIdentifier) |
180 | { |
181 | auto iterator = m_clientsUsingRegistration.find(clientIdentifier.serverConnectionIdentifier); |
182 | ASSERT(iterator != m_clientsUsingRegistration.end()); |
183 | if (iterator == m_clientsUsingRegistration.end()) |
184 | return; |
185 | |
186 | bool wasRemoved = iterator->value.remove(clientIdentifier.contextIdentifier); |
187 | ASSERT_UNUSED(wasRemoved, wasRemoved); |
188 | |
189 | if (iterator->value.isEmpty()) |
190 | m_clientsUsingRegistration.remove(iterator); |
191 | |
192 | handleClientUnload(); |
193 | } |
194 | |
195 | // https://w3c.github.io/ServiceWorker/#notify-controller-change |
196 | void SWServerRegistration::notifyClientsOfControllerChange() |
197 | { |
198 | ASSERT(activeWorker()); |
199 | |
200 | for (auto& item : m_clientsUsingRegistration) { |
201 | if (auto* connection = m_server.connection(item.key)) |
202 | connection->notifyClientsOfControllerChange(item.value, activeWorker()->data()); |
203 | } |
204 | } |
205 | |
206 | void SWServerRegistration::unregisterServerConnection(SWServerConnectionIdentifier serverConnectionIdentifier) |
207 | { |
208 | m_connectionsWithClientRegistrations.removeAll(serverConnectionIdentifier); |
209 | m_clientsUsingRegistration.remove(serverConnectionIdentifier); |
210 | } |
211 | |
212 | // https://w3c.github.io/ServiceWorker/#try-clear-registration-algorithm |
213 | bool SWServerRegistration::tryClear() |
214 | { |
215 | if (hasClientsUsingRegistration()) |
216 | return false; |
217 | |
218 | if (installingWorker() && installingWorker()->hasPendingEvents()) |
219 | return false; |
220 | if (waitingWorker() && waitingWorker()->hasPendingEvents()) |
221 | return false; |
222 | if (activeWorker() && activeWorker()->hasPendingEvents()) |
223 | return false; |
224 | |
225 | clear(); |
226 | return true; |
227 | } |
228 | |
229 | // https://w3c.github.io/ServiceWorker/#clear-registration |
230 | void SWServerRegistration::clear() |
231 | { |
232 | if (m_preInstallationWorker) { |
233 | ASSERT(m_preInstallationWorker->state() == ServiceWorkerState::Redundant); |
234 | m_preInstallationWorker->terminate(); |
235 | m_preInstallationWorker = nullptr; |
236 | } |
237 | |
238 | RefPtr<SWServerWorker> installingWorker = this->installingWorker(); |
239 | if (installingWorker) { |
240 | installingWorker->terminate(); |
241 | updateRegistrationState(ServiceWorkerRegistrationState::Installing, nullptr); |
242 | } |
243 | RefPtr<SWServerWorker> waitingWorker = this->waitingWorker(); |
244 | if (waitingWorker) { |
245 | waitingWorker->terminate(); |
246 | updateRegistrationState(ServiceWorkerRegistrationState::Waiting, nullptr); |
247 | } |
248 | RefPtr<SWServerWorker> activeWorker = this->activeWorker(); |
249 | if (activeWorker) { |
250 | activeWorker->terminate(); |
251 | updateRegistrationState(ServiceWorkerRegistrationState::Active, nullptr); |
252 | } |
253 | |
254 | if (installingWorker) |
255 | updateWorkerState(*installingWorker, ServiceWorkerState::Redundant); |
256 | if (waitingWorker) |
257 | updateWorkerState(*waitingWorker, ServiceWorkerState::Redundant); |
258 | if (activeWorker) |
259 | updateWorkerState(*activeWorker, ServiceWorkerState::Redundant); |
260 | |
261 | // Remove scope to registration map[scopeString]. |
262 | m_server.removeRegistration(key()); |
263 | } |
264 | |
265 | // https://w3c.github.io/ServiceWorker/#try-activate-algorithm |
266 | void SWServerRegistration::tryActivate() |
267 | { |
268 | // If registration's waiting worker is null, return. |
269 | if (!waitingWorker()) |
270 | return; |
271 | // If registration's active worker is not null and registration's active worker's state is activating, return. |
272 | if (activeWorker() && activeWorker()->state() == ServiceWorkerState::Activating) |
273 | return; |
274 | |
275 | // Invoke Activate with registration if either of the following is true: |
276 | // - registration's active worker is null. |
277 | // - The result of running Service Worker Has No Pending Events with registration's active worker is true, |
278 | // and no service worker client is using registration or registration's waiting worker's skip waiting flag is set. |
279 | if (!activeWorker() || (!activeWorker()->hasPendingEvents() && (!hasClientsUsingRegistration() || waitingWorker()->isSkipWaitingFlagSet()))) |
280 | activate(); |
281 | } |
282 | |
283 | // https://w3c.github.io/ServiceWorker/#activate |
284 | void SWServerRegistration::activate() |
285 | { |
286 | // If registration's waiting worker is null, abort these steps. |
287 | if (!waitingWorker()) |
288 | return; |
289 | |
290 | // If registration's active worker is not null, then: |
291 | if (auto* worker = activeWorker()) { |
292 | // Terminate registration's active worker. |
293 | worker->terminate(); |
294 | // Run the Update Worker State algorithm passing registration's active worker and redundant as the arguments. |
295 | updateWorkerState(*worker, ServiceWorkerState::Redundant); |
296 | } |
297 | // Run the Update Registration State algorithm passing registration, "active" and registration's waiting worker as the arguments. |
298 | updateRegistrationState(ServiceWorkerRegistrationState::Active, waitingWorker()); |
299 | // Run the Update Registration State algorithm passing registration, "waiting" and null as the arguments. |
300 | updateRegistrationState(ServiceWorkerRegistrationState::Waiting, nullptr); |
301 | // Run the Update Worker State algorithm passing registration's active worker and activating as the arguments. |
302 | updateWorkerState(*activeWorker(), ServiceWorkerState::Activating); |
303 | // FIXME: For each service worker client whose creation URL matches registration's scope url... |
304 | |
305 | // The registration now has an active worker so we need to check if there are any ready promises that were waiting for this. |
306 | m_server.resolveRegistrationReadyRequests(*this); |
307 | |
308 | // For each service worker client who is using registration: |
309 | // - Set client's active worker to registration's active worker. |
310 | |
311 | // - Invoke Notify Controller Change algorithm with client as the argument. |
312 | notifyClientsOfControllerChange(); |
313 | |
314 | // FIXME: Invoke Run Service Worker algorithm with activeWorker as the argument. |
315 | |
316 | // Queue a task to fire the activate event. |
317 | ASSERT(activeWorker()); |
318 | m_server.fireActivateEvent(*activeWorker()); |
319 | } |
320 | |
321 | // https://w3c.github.io/ServiceWorker/#activate (post activate event steps). |
322 | void SWServerRegistration::didFinishActivation(ServiceWorkerIdentifier serviceWorkerIdentifier) |
323 | { |
324 | if (!activeWorker() || activeWorker()->identifier() != serviceWorkerIdentifier) |
325 | return; |
326 | |
327 | // Run the Update Worker State algorithm passing registration's active worker and activated as the arguments. |
328 | updateWorkerState(*activeWorker(), ServiceWorkerState::Activated); |
329 | } |
330 | |
331 | // https://w3c.github.io/ServiceWorker/#on-client-unload-algorithm |
332 | void SWServerRegistration::handleClientUnload() |
333 | { |
334 | if (hasClientsUsingRegistration()) |
335 | return; |
336 | if (isUninstalling() && tryClear()) |
337 | return; |
338 | tryActivate(); |
339 | } |
340 | |
341 | void SWServerRegistration::controlClient(ServiceWorkerClientIdentifier identifier) |
342 | { |
343 | ASSERT(activeWorker()); |
344 | |
345 | addClientUsingRegistration(identifier); |
346 | |
347 | HashSet<DocumentIdentifier> identifiers; |
348 | identifiers.add(identifier.contextIdentifier); |
349 | m_server.connection(identifier.serverConnectionIdentifier)->notifyClientsOfControllerChange(identifiers, activeWorker()->data()); |
350 | } |
351 | |
352 | void SWServerRegistration::setIsUninstalling(bool value) |
353 | { |
354 | if (m_uninstalling == value) |
355 | return; |
356 | |
357 | m_uninstalling = value; |
358 | |
359 | if (!m_uninstalling && activeWorker()) { |
360 | // Registration with active worker has been resurrected, we need to check if any ready promises were waiting for this. |
361 | m_server.resolveRegistrationReadyRequests(*this); |
362 | } |
363 | } |
364 | |
365 | } // namespace WebCore |
366 | |
367 | #endif // ENABLE(SERVICE_WORKER) |
368 | |