Unreviewed, rolling out r221461.
[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 "NavigatorBase.h"
36 #include "ScopeGuard.h"
37 #include "ScriptExecutionContext.h"
38 #include "SecurityOrigin.h"
39 #include "ServiceWorkerJob.h"
40 #include "ServiceWorkerJobData.h"
41 #include "ServiceWorkerProvider.h"
42 #include "URL.h"
43 #include <wtf/RunLoop.h>
44
45 namespace WebCore {
46
47 ServiceWorkerContainer::ServiceWorkerContainer(ScriptExecutionContext& context, NavigatorBase& navigator)
48     : ActiveDOMObject(&context)
49     , m_navigator(navigator)
50 {
51     suspendIfNeeded();
52
53     m_readyPromise.reject(Exception { UnknownError, ASCIILiteral("serviceWorker.ready() is not yet implemented") });
54 }
55
56 ServiceWorkerContainer::~ServiceWorkerContainer()
57 {
58 #ifndef NDEBUG
59     ASSERT(m_creationThread == currentThread());
60 #endif
61 }
62
63 void ServiceWorkerContainer::refEventTarget()
64 {
65     m_navigator.ref();
66 }
67
68 void ServiceWorkerContainer::derefEventTarget()
69 {
70     m_navigator.deref();
71 }
72
73 ServiceWorker* ServiceWorkerContainer::controller() const
74 {
75     return nullptr;
76 }
77
78 void ServiceWorkerContainer::addRegistration(const String& relativeScriptURL, const RegistrationOptions& options, Ref<DeferredPromise>&& promise)
79 {
80     auto* context = scriptExecutionContext();
81     if (!context || !context->sessionID().isValid()) {
82         ASSERT_NOT_REACHED();
83         return;
84     }
85
86     if (!m_swConnection)
87         m_swConnection = &ServiceWorkerProvider::singleton().serviceWorkerConnectionForSession(context->sessionID());
88
89     if (relativeScriptURL.isEmpty()) {
90         promise->reject(Exception { TypeError, ASCIILiteral("serviceWorker.register() cannot be called with an empty script URL") });
91         return;
92     }
93
94     ServiceWorkerJobData jobData(m_swConnection->identifier());
95
96     jobData.scriptURL = context->completeURL(relativeScriptURL);
97     if (!jobData.scriptURL.isValid()) {
98         promise->reject(Exception { TypeError, ASCIILiteral("serviceWorker.register() must be called with a valid relative script URL") });
99         return;
100     }
101
102     // FIXME: The spec disallows scripts outside of HTTP(S), but we'll likely support app custom URL schemes in WebKit.
103     if (!jobData.scriptURL.protocolIsInHTTPFamily()) {
104         promise->reject(Exception { TypeError, ASCIILiteral("serviceWorker.register() must be called with a script URL whose protocol is either HTTP or HTTPS") });
105         return;
106     }
107
108     String path = jobData.scriptURL.path();
109     if (path.containsIgnoringASCIICase("%2f") || path.containsIgnoringASCIICase("%5c")) {
110         promise->reject(Exception { TypeError, ASCIILiteral("serviceWorker.register() must be called with a script URL whose path does not contain '%2f' or '%5c'") });
111         return;
112     }
113
114     String scope = options.scope.isEmpty() ? ASCIILiteral("./") : options.scope;
115     if (!scope.isEmpty())
116         jobData.scopeURL = context->completeURL(scope);
117
118     if (!jobData.scopeURL.isNull() && !jobData.scopeURL.protocolIsInHTTPFamily()) {
119         promise->reject(Exception { TypeError, ASCIILiteral("Scope URL provided to serviceWorker.register() must be either HTTP or HTTPS") });
120         return;
121     }
122
123     path = jobData.scopeURL.path();
124     if (path.containsIgnoringASCIICase("%2f") || path.containsIgnoringASCIICase("%5c")) {
125         promise->reject(Exception { TypeError, ASCIILiteral("Scope URL provided to serviceWorker.register() cannot have a path that contains '%2f' or '%5c'") });
126         return;
127     }
128
129     jobData.clientCreationURL = context->url();
130     jobData.topOrigin = SecurityOriginData::fromSecurityOrigin(context->topOrigin());
131     jobData.type = ServiceWorkerJobType::Register;
132     jobData.registrationOptions = options;
133
134     scheduleJob(ServiceWorkerJob::create(*this, WTFMove(promise), WTFMove(jobData)));
135 }
136
137 void ServiceWorkerContainer::scheduleJob(Ref<ServiceWorkerJob>&& job)
138 {
139     ASSERT(m_swConnection);
140
141     ServiceWorkerJob& rawJob = job.get();
142     auto result = m_jobMap.add(rawJob.data().identifier(), WTFMove(job));
143     ASSERT_UNUSED(result, result.isNewEntry);
144
145     m_swConnection->scheduleJob(rawJob);
146 }
147
148 void ServiceWorkerContainer::getRegistration(const String&, Ref<DeferredPromise>&& promise)
149 {
150     promise->reject(Exception { UnknownError, ASCIILiteral("serviceWorker.getRegistration() is not yet implemented") });
151 }
152
153 void ServiceWorkerContainer::getRegistrations(Ref<DeferredPromise>&& promise)
154 {
155     promise->reject(Exception { UnknownError, ASCIILiteral("serviceWorker.getRegistrations() is not yet implemented") });
156 }
157
158 void ServiceWorkerContainer::startMessages()
159 {
160 }
161
162 void ServiceWorkerContainer::jobFailedWithException(ServiceWorkerJob& job, const Exception& exception)
163 {
164     job.promise().reject(exception);
165     jobDidFinish(job);
166 }
167
168 void ServiceWorkerContainer::jobResolvedWithRegistration(ServiceWorkerJob& job, const ServiceWorkerRegistrationData& data)
169 {
170     ScopeGuard guard([this, &job] {
171         jobDidFinish(job);
172     });
173
174     auto* context = scriptExecutionContext();
175     if (!context) {
176         LOG_ERROR("ServiceWorkerContainer::jobResolvedWithRegistration called but the containers ScriptExecutionContext is gone");
177         return;
178     }
179
180     auto registration = ServiceWorkerRegistration::create(*context, data);
181     job.promise().resolve<IDLInterface<ServiceWorkerRegistration>>(registration.get());
182 }
183
184 void ServiceWorkerContainer::jobDidFinish(ServiceWorkerJob& job)
185 {
186     auto taken = m_jobMap.take(job.data().identifier());
187     ASSERT_UNUSED(taken, !taken || taken.get() == &job);
188 }
189
190 uint64_t ServiceWorkerContainer::connectionIdentifier()
191 {
192     ASSERT(m_swConnection);
193     return m_swConnection->identifier();
194 }
195
196 const char* ServiceWorkerContainer::activeDOMObjectName() const
197 {
198     return "ServiceWorkerContainer";
199 }
200
201 bool ServiceWorkerContainer::canSuspendForDocumentSuspension() const
202 {
203     return true;
204 }
205
206 } // namespace WebCore
207
208 #endif // ENABLE(SERVICE_WORKER)