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