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