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