15d0fecc73f3f591dbf9c20f071a5acc4da10614
[WebKit.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     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 == currentThread());
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     // FIXME: Add support in workers.
222     if (!is<Document>(*context)) {
223         promise->reject(Exception { NotSupportedError, ASCIILiteral("serviceWorker.getRegistration() is not yet supported in workers") });
224         return;
225     }
226
227     URL parsedURL = context->completeURL(clientURL);
228     if (!protocolHostAndPortAreEqual(parsedURL, context->url())) {
229         promise->reject(Exception { SecurityError, ASCIILiteral("Origin of clientURL is not client's origin") });
230         return;
231     }
232
233     return ensureSWClientConnection().matchRegistration(context->topOrigin(), parsedURL, [promise = WTFMove(promise), protectingThis = makePendingActivity(*this), this] (auto&& result) mutable {
234         if (m_isStopped)
235             return;
236
237         if (!result) {
238             promise->resolve();
239             return;
240         }
241
242         auto registration = ServiceWorkerRegistration::getOrCreate(*scriptExecutionContext(), *this, WTFMove(result.value()));
243         promise->resolve<IDLInterface<ServiceWorkerRegistration>>(WTFMove(registration));
244     });
245 }
246
247 void ServiceWorkerContainer::scheduleTaskToUpdateRegistrationState(ServiceWorkerRegistrationIdentifier identifier, ServiceWorkerRegistrationState state, const std::optional<ServiceWorkerData>& serviceWorkerData)
248 {
249     auto* context = scriptExecutionContext();
250     if (!context)
251         return;
252
253     RefPtr<ServiceWorker> serviceWorker;
254     if (serviceWorkerData)
255         serviceWorker = ServiceWorker::getOrCreate(*context, ServiceWorkerData { *serviceWorkerData });
256
257     context->postTask([this, protectedThis = makeRef(*this), identifier, state, serviceWorker = WTFMove(serviceWorker)](ScriptExecutionContext&) mutable {
258         if (auto* registration = m_registrations.get(identifier))
259             registration->updateStateFromServer(state, WTFMove(serviceWorker));
260     });
261 }
262
263 void ServiceWorkerContainer::getRegistrations(RegistrationsPromise&& promise)
264 {
265     auto* context = scriptExecutionContext();
266     if (!context) {
267         promise.reject(Exception { InvalidStateError });
268         return;
269     }
270
271     // FIXME: Add support in workers.
272     if (!is<Document>(*context)) {
273         promise.reject(Exception { NotSupportedError, ASCIILiteral("serviceWorker.getRegistrations() is not yet supported in workers") });
274         return;
275     }
276
277     return ensureSWClientConnection().getRegistrations(context->topOrigin(), context->url(), [this, pendingActivity = makePendingActivity(*this), promise = WTFMove(promise)] (auto&& registrationDatas) mutable {
278         if (m_isStopped)
279             return;
280
281         Vector<Ref<ServiceWorkerRegistration>> registrations;
282         registrations.reserveInitialCapacity(registrationDatas.size());
283         for (auto& registrationData : registrationDatas) {
284             auto registration = ServiceWorkerRegistration::getOrCreate(*scriptExecutionContext(), *this, WTFMove(registrationData));
285             registrations.uncheckedAppend(WTFMove(registration));
286         }
287
288         promise.resolve(WTFMove(registrations));
289     });
290 }
291
292 void ServiceWorkerContainer::startMessages()
293 {
294 }
295
296 void ServiceWorkerContainer::jobFailedWithException(ServiceWorkerJob& job, const Exception& exception)
297 {
298 #ifndef NDEBUG
299     ASSERT(m_creationThread == currentThread());
300 #endif
301
302     if (auto* context = scriptExecutionContext()) {
303         context->postTask([job = makeRef(job), exception](ScriptExecutionContext&) {
304             job->promise().reject(exception);
305         });
306     }
307     jobDidFinish(job);
308 }
309
310 void ServiceWorkerContainer::scheduleTaskToFireUpdateFoundEvent(ServiceWorkerRegistrationIdentifier identifier)
311 {
312 #ifndef NDEBUG
313     ASSERT(m_creationThread == currentThread());
314 #endif
315
316     if (auto* registration = m_registrations.get(identifier))
317         registration->scheduleTaskToFireUpdateFoundEvent();
318 }
319
320 void ServiceWorkerContainer::jobResolvedWithRegistration(ServiceWorkerJob& job, ServiceWorkerRegistrationData&& data, ShouldNotifyWhenResolved shouldNotifyWhenResolved)
321 {
322 #ifndef NDEBUG
323     ASSERT(m_creationThread == currentThread());
324 #endif
325
326     auto guard = WTF::makeScopeExit([this, &job] {
327         jobDidFinish(job);
328     });
329
330     WTF::Function<void()> notifyWhenResolvedIfNeeded = [] { };
331     if (shouldNotifyWhenResolved == ShouldNotifyWhenResolved::Yes) {
332         notifyWhenResolvedIfNeeded = [connection = m_swConnection, registrationKey = data.key.isolatedCopy()]() mutable {
333             callOnMainThread([connection = WTFMove(connection), registrationKey = WTFMove(registrationKey)] {
334                 connection->didResolveRegistrationPromise(registrationKey);
335             });
336         };
337     }
338
339     if (isStopped()) {
340         notifyWhenResolvedIfNeeded();
341         return;
342     }
343
344     scriptExecutionContext()->postTask([this, protectedThis = makeRef(*this), job = makeRef(job), data = WTFMove(data), notifyWhenResolvedIfNeeded = WTFMove(notifyWhenResolvedIfNeeded)](ScriptExecutionContext& context) mutable {
345         if (isStopped()) {
346             notifyWhenResolvedIfNeeded();
347             return;
348         }
349
350         auto registration = ServiceWorkerRegistration::getOrCreate(context, *this, WTFMove(data));
351
352         LOG(ServiceWorker, "Container %p resolved job with registration %p", this, registration.ptr());
353
354         job->promise().resolve<IDLInterface<ServiceWorkerRegistration>>(WTFMove(registration));
355
356         notifyWhenResolvedIfNeeded();
357     });
358 }
359
360 void ServiceWorkerContainer::jobResolvedWithUnregistrationResult(ServiceWorkerJob& job, bool unregistrationResult)
361 {
362 #ifndef NDEBUG
363     ASSERT(m_creationThread == currentThread());
364 #endif
365
366     auto guard = WTF::makeScopeExit([this, &job] {
367         jobDidFinish(job);
368     });
369
370     auto* context = scriptExecutionContext();
371     if (!context) {
372         LOG_ERROR("ServiceWorkerContainer::jobResolvedWithUnregistrationResult called but the containers ScriptExecutionContext is gone");
373         return;
374     }
375
376     context->postTask([job = makeRef(job), unregistrationResult](ScriptExecutionContext&) mutable {
377         job->promise().resolve<IDLBoolean>(unregistrationResult);
378     });
379 }
380
381 void ServiceWorkerContainer::startScriptFetchForJob(ServiceWorkerJob& job)
382 {
383 #ifndef NDEBUG
384     ASSERT(m_creationThread == currentThread());
385 #endif
386
387     LOG(ServiceWorker, "SeviceWorkerContainer %p starting script fetch for job %s", this, job.identifier().loggingString().utf8().data());
388
389     auto* context = scriptExecutionContext();
390     if (!context) {
391         LOG_ERROR("ServiceWorkerContainer::jobResolvedWithRegistration called but the container's ScriptExecutionContext is gone");
392         callOnMainThread([connection = m_swConnection, job = makeRef(job)] {
393             connection->failedFetchingScript(job, { errorDomainWebKitInternal, 0, job->data().scriptURL, ASCIILiteral("Attempt to fetch service worker script with no ScriptExecutionContext") });
394         });
395         jobDidFinish(job);
396         return;
397     }
398
399     job.fetchScriptWithContext(*context);
400 }
401
402 void ServiceWorkerContainer::jobFinishedLoadingScript(ServiceWorkerJob& job, const String& script)
403 {
404 #ifndef NDEBUG
405     ASSERT(m_creationThread == currentThread());
406 #endif
407
408     LOG(ServiceWorker, "SeviceWorkerContainer %p finished fetching script for job %s", this, job.identifier().loggingString().utf8().data());
409
410     callOnMainThread([connection = m_swConnection, job = makeRef(job), script = script.isolatedCopy()] {
411         connection->finishedFetchingScript(job, script);
412     });
413 }
414
415 void ServiceWorkerContainer::jobFailedLoadingScript(ServiceWorkerJob& job, const ResourceError& error, std::optional<Exception>&& exception)
416 {
417 #ifndef NDEBUG
418     ASSERT(m_creationThread == currentThread());
419 #endif
420
421     LOG(ServiceWorker, "SeviceWorkerContainer %p failed fetching script for job %s", this, job.identifier().loggingString().utf8().data());
422
423     if (exception)
424         job.promise().reject(*exception);
425
426     callOnMainThread([connection = m_swConnection, job = makeRef(job), error = error.isolatedCopy()] {
427         connection->failedFetchingScript(job, error);
428     });
429 }
430
431 void ServiceWorkerContainer::jobDidFinish(ServiceWorkerJob& job)
432 {
433 #ifndef NDEBUG
434     ASSERT(m_creationThread == currentThread());
435 #endif
436
437     auto taken = m_jobMap.take(job.identifier());
438     ASSERT_UNUSED(taken, !taken || taken->ptr() == &job);
439
440     unsetPendingActivity(this);
441 }
442
443 SWServerConnectionIdentifier ServiceWorkerContainer::connectionIdentifier()
444 {
445     ASSERT(m_swConnection);
446     return m_swConnection->serverConnectionIdentifier();
447 }
448
449 const char* ServiceWorkerContainer::activeDOMObjectName() const
450 {
451     return "ServiceWorkerContainer";
452 }
453
454 bool ServiceWorkerContainer::canSuspendForDocumentSuspension() const
455 {
456     return !hasPendingActivity();
457 }
458
459 SWClientConnection& ServiceWorkerContainer::ensureSWClientConnection()
460 {
461     if (!m_swConnection) {
462         ASSERT(scriptExecutionContext());
463         callOnMainThreadAndWait([this, sessionID = scriptExecutionContext()->sessionID()]() {
464             m_swConnection = &ServiceWorkerProvider::singleton().serviceWorkerConnectionForSession(sessionID);
465         });
466     }
467     return *m_swConnection;
468 }
469
470 void ServiceWorkerContainer::addRegistration(ServiceWorkerRegistration& registration)
471 {
472 #ifndef NDEBUG
473     ASSERT(m_creationThread == currentThread());
474 #endif
475
476     ensureSWClientConnection().addServiceWorkerRegistrationInServer(registration.identifier());
477     m_registrations.add(registration.identifier(), &registration);
478 }
479
480 void ServiceWorkerContainer::removeRegistration(ServiceWorkerRegistration& registration)
481 {
482 #ifndef NDEBUG
483     ASSERT(m_creationThread == currentThread());
484 #endif
485
486     m_swConnection->removeServiceWorkerRegistrationInServer(registration.identifier());
487     m_registrations.remove(registration.identifier());
488 }
489
490 void ServiceWorkerContainer::scheduleTaskToFireControllerChangeEvent()
491 {
492 #ifndef NDEBUG
493     ASSERT(m_creationThread == currentThread());
494 #endif
495
496     if (m_isStopped)
497         return;
498
499     scriptExecutionContext()->postTask([this, protectedThis = makeRef(*this)](ScriptExecutionContext&) mutable {
500         if (m_isStopped)
501             return;
502
503         dispatchEvent(Event::create(eventNames().controllerchangeEvent, false, false));
504     });
505 }
506
507 void ServiceWorkerContainer::stop()
508 {
509     m_isStopped = true;
510     removeAllEventListeners();
511 }
512
513 DocumentOrWorkerIdentifier ServiceWorkerContainer::contextIdentifier()
514 {
515 #ifndef NDEBUG
516     ASSERT(m_creationThread == currentThread());
517 #endif
518
519     ASSERT(scriptExecutionContext());
520     if (is<ServiceWorkerGlobalScope>(*scriptExecutionContext()))
521         return downcast<ServiceWorkerGlobalScope>(*scriptExecutionContext()).thread().identifier();
522     return downcast<Document>(*scriptExecutionContext()).identifier();
523 }
524
525 } // namespace WebCore
526
527 #endif // ENABLE(SERVICE_WORKER)