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 "ServiceWorkerContainer.h"
28
29#if ENABLE(SERVICE_WORKER)
30
31#include "Document.h"
32#include "Event.h"
33#include "EventNames.h"
34#include "Exception.h"
35#include "IDLTypes.h"
36#include "JSDOMPromiseDeferred.h"
37#include "JSServiceWorkerRegistration.h"
38#include "Logging.h"
39#include "MessageEvent.h"
40#include "NavigatorBase.h"
41#include "ResourceError.h"
42#include "SchemeRegistry.h"
43#include "ScriptExecutionContext.h"
44#include "SecurityOrigin.h"
45#include "ServiceWorker.h"
46#include "ServiceWorkerFetchResult.h"
47#include "ServiceWorkerGlobalScope.h"
48#include "ServiceWorkerJob.h"
49#include "ServiceWorkerJobData.h"
50#include "ServiceWorkerProvider.h"
51#include "ServiceWorkerThread.h"
52#include <wtf/IsoMallocInlines.h>
53#include <wtf/RunLoop.h>
54#include <wtf/Scope.h>
55#include <wtf/URL.h>
56
57#define CONTAINER_RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), ServiceWorker, "%p - ServiceWorkerContainer::" fmt, this, ##__VA_ARGS__)
58#define CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED(fmt, ...) RELEASE_LOG_ERROR_IF(isAlwaysOnLoggingAllowed(), ServiceWorker, "%p - ServiceWorkerContainer::" fmt, this, ##__VA_ARGS__)
59
60namespace WebCore {
61
62WTF_MAKE_ISO_ALLOCATED_IMPL(ServiceWorkerContainer);
63
64ServiceWorkerContainer::ServiceWorkerContainer(ScriptExecutionContext* context, NavigatorBase& navigator)
65 : ActiveDOMObject(context)
66 , m_navigator(navigator)
67{
68 suspendIfNeeded();
69}
70
71ServiceWorkerContainer::~ServiceWorkerContainer()
72{
73#ifndef NDEBUG
74 ASSERT(m_creationThread.ptr() == &Thread::current());
75#endif
76}
77
78void ServiceWorkerContainer::refEventTarget()
79{
80 m_navigator.ref();
81}
82
83void ServiceWorkerContainer::derefEventTarget()
84{
85 m_navigator.deref();
86}
87
88auto ServiceWorkerContainer::ready() -> ReadyPromise&
89{
90 if (!m_readyPromise) {
91 m_readyPromise = std::make_unique<ReadyPromise>();
92
93 if (m_isStopped || !scriptExecutionContext()->sessionID().isValid())
94 return *m_readyPromise;
95
96 auto& context = *scriptExecutionContext();
97 auto contextIdentifier = this->contextIdentifier();
98 callOnMainThread([connection = makeRef(ensureSWClientConnection()), topOrigin = context.topOrigin().isolatedCopy(), clientURL = context.url().isolatedCopy(), contextIdentifier]() mutable {
99 connection->whenRegistrationReady(topOrigin, clientURL, [contextIdentifier](auto&& registrationData) {
100 ScriptExecutionContext::postTaskTo(contextIdentifier, [registrationData = crossThreadCopy(registrationData)](auto& context) mutable {
101 auto* serviceWorkerContainer = context.serviceWorkerContainer();
102 if (!serviceWorkerContainer)
103 return;
104 if (serviceWorkerContainer->m_isStopped || !context.sessionID().isValid())
105 return;
106
107 auto registration = ServiceWorkerRegistration::getOrCreate(context, *serviceWorkerContainer, WTFMove(registrationData));
108 serviceWorkerContainer->m_readyPromise->resolve(WTFMove(registration));
109 });
110 });
111 });
112 }
113 return *m_readyPromise;
114}
115
116ServiceWorker* ServiceWorkerContainer::controller() const
117{
118 auto* context = scriptExecutionContext();
119 ASSERT_WITH_MESSAGE(!context || is<Document>(*context) || !context->activeServiceWorker(), "Only documents can have a controller at the moment.");
120 return context ? context->activeServiceWorker() : nullptr;
121}
122
123void ServiceWorkerContainer::addRegistration(const String& relativeScriptURL, const RegistrationOptions& options, Ref<DeferredPromise>&& promise)
124{
125 auto* context = scriptExecutionContext();
126 if (m_isStopped || !context->sessionID().isValid()) {
127 promise->reject(Exception(InvalidStateError));
128 return;
129 }
130
131 if (relativeScriptURL.isEmpty()) {
132 promise->reject(Exception { TypeError, "serviceWorker.register() cannot be called with an empty script URL"_s });
133 return;
134 }
135
136 ServiceWorkerJobData jobData(ensureSWClientConnection().serverConnectionIdentifier(), contextIdentifier());
137
138 jobData.scriptURL = context->completeURL(relativeScriptURL);
139 if (!jobData.scriptURL.isValid()) {
140 CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: Invalid scriptURL");
141 promise->reject(Exception { TypeError, "serviceWorker.register() must be called with a valid relative script URL"_s });
142 return;
143 }
144
145 if (!SchemeRegistry::canServiceWorkersHandleURLScheme(jobData.scriptURL.protocol().toStringWithoutCopying())) {
146 CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: Invalid scriptURL scheme is not HTTP or HTTPS");
147 promise->reject(Exception { TypeError, "serviceWorker.register() must be called with a script URL whose protocol is either HTTP or HTTPS"_s });
148 return;
149 }
150
151 String path = jobData.scriptURL.path();
152 if (path.containsIgnoringASCIICase("%2f") || path.containsIgnoringASCIICase("%5c")) {
153 CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: scriptURL contains invalid character");
154 promise->reject(Exception { TypeError, "serviceWorker.register() must be called with a script URL whose path does not contain '%2f' or '%5c'"_s });
155 return;
156 }
157
158 if (!options.scope.isEmpty())
159 jobData.scopeURL = context->completeURL(options.scope);
160 else
161 jobData.scopeURL = URL(jobData.scriptURL, "./");
162
163 if (!jobData.scopeURL.isNull() && !SchemeRegistry::canServiceWorkersHandleURLScheme(jobData.scopeURL.protocol().toStringWithoutCopying())) {
164 CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: scopeURL scheme is not HTTP or HTTPS");
165 promise->reject(Exception { TypeError, "Scope URL provided to serviceWorker.register() must be either HTTP or HTTPS"_s });
166 return;
167 }
168
169 path = jobData.scopeURL.path();
170 if (path.containsIgnoringASCIICase("%2f") || path.containsIgnoringASCIICase("%5c")) {
171 CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: scopeURL contains invalid character");
172 promise->reject(Exception { TypeError, "Scope URL provided to serviceWorker.register() cannot have a path that contains '%2f' or '%5c'"_s });
173 return;
174 }
175
176 CONTAINER_RELEASE_LOG_IF_ALLOWED("addRegistration: Registering service worker. Job ID: %" PRIu64, jobData.identifier().jobIdentifier.toUInt64());
177
178 jobData.clientCreationURL = context->url();
179 jobData.topOrigin = context->topOrigin().data();
180 jobData.type = ServiceWorkerJobType::Register;
181 jobData.registrationOptions = options;
182
183 scheduleJob(std::make_unique<ServiceWorkerJob>(*this, WTFMove(promise), WTFMove(jobData)));
184}
185
186void ServiceWorkerContainer::removeRegistration(const URL& scopeURL, Ref<DeferredPromise>&& promise)
187{
188 auto* context = scriptExecutionContext();
189 if (!context || !context->sessionID().isValid()) {
190 ASSERT_NOT_REACHED();
191 promise->reject(Exception(InvalidStateError));
192 return;
193 }
194
195 if (!m_swConnection) {
196 ASSERT_NOT_REACHED();
197 promise->reject(Exception(InvalidStateError));
198 return;
199 }
200
201 ServiceWorkerJobData jobData(m_swConnection->serverConnectionIdentifier(), contextIdentifier());
202 jobData.clientCreationURL = context->url();
203 jobData.topOrigin = context->topOrigin().data();
204 jobData.type = ServiceWorkerJobType::Unregister;
205 jobData.scopeURL = scopeURL;
206
207 CONTAINER_RELEASE_LOG_IF_ALLOWED("removeRegistration: Unregistering service worker. Job ID: %" PRIu64, jobData.identifier().jobIdentifier.toUInt64());
208
209 scheduleJob(std::make_unique<ServiceWorkerJob>(*this, WTFMove(promise), WTFMove(jobData)));
210}
211
212void ServiceWorkerContainer::updateRegistration(const URL& scopeURL, const URL& scriptURL, WorkerType, RefPtr<DeferredPromise>&& promise)
213{
214 ASSERT(!m_isStopped);
215
216 auto& context = *scriptExecutionContext();
217 ASSERT(context.sessionID().isValid());
218
219 if (!m_swConnection) {
220 ASSERT_NOT_REACHED();
221 if (promise)
222 promise->reject(Exception(InvalidStateError));
223 return;
224 }
225
226 ServiceWorkerJobData jobData(m_swConnection->serverConnectionIdentifier(), contextIdentifier());
227 jobData.clientCreationURL = context.url();
228 jobData.topOrigin = context.topOrigin().data();
229 jobData.type = ServiceWorkerJobType::Update;
230 jobData.scopeURL = scopeURL;
231 jobData.scriptURL = scriptURL;
232
233 CONTAINER_RELEASE_LOG_IF_ALLOWED("removeRegistration: Updating service worker. Job ID: %" PRIu64, jobData.identifier().jobIdentifier.toUInt64());
234
235 scheduleJob(std::make_unique<ServiceWorkerJob>(*this, WTFMove(promise), WTFMove(jobData)));
236}
237
238void ServiceWorkerContainer::scheduleJob(std::unique_ptr<ServiceWorkerJob>&& job)
239{
240#ifndef NDEBUG
241 ASSERT(m_creationThread.ptr() == &Thread::current());
242#endif
243
244 ASSERT(m_swConnection);
245 ASSERT(!isStopped());
246
247 auto& jobData = job->data();
248 auto jobIdentifier = job->identifier();
249 ASSERT(!m_jobMap.contains(jobIdentifier));
250 m_jobMap.add(jobIdentifier, OngoingJob { WTFMove(job), makePendingActivity(*this) });
251
252 callOnMainThread([connection = m_swConnection, contextIdentifier = this->contextIdentifier(), jobData = jobData.isolatedCopy()] {
253 connection->scheduleJob(contextIdentifier, jobData);
254 });
255}
256
257void ServiceWorkerContainer::getRegistration(const String& clientURL, Ref<DeferredPromise>&& promise)
258{
259 auto* context = scriptExecutionContext();
260 if (m_isStopped || !context->sessionID().isValid()) {
261 promise->reject(Exception { InvalidStateError });
262 return;
263 }
264
265 URL parsedURL = context->completeURL(clientURL);
266 if (!protocolHostAndPortAreEqual(parsedURL, context->url())) {
267 promise->reject(Exception { SecurityError, "Origin of clientURL is not client's origin"_s });
268 return;
269 }
270
271 uint64_t pendingPromiseIdentifier = ++m_lastPendingPromiseIdentifier;
272 auto pendingPromise = std::make_unique<PendingPromise>(WTFMove(promise), makePendingActivity(*this));
273 m_pendingPromises.add(pendingPromiseIdentifier, WTFMove(pendingPromise));
274
275 auto contextIdentifier = this->contextIdentifier();
276 callOnMainThread([connection = makeRef(ensureSWClientConnection()), topOrigin = context->topOrigin().data().isolatedCopy(), parsedURL = parsedURL.isolatedCopy(), contextIdentifier, pendingPromiseIdentifier]() mutable {
277 connection->matchRegistration(WTFMove(topOrigin), parsedURL, [contextIdentifier, pendingPromiseIdentifier] (auto&& result) mutable {
278 ScriptExecutionContext::postTaskTo(contextIdentifier, [pendingPromiseIdentifier, result = crossThreadCopy(result)](auto& context) mutable {
279 auto* serviceWorkerContainer = context.serviceWorkerContainer();
280 if (!serviceWorkerContainer)
281 return;
282 if (serviceWorkerContainer->m_isStopped || !context.sessionID().isValid())
283 return;
284
285 serviceWorkerContainer->didFinishGetRegistrationRequest(pendingPromiseIdentifier, WTFMove(result));
286 });
287 });
288 });
289}
290
291void ServiceWorkerContainer::didFinishGetRegistrationRequest(uint64_t pendingPromiseIdentifier, Optional<ServiceWorkerRegistrationData>&& result)
292{
293#ifndef NDEBUG
294 ASSERT(m_creationThread.ptr() == &Thread::current());
295#endif
296
297 auto pendingPromise = m_pendingPromises.take(pendingPromiseIdentifier);
298 if (!pendingPromise)
299 return;
300
301 if (m_isStopped || !scriptExecutionContext()->sessionID().isValid()) {
302 pendingPromise->promise->reject(Exception { InvalidStateError });
303 return;
304 }
305
306 if (!result) {
307 pendingPromise->promise->resolve();
308 return;
309 }
310
311 auto registration = ServiceWorkerRegistration::getOrCreate(*scriptExecutionContext(), *this, WTFMove(result.value()));
312 pendingPromise->promise->resolve<IDLInterface<ServiceWorkerRegistration>>(WTFMove(registration));
313}
314
315void ServiceWorkerContainer::updateRegistrationState(ServiceWorkerRegistrationIdentifier identifier, ServiceWorkerRegistrationState state, const Optional<ServiceWorkerData>& serviceWorkerData)
316{
317 if (m_isStopped)
318 return;
319
320 RefPtr<ServiceWorker> serviceWorker;
321 if (serviceWorkerData)
322 serviceWorker = ServiceWorker::getOrCreate(*scriptExecutionContext(), ServiceWorkerData { *serviceWorkerData });
323
324 if (auto* registration = m_registrations.get(identifier))
325 registration->updateStateFromServer(state, WTFMove(serviceWorker));
326}
327
328void ServiceWorkerContainer::getRegistrations(Ref<DeferredPromise>&& promise)
329{
330 auto* context = scriptExecutionContext();
331 if (m_isStopped || !context->sessionID().isValid()) {
332 promise->reject(Exception { InvalidStateError });
333 return;
334 }
335
336 uint64_t pendingPromiseIdentifier = ++m_lastPendingPromiseIdentifier;
337 auto pendingPromise = std::make_unique<PendingPromise>(WTFMove(promise), makePendingActivity(*this));
338 m_pendingPromises.add(pendingPromiseIdentifier, WTFMove(pendingPromise));
339
340 auto contextIdentifier = this->contextIdentifier();
341 auto contextURL = context->url();
342 callOnMainThread([connection = makeRef(ensureSWClientConnection()), topOrigin = context->topOrigin().data().isolatedCopy(), contextURL = contextURL.isolatedCopy(), contextIdentifier, pendingPromiseIdentifier]() mutable {
343 connection->getRegistrations(WTFMove(topOrigin), contextURL, [contextIdentifier, pendingPromiseIdentifier] (auto&& registrationDatas) mutable {
344 ScriptExecutionContext::postTaskTo(contextIdentifier, [pendingPromiseIdentifier, registrationDatas = crossThreadCopy(registrationDatas)](auto& context) mutable {
345 auto* serviceWorkerContainer = context.serviceWorkerContainer();
346 if (!serviceWorkerContainer)
347 return;
348 if (serviceWorkerContainer->m_isStopped || !context.sessionID().isValid())
349 return;
350
351 serviceWorkerContainer->didFinishGetRegistrationsRequest(pendingPromiseIdentifier, WTFMove(registrationDatas));
352 });
353 });
354 });
355}
356
357void ServiceWorkerContainer::didFinishGetRegistrationsRequest(uint64_t pendingPromiseIdentifier, Vector<ServiceWorkerRegistrationData>&& registrationDatas)
358{
359#ifndef NDEBUG
360 ASSERT(m_creationThread.ptr() == &Thread::current());
361#endif
362
363 auto pendingPromise = m_pendingPromises.take(pendingPromiseIdentifier);
364 if (!pendingPromise)
365 return;
366
367 if (m_isStopped || !scriptExecutionContext()->sessionID().isValid()) {
368 pendingPromise->promise->reject(Exception { InvalidStateError });
369 return;
370 }
371
372 auto registrations = WTF::map(WTFMove(registrationDatas), [&] (auto&& registrationData) {
373 return ServiceWorkerRegistration::getOrCreate(*this->scriptExecutionContext(), *this, WTFMove(registrationData));
374 });
375
376 pendingPromise->promise->resolve<IDLSequence<IDLInterface<ServiceWorkerRegistration>>>(WTFMove(registrations));
377}
378
379void ServiceWorkerContainer::startMessages()
380{
381}
382
383void ServiceWorkerContainer::jobFailedWithException(ServiceWorkerJob& job, const Exception& exception)
384{
385#ifndef NDEBUG
386 ASSERT(m_creationThread.ptr() == &Thread::current());
387#endif
388
389 ASSERT_WITH_MESSAGE(job.hasPromise() || job.data().type == ServiceWorkerJobType::Update, "Only soft updates have no promise");
390
391 auto guard = WTF::makeScopeExit([this, &job] {
392 destroyJob(job);
393 });
394
395 CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("jobFailedWithException: Job %" PRIu64 " failed with error %s", job.identifier().toUInt64(), exception.message().utf8().data());
396
397 auto promise = job.takePromise();
398 if (!promise)
399 return;
400
401 if (auto* context = scriptExecutionContext()) {
402 context->postTask([promise = WTFMove(promise), exception](auto&) mutable {
403 promise->reject(exception);
404 });
405 }
406}
407
408void ServiceWorkerContainer::fireUpdateFoundEvent(ServiceWorkerRegistrationIdentifier identifier)
409{
410#ifndef NDEBUG
411 ASSERT(m_creationThread.ptr() == &Thread::current());
412#endif
413
414 if (auto* registration = m_registrations.get(identifier))
415 registration->fireUpdateFoundEvent();
416}
417
418void ServiceWorkerContainer::jobResolvedWithRegistration(ServiceWorkerJob& job, ServiceWorkerRegistrationData&& data, ShouldNotifyWhenResolved shouldNotifyWhenResolved)
419{
420#ifndef NDEBUG
421 ASSERT(m_creationThread.ptr() == &Thread::current());
422#endif
423 ASSERT_WITH_MESSAGE(job.hasPromise() || job.data().type == ServiceWorkerJobType::Update, "Only soft updates have no promise");
424
425 if (job.data().type == ServiceWorkerJobType::Register)
426 CONTAINER_RELEASE_LOG_IF_ALLOWED("jobResolvedWithRegistration: Registration job %" PRIu64 " succeeded", job.identifier().toUInt64());
427 else {
428 ASSERT(job.data().type == ServiceWorkerJobType::Update);
429 CONTAINER_RELEASE_LOG_IF_ALLOWED("jobResolvedWithRegistration: Update job %" PRIu64 " succeeded", job.identifier().toUInt64());
430 }
431
432 auto guard = WTF::makeScopeExit([this, &job] {
433 destroyJob(job);
434 });
435
436 auto notifyIfExitEarly = WTF::makeScopeExit([this, &data, &shouldNotifyWhenResolved] {
437 if (shouldNotifyWhenResolved == ShouldNotifyWhenResolved::Yes)
438 notifyRegistrationIsSettled(data.key);
439 });
440
441 if (isStopped())
442 return;
443
444 auto promise = job.takePromise();
445 if (!promise)
446 return;
447
448 notifyIfExitEarly.release();
449
450 scriptExecutionContext()->postTask([this, protectedThis = RefPtr<ServiceWorkerContainer>(this), promise = WTFMove(promise), jobIdentifier = job.identifier(), data = WTFMove(data), shouldNotifyWhenResolved](ScriptExecutionContext& context) mutable {
451 if (isStopped() || !context.sessionID().isValid()) {
452 if (shouldNotifyWhenResolved == ShouldNotifyWhenResolved::Yes)
453 notifyRegistrationIsSettled(data.key);
454 return;
455 }
456
457 auto registration = ServiceWorkerRegistration::getOrCreate(context, *this, WTFMove(data));
458
459 CONTAINER_RELEASE_LOG_IF_ALLOWED("jobResolvedWithRegistration: Resolving promise for job %" PRIu64 ". Registration ID: %" PRIu64, jobIdentifier.toUInt64(), registration->identifier().toUInt64());
460
461 if (shouldNotifyWhenResolved == ShouldNotifyWhenResolved::Yes) {
462 m_ongoingSettledRegistrations.add(++m_lastOngoingSettledRegistrationIdentifier, registration->data().key);
463 promise->whenSettled([this, protectedThis = WTFMove(protectedThis), identifier = m_lastOngoingSettledRegistrationIdentifier] {
464 notifyRegistrationIsSettled(m_ongoingSettledRegistrations.take(identifier));
465 });
466 }
467
468 promise->resolve<IDLInterface<ServiceWorkerRegistration>>(WTFMove(registration));
469 });
470}
471
472void ServiceWorkerContainer::postMessage(MessageWithMessagePorts&& message, ServiceWorkerData&& sourceData, String&& sourceOrigin)
473{
474 auto& context = *scriptExecutionContext();
475 MessageEventSource source = RefPtr<ServiceWorker> { ServiceWorker::getOrCreate(context, WTFMove(sourceData)) };
476
477 auto messageEvent = MessageEvent::create(MessagePort::entanglePorts(context, WTFMove(message.transferredPorts)), message.message.releaseNonNull(), sourceOrigin, { }, WTFMove(source));
478 dispatchEvent(messageEvent);
479}
480
481void ServiceWorkerContainer::notifyRegistrationIsSettled(const ServiceWorkerRegistrationKey& registrationKey)
482{
483 callOnMainThread([connection = m_swConnection, registrationKey = registrationKey.isolatedCopy()] {
484 connection->didResolveRegistrationPromise(registrationKey);
485 });
486}
487
488void ServiceWorkerContainer::jobResolvedWithUnregistrationResult(ServiceWorkerJob& job, bool unregistrationResult)
489{
490#ifndef NDEBUG
491 ASSERT(m_creationThread.ptr() == &Thread::current());
492#endif
493
494 ASSERT(job.hasPromise());
495
496 auto guard = WTF::makeScopeExit([this, &job] {
497 destroyJob(job);
498 });
499
500 CONTAINER_RELEASE_LOG_IF_ALLOWED("jobResolvedWithUnregistrationResult: Unregister job %" PRIu64 " finished. Success? %d", job.identifier().toUInt64(), unregistrationResult);
501
502 auto* context = scriptExecutionContext();
503 if (!context) {
504 LOG_ERROR("ServiceWorkerContainer::jobResolvedWithUnregistrationResult called but the containers ScriptExecutionContext is gone");
505 return;
506 }
507
508 context->postTask([promise = job.takePromise(), unregistrationResult](auto&) mutable {
509 promise->resolve<IDLBoolean>(unregistrationResult);
510 });
511}
512
513void ServiceWorkerContainer::startScriptFetchForJob(ServiceWorkerJob& job, FetchOptions::Cache cachePolicy)
514{
515#ifndef NDEBUG
516 ASSERT(m_creationThread.ptr() == &Thread::current());
517#endif
518
519 CONTAINER_RELEASE_LOG_IF_ALLOWED("startScriptFetchForJob: Starting script fetch for job %" PRIu64, job.identifier().toUInt64());
520
521 auto* context = scriptExecutionContext();
522 if (!context) {
523 LOG_ERROR("ServiceWorkerContainer::jobResolvedWithRegistration called but the container's ScriptExecutionContext is gone");
524 notifyFailedFetchingScript(job, { errorDomainWebKitInternal, 0, job.data().scriptURL, "Attempt to fetch service worker script with no ScriptExecutionContext"_s });
525 destroyJob(job);
526 return;
527 }
528
529 job.fetchScriptWithContext(*context, cachePolicy);
530}
531
532void ServiceWorkerContainer::jobFinishedLoadingScript(ServiceWorkerJob& job, const String& script, const ContentSecurityPolicyResponseHeaders& contentSecurityPolicy, const String& referrerPolicy)
533{
534#ifndef NDEBUG
535 ASSERT(m_creationThread.ptr() == &Thread::current());
536#endif
537
538 CONTAINER_RELEASE_LOG_IF_ALLOWED("jobFinishedLoadingScript: Successfuly finished fetching script for job %" PRIu64, job.identifier().toUInt64());
539
540 callOnMainThread([connection = m_swConnection, jobDataIdentifier = job.data().identifier(), registrationKey = job.data().registrationKey().isolatedCopy(), script = script.isolatedCopy(), contentSecurityPolicy = contentSecurityPolicy.isolatedCopy(), referrerPolicy = referrerPolicy.isolatedCopy()] {
541 connection->finishFetchingScriptInServer({ jobDataIdentifier, registrationKey, script, contentSecurityPolicy, referrerPolicy, { } });
542 });
543}
544
545void ServiceWorkerContainer::jobFailedLoadingScript(ServiceWorkerJob& job, const ResourceError& error, Exception&& exception)
546{
547#ifndef NDEBUG
548 ASSERT(m_creationThread.ptr() == &Thread::current());
549#endif
550 ASSERT_WITH_MESSAGE(job.hasPromise() || job.data().type == ServiceWorkerJobType::Update, "Only soft updates have no promise");
551
552 CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("jobFinishedLoadingScript: Failed to fetch script for job %" PRIu64 ", error: %s", job.identifier().toUInt64(), error.localizedDescription().utf8().data());
553
554 if (auto promise = job.takePromise())
555 promise->reject(WTFMove(exception));
556
557 notifyFailedFetchingScript(job, error);
558 destroyJob(job);
559}
560
561void ServiceWorkerContainer::notifyFailedFetchingScript(ServiceWorkerJob& job, const ResourceError& error)
562{
563 callOnMainThread([connection = m_swConnection, jobIdentifier = job.identifier(), registrationKey = job.data().registrationKey().isolatedCopy(), error = error.isolatedCopy()] {
564 connection->failedFetchingScript(jobIdentifier, registrationKey, error);
565 });
566}
567
568void ServiceWorkerContainer::destroyJob(ServiceWorkerJob& job)
569{
570#ifndef NDEBUG
571 ASSERT(m_creationThread.ptr() == &Thread::current());
572#endif
573
574 ASSERT(m_jobMap.contains(job.identifier()));
575 m_jobMap.remove(job.identifier());
576}
577
578SWServerConnectionIdentifier ServiceWorkerContainer::connectionIdentifier()
579{
580 ASSERT(m_swConnection);
581 return m_swConnection->serverConnectionIdentifier();
582}
583
584const char* ServiceWorkerContainer::activeDOMObjectName() const
585{
586 return "ServiceWorkerContainer";
587}
588
589bool ServiceWorkerContainer::canSuspendForDocumentSuspension() const
590{
591 return !hasPendingActivity();
592}
593
594SWClientConnection& ServiceWorkerContainer::ensureSWClientConnection()
595{
596 ASSERT(scriptExecutionContext());
597 ASSERT(scriptExecutionContext()->sessionID().isValid());
598 if (!m_swConnection) {
599 ASSERT(scriptExecutionContext());
600 callOnMainThreadAndWait([this, sessionID = scriptExecutionContext()->sessionID()]() {
601 m_swConnection = &ServiceWorkerProvider::singleton().serviceWorkerConnectionForSession(sessionID);
602 });
603 }
604 return *m_swConnection;
605}
606
607void ServiceWorkerContainer::addRegistration(ServiceWorkerRegistration& registration)
608{
609#ifndef NDEBUG
610 ASSERT(m_creationThread.ptr() == &Thread::current());
611#endif
612
613 ensureSWClientConnection().addServiceWorkerRegistrationInServer(registration.identifier());
614 m_registrations.add(registration.identifier(), &registration);
615}
616
617void ServiceWorkerContainer::removeRegistration(ServiceWorkerRegistration& registration)
618{
619#ifndef NDEBUG
620 ASSERT(m_creationThread.ptr() == &Thread::current());
621#endif
622
623 m_swConnection->removeServiceWorkerRegistrationInServer(registration.identifier());
624 m_registrations.remove(registration.identifier());
625}
626
627void ServiceWorkerContainer::fireControllerChangeEvent()
628{
629#ifndef NDEBUG
630 ASSERT(m_creationThread.ptr() == &Thread::current());
631#endif
632
633 if (m_isStopped)
634 return;
635
636 dispatchEvent(Event::create(eventNames().controllerchangeEvent, Event::CanBubble::No, Event::IsCancelable::No));
637}
638
639void ServiceWorkerContainer::stop()
640{
641 m_isStopped = true;
642 removeAllEventListeners();
643 m_pendingPromises.clear();
644 m_readyPromise = nullptr;
645 auto jobMap = WTFMove(m_jobMap);
646 for (auto& ongoingJob : jobMap.values()) {
647 if (ongoingJob.job->cancelPendingLoad())
648 notifyFailedFetchingScript(*ongoingJob.job.get(), ResourceError { errorDomainWebKitInternal, 0, ongoingJob.job->data().scriptURL, "Job cancelled"_s, ResourceError::Type::Cancellation });
649 }
650
651 auto registrationMap = WTFMove(m_ongoingSettledRegistrations);
652 for (auto& registration : registrationMap.values())
653 notifyRegistrationIsSettled(registration);
654}
655
656DocumentOrWorkerIdentifier ServiceWorkerContainer::contextIdentifier()
657{
658#ifndef NDEBUG
659 ASSERT(m_creationThread.ptr() == &Thread::current());
660#endif
661
662 ASSERT(scriptExecutionContext());
663 if (is<ServiceWorkerGlobalScope>(*scriptExecutionContext()))
664 return downcast<ServiceWorkerGlobalScope>(*scriptExecutionContext()).thread().identifier();
665 return downcast<Document>(*scriptExecutionContext()).identifier();
666}
667
668ServiceWorkerJob* ServiceWorkerContainer::job(ServiceWorkerJobIdentifier identifier)
669{
670 auto iterator = m_jobMap.find(identifier);
671 if (iterator == m_jobMap.end())
672 return nullptr;
673 return iterator->value.job.get();
674}
675
676bool ServiceWorkerContainer::isAlwaysOnLoggingAllowed() const
677{
678 auto* context = scriptExecutionContext();
679 if (!context)
680 return false;
681
682 if (is<Document>(*context))
683 return downcast<Document>(*context).sessionID().isAlwaysOnLoggingAllowed();
684
685 // FIXME: No logging inside service workers for now.
686 return false;
687}
688
689} // namespace WebCore
690
691#endif // ENABLE(SERVICE_WORKER)
692