56d56c7f84255f8b29b208af89534752627ce75f
[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 "Exception.h"
32 #include "IDLTypes.h"
33 #include "JSDOMPromiseDeferred.h"
34 #include "JSServiceWorkerRegistration.h"
35 #include "Logging.h"
36 #include "NavigatorBase.h"
37 #include "ResourceError.h"
38 #include "ScopeGuard.h"
39 #include "ScriptExecutionContext.h"
40 #include "SecurityOrigin.h"
41 #include "ServiceWorker.h"
42 #include "ServiceWorkerJob.h"
43 #include "ServiceWorkerJobData.h"
44 #include "ServiceWorkerProvider.h"
45 #include "URL.h"
46 #include <wtf/RunLoop.h>
47
48 namespace WebCore {
49
50 ServiceWorkerContainer::ServiceWorkerContainer(ScriptExecutionContext& context, NavigatorBase& navigator)
51     : ActiveDOMObject(&context)
52     , m_navigator(navigator)
53 {
54     suspendIfNeeded();
55
56     m_readyPromise.reject(Exception { UnknownError, ASCIILiteral("serviceWorker.ready() is not yet implemented") });
57 }
58
59 ServiceWorkerContainer::~ServiceWorkerContainer()
60 {
61 #ifndef NDEBUG
62     ASSERT(m_creationThread == currentThread());
63 #endif
64 }
65
66 void ServiceWorkerContainer::refEventTarget()
67 {
68     m_navigator.ref();
69 }
70
71 void ServiceWorkerContainer::derefEventTarget()
72 {
73     m_navigator.deref();
74 }
75
76 ServiceWorker* ServiceWorkerContainer::controller() const
77 {
78     auto* context = scriptExecutionContext();
79     if (!context || !context->selectedServiceWorkerIdentifier()) {
80         m_controller = nullptr;
81         return nullptr;
82     }
83     if (!m_controller || m_controller->identifier() != context->selectedServiceWorkerIdentifier())
84         m_controller = ServiceWorker::create(*context, context->selectedServiceWorkerIdentifier());
85     return m_controller.get();
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         return;
94     }
95
96     if (!m_swConnection)
97         m_swConnection = &ServiceWorkerProvider::singleton().serviceWorkerConnectionForSession(context->sessionID());
98
99     if (relativeScriptURL.isEmpty()) {
100         promise->reject(Exception { TypeError, ASCIILiteral("serviceWorker.register() cannot be called with an empty script URL") });
101         return;
102     }
103
104     ServiceWorkerJobData jobData(m_swConnection->identifier());
105
106     jobData.scriptURL = context->completeURL(relativeScriptURL);
107     if (!jobData.scriptURL.isValid()) {
108         promise->reject(Exception { TypeError, ASCIILiteral("serviceWorker.register() must be called with a valid relative script URL") });
109         return;
110     }
111
112     // FIXME: The spec disallows scripts outside of HTTP(S), but we'll likely support app custom URL schemes in WebKit.
113     if (!jobData.scriptURL.protocolIsInHTTPFamily()) {
114         promise->reject(Exception { TypeError, ASCIILiteral("serviceWorker.register() must be called with a script URL whose protocol is either HTTP or HTTPS") });
115         return;
116     }
117
118     String path = jobData.scriptURL.path();
119     if (path.containsIgnoringASCIICase("%2f") || path.containsIgnoringASCIICase("%5c")) {
120         promise->reject(Exception { TypeError, ASCIILiteral("serviceWorker.register() must be called with a script URL whose path does not contain '%2f' or '%5c'") });
121         return;
122     }
123
124     String scope = options.scope.isEmpty() ? ASCIILiteral("./") : options.scope;
125     if (!scope.isEmpty())
126         jobData.scopeURL = context->completeURL(scope);
127
128     if (!jobData.scopeURL.isNull() && !jobData.scopeURL.protocolIsInHTTPFamily()) {
129         promise->reject(Exception { TypeError, ASCIILiteral("Scope URL provided to serviceWorker.register() must be either HTTP or HTTPS") });
130         return;
131     }
132
133     path = jobData.scopeURL.path();
134     if (path.containsIgnoringASCIICase("%2f") || path.containsIgnoringASCIICase("%5c")) {
135         promise->reject(Exception { TypeError, ASCIILiteral("Scope URL provided to serviceWorker.register() cannot have a path that contains '%2f' or '%5c'") });
136         return;
137     }
138
139     jobData.clientCreationURL = context->url();
140     jobData.topOrigin = SecurityOriginData::fromSecurityOrigin(context->topOrigin());
141     jobData.type = ServiceWorkerJobType::Register;
142     jobData.registrationOptions = options;
143
144     scheduleJob(ServiceWorkerJob::create(*this, WTFMove(promise), WTFMove(jobData)));
145 }
146
147 void ServiceWorkerContainer::scheduleJob(Ref<ServiceWorkerJob>&& job)
148 {
149     ASSERT(m_swConnection);
150
151     ServiceWorkerJob& rawJob = job.get();
152     auto result = m_jobMap.add(rawJob.data().identifier(), WTFMove(job));
153     ASSERT_UNUSED(result, result.isNewEntry);
154
155     m_swConnection->scheduleJob(rawJob);
156 }
157
158 void ServiceWorkerContainer::getRegistration(const String&, Ref<DeferredPromise>&& promise)
159 {
160     promise->reject(Exception { UnknownError, ASCIILiteral("serviceWorker.getRegistration() is not yet implemented") });
161 }
162
163 void ServiceWorkerContainer::getRegistrations(Ref<DeferredPromise>&& promise)
164 {
165     promise->reject(Exception { UnknownError, ASCIILiteral("serviceWorker.getRegistrations() is not yet implemented") });
166 }
167
168 void ServiceWorkerContainer::startMessages()
169 {
170 }
171
172 void ServiceWorkerContainer::jobFailedWithException(ServiceWorkerJob& job, const Exception& exception)
173 {
174     job.promise().reject(exception);
175     jobDidFinish(job);
176 }
177
178 void ServiceWorkerContainer::jobResolvedWithRegistration(ServiceWorkerJob& job, ServiceWorkerRegistrationData&& data)
179 {
180     ScopeGuard guard([this, &job] {
181         jobDidFinish(job);
182     });
183
184     auto* context = scriptExecutionContext();
185     if (!context) {
186         LOG_ERROR("ServiceWorkerContainer::jobResolvedWithRegistration called but the containers ScriptExecutionContext is gone");
187         return;
188     }
189
190     // FIXME: Implement proper selection of service workers.
191     context->setSelectedServiceWorkerIdentifier(data.identifier);
192
193     auto registration = ServiceWorkerRegistration::create(*context, WTFMove(data));
194     job.promise().resolve<IDLInterface<ServiceWorkerRegistration>>(registration.get());
195 }
196
197 void ServiceWorkerContainer::startScriptFetchForJob(ServiceWorkerJob& job)
198 {
199     LOG(ServiceWorker, "SeviceWorkerContainer %p starting script fetch for job %" PRIu64, this, job.data().identifier());
200
201     auto* context = scriptExecutionContext();
202     if (!context) {
203         LOG_ERROR("ServiceWorkerContainer::jobResolvedWithRegistration called but the container's ScriptExecutionContext is gone");
204         m_swConnection->failedFetchingScript(job, { errorDomainWebKitInternal, 0, job.data().scriptURL, ASCIILiteral("Attempt to fetch service worker script with no ScriptExecutionContext") });
205         jobDidFinish(job);
206         return;
207     }
208
209     job.fetchScriptWithContext(*context);
210 }
211
212 void ServiceWorkerContainer::jobFinishedLoadingScript(ServiceWorkerJob& job, const String& script)
213 {
214     LOG(ServiceWorker, "SeviceWorkerContainer %p finished fetching script for job %" PRIu64, this, job.data().identifier());
215
216     m_swConnection->finishedFetchingScript(job, script);
217 }
218
219 void ServiceWorkerContainer::jobFailedLoadingScript(ServiceWorkerJob& job, const ResourceError& error)
220 {
221     LOG(ServiceWorker, "SeviceWorkerContainer %p failed fetching script for job %" PRIu64, this, job.data().identifier());
222
223     m_swConnection->failedFetchingScript(job, error);
224 }
225
226 void ServiceWorkerContainer::jobDidFinish(ServiceWorkerJob& job)
227 {
228     auto taken = m_jobMap.take(job.data().identifier());
229     ASSERT_UNUSED(taken, !taken || taken.get() == &job);
230 }
231
232 uint64_t ServiceWorkerContainer::connectionIdentifier()
233 {
234     ASSERT(m_swConnection);
235     return m_swConnection->identifier();
236 }
237
238 const char* ServiceWorkerContainer::activeDOMObjectName() const
239 {
240     return "ServiceWorkerContainer";
241 }
242
243 bool ServiceWorkerContainer::canSuspendForDocumentSuspension() const
244 {
245     return true;
246 }
247
248 } // namespace WebCore
249
250 #endif // ENABLE(SERVICE_WORKER)