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 "ServiceWorkerRegistration.h"
28
29#if ENABLE(SERVICE_WORKER)
30#include "DOMWindow.h"
31#include "Document.h"
32#include "Event.h"
33#include "EventNames.h"
34#include "Logging.h"
35#include "ServiceWorker.h"
36#include "ServiceWorkerContainer.h"
37#include "ServiceWorkerTypes.h"
38#include "WorkerGlobalScope.h"
39#include <wtf/IsoMallocInlines.h>
40
41#define REGISTRATION_RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(m_container->isAlwaysOnLoggingAllowed(), ServiceWorker, "%p - ServiceWorkerRegistration::" fmt, this, ##__VA_ARGS__)
42#define REGISTRATION_RELEASE_LOG_ERROR_IF_ALLOWED(fmt, ...) RELEASE_LOG_ERROR_IF(m_container->isAlwaysOnLoggingAllowed(), ServiceWorker, "%p - ServiceWorkerRegistration::" fmt, this, ##__VA_ARGS__)
43
44namespace WebCore {
45
46WTF_MAKE_ISO_ALLOCATED_IMPL(ServiceWorkerRegistration);
47
48const Seconds softUpdateDelay { 1_s };
49
50Ref<ServiceWorkerRegistration> ServiceWorkerRegistration::getOrCreate(ScriptExecutionContext& context, Ref<ServiceWorkerContainer>&& container, ServiceWorkerRegistrationData&& data)
51{
52 if (auto* registration = container->registration(data.identifier)) {
53 ASSERT(!registration->m_isStopped);
54 return *registration;
55 }
56
57 return adoptRef(*new ServiceWorkerRegistration(context, WTFMove(container), WTFMove(data)));
58}
59
60ServiceWorkerRegistration::ServiceWorkerRegistration(ScriptExecutionContext& context, Ref<ServiceWorkerContainer>&& container, ServiceWorkerRegistrationData&& registrationData)
61 : ActiveDOMObject(&context)
62 , m_registrationData(WTFMove(registrationData))
63 , m_container(WTFMove(container))
64 , m_softUpdateTimer([this] { softUpdate(); })
65{
66 LOG(ServiceWorker, "Creating registration %p for registration key %s", this, m_registrationData.key.loggingString().utf8().data());
67 suspendIfNeeded();
68
69 if (m_registrationData.installingWorker)
70 m_installingWorker = ServiceWorker::getOrCreate(context, WTFMove(*m_registrationData.installingWorker));
71 if (m_registrationData.waitingWorker)
72 m_waitingWorker = ServiceWorker::getOrCreate(context, WTFMove(*m_registrationData.waitingWorker));
73 if (m_registrationData.activeWorker)
74 m_activeWorker = ServiceWorker::getOrCreate(context, WTFMove(*m_registrationData.activeWorker));
75
76 REGISTRATION_RELEASE_LOG_IF_ALLOWED("ServiceWorkerRegistration: ID %llu, installing: %llu, waiting: %llu, active: %llu", identifier().toUInt64(), m_installingWorker ? m_installingWorker->identifier().toUInt64() : 0, m_waitingWorker ? m_waitingWorker->identifier().toUInt64() : 0, m_activeWorker ? m_activeWorker->identifier().toUInt64() : 0);
77
78 m_container->addRegistration(*this);
79
80 relaxAdoptionRequirement();
81 updatePendingActivityForEventDispatch();
82}
83
84ServiceWorkerRegistration::~ServiceWorkerRegistration()
85{
86 LOG(ServiceWorker, "Deleting registration %p for registration key %s", this, m_registrationData.key.loggingString().utf8().data());
87
88 m_container->removeRegistration(*this);
89}
90
91ServiceWorker* ServiceWorkerRegistration::installing()
92{
93 return m_installingWorker.get();
94}
95
96ServiceWorker* ServiceWorkerRegistration::waiting()
97{
98 return m_waitingWorker.get();
99}
100
101ServiceWorker* ServiceWorkerRegistration::active()
102{
103 return m_activeWorker.get();
104}
105
106ServiceWorker* ServiceWorkerRegistration::getNewestWorker()
107{
108 if (m_installingWorker)
109 return m_installingWorker.get();
110 if (m_waitingWorker)
111 return m_waitingWorker.get();
112
113 return m_activeWorker.get();
114}
115
116const String& ServiceWorkerRegistration::scope() const
117{
118 return m_registrationData.scopeURL;
119}
120
121ServiceWorkerUpdateViaCache ServiceWorkerRegistration::updateViaCache() const
122{
123 return m_registrationData.updateViaCache;
124}
125
126WallTime ServiceWorkerRegistration::lastUpdateTime() const
127{
128 return m_registrationData.lastUpdateTime;
129}
130
131void ServiceWorkerRegistration::setLastUpdateTime(WallTime lastUpdateTime)
132{
133 m_registrationData.lastUpdateTime = lastUpdateTime;
134}
135
136void ServiceWorkerRegistration::setUpdateViaCache(ServiceWorkerUpdateViaCache updateViaCache)
137{
138 m_registrationData.updateViaCache = updateViaCache;
139}
140
141void ServiceWorkerRegistration::update(Ref<DeferredPromise>&& promise)
142{
143 if (m_isStopped) {
144 promise->reject(Exception(InvalidStateError));
145 return;
146 }
147
148 auto* newestWorker = getNewestWorker();
149 if (!newestWorker) {
150 promise->reject(Exception(InvalidStateError, "newestWorker is null"_s));
151 return;
152 }
153
154 // FIXME: Support worker types.
155 m_container->updateRegistration(m_registrationData.scopeURL, newestWorker->scriptURL(), WorkerType::Classic, WTFMove(promise));
156}
157
158// To avoid scheduling many updates during a single page load, we do soft updates on a 1 second delay and keep delaying
159// as long as soft update requests keep coming. This seems to match Chrome's behavior.
160void ServiceWorkerRegistration::scheduleSoftUpdate()
161{
162 if (m_softUpdateTimer.isActive())
163 m_softUpdateTimer.stop();
164 m_softUpdateTimer.startOneShot(softUpdateDelay);
165}
166
167void ServiceWorkerRegistration::softUpdate()
168{
169 if (m_isStopped)
170 return;
171
172 auto* newestWorker = getNewestWorker();
173 if (!newestWorker)
174 return;
175
176 // FIXME: Support worker types.
177 m_container->updateRegistration(m_registrationData.scopeURL, newestWorker->scriptURL(), WorkerType::Classic, nullptr);
178}
179
180void ServiceWorkerRegistration::unregister(Ref<DeferredPromise>&& promise)
181{
182 if (m_isStopped) {
183 promise->reject(Exception(InvalidStateError));
184 return;
185 }
186
187 m_container->removeRegistration(m_registrationData.scopeURL, WTFMove(promise));
188}
189
190void ServiceWorkerRegistration::updateStateFromServer(ServiceWorkerRegistrationState state, RefPtr<ServiceWorker>&& serviceWorker)
191{
192 switch (state) {
193 case ServiceWorkerRegistrationState::Installing:
194 REGISTRATION_RELEASE_LOG_IF_ALLOWED("updateStateFromServer: Setting registration %llu installing worker to %llu", identifier().toUInt64(), serviceWorker ? serviceWorker->identifier().toUInt64() : 0);
195 m_installingWorker = WTFMove(serviceWorker);
196 break;
197 case ServiceWorkerRegistrationState::Waiting:
198 REGISTRATION_RELEASE_LOG_IF_ALLOWED("updateStateFromServer: Setting registration %llu waiting worker to %llu", identifier().toUInt64(), serviceWorker ? serviceWorker->identifier().toUInt64() : 0);
199 m_waitingWorker = WTFMove(serviceWorker);
200 break;
201 case ServiceWorkerRegistrationState::Active:
202 REGISTRATION_RELEASE_LOG_IF_ALLOWED("updateStateFromServer: Setting registration %llu active worker to %llu", identifier().toUInt64(), serviceWorker ? serviceWorker->identifier().toUInt64() : 0);
203 m_activeWorker = WTFMove(serviceWorker);
204 break;
205 }
206 updatePendingActivityForEventDispatch();
207}
208
209void ServiceWorkerRegistration::fireUpdateFoundEvent()
210{
211 if (m_isStopped)
212 return;
213
214 REGISTRATION_RELEASE_LOG_IF_ALLOWED("fireUpdateFoundEvent: Firing updatefound event for registration %llu", identifier().toUInt64());
215
216 ASSERT(m_pendingActivityForEventDispatch);
217 dispatchEvent(Event::create(eventNames().updatefoundEvent, Event::CanBubble::No, Event::IsCancelable::No));
218}
219
220EventTargetInterface ServiceWorkerRegistration::eventTargetInterface() const
221{
222 return ServiceWorkerRegistrationEventTargetInterfaceType;
223}
224
225ScriptExecutionContext* ServiceWorkerRegistration::scriptExecutionContext() const
226{
227 return ActiveDOMObject::scriptExecutionContext();
228}
229
230const char* ServiceWorkerRegistration::activeDOMObjectName() const
231{
232 return "ServiceWorkerRegistration";
233}
234
235bool ServiceWorkerRegistration::canSuspendForDocumentSuspension() const
236{
237 // FIXME: We should do better as this prevents a page from entering PageCache when there is a service worker registration.
238 return !hasPendingActivity();
239}
240
241void ServiceWorkerRegistration::stop()
242{
243 m_isStopped = true;
244 removeAllEventListeners();
245 updatePendingActivityForEventDispatch();
246}
247
248void ServiceWorkerRegistration::updatePendingActivityForEventDispatch()
249{
250 // If a registration has no ServiceWorker, then it has been cleared on server-side.
251 if (m_isStopped || !getNewestWorker()) {
252 m_pendingActivityForEventDispatch = nullptr;
253 return;
254 }
255 if (m_pendingActivityForEventDispatch)
256 return;
257 m_pendingActivityForEventDispatch = makePendingActivity(*this);
258}
259
260} // namespace WebCore
261
262#endif // ENABLE(SERVICE_WORKER)
263