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