71965fd92bb6cda6087a3cad0a5c87390ed51794
[WebKit-https.git] / Source / WebCore / workers / service / ServiceWorkerJob.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 "ServiceWorkerJob.h"
28
29 #if ENABLE(SERVICE_WORKER)
30
31 #include "HTTPHeaderNames.h"
32 #include "JSDOMPromiseDeferred.h"
33 #include "MIMETypeRegistry.h"
34 #include "ResourceError.h"
35 #include "ResourceResponse.h"
36 #include "ScriptExecutionContext.h"
37 #include "SecurityOrigin.h"
38 #include "ServiceWorkerJobData.h"
39 #include "ServiceWorkerRegistration.h"
40
41 namespace WebCore {
42
43 ServiceWorkerJob::ServiceWorkerJob(ServiceWorkerJobClient& client, RefPtr<DeferredPromise>&& promise, ServiceWorkerJobData&& jobData)
44     : m_client(client)
45     , m_jobData(WTFMove(jobData))
46     , m_promise(WTFMove(promise))
47     , m_contextIdentifier(client.contextIdentifier())
48 {
49 }
50
51 ServiceWorkerJob::~ServiceWorkerJob()
52 {
53     ASSERT(m_creationThread.ptr() == &Thread::current());
54 }
55
56 void ServiceWorkerJob::failedWithException(const Exception& exception)
57 {
58     ASSERT(m_creationThread.ptr() == &Thread::current());
59     ASSERT(!m_completed);
60
61     m_completed = true;
62     m_client.jobFailedWithException(*this, exception);
63 }
64
65 void ServiceWorkerJob::resolvedWithRegistration(ServiceWorkerRegistrationData&& data, ShouldNotifyWhenResolved shouldNotifyWhenResolved)
66 {
67     ASSERT(m_creationThread.ptr() == &Thread::current());
68     ASSERT(!m_completed);
69
70     m_completed = true;
71     m_client.jobResolvedWithRegistration(*this, WTFMove(data), shouldNotifyWhenResolved);
72 }
73
74 void ServiceWorkerJob::resolvedWithUnregistrationResult(bool unregistrationResult)
75 {
76     ASSERT(m_creationThread.ptr() == &Thread::current());
77     ASSERT(!m_completed);
78
79     m_completed = true;
80     m_client.jobResolvedWithUnregistrationResult(*this, unregistrationResult);
81 }
82
83 void ServiceWorkerJob::startScriptFetch(FetchOptions::Cache cachePolicy)
84 {
85     ASSERT(m_creationThread.ptr() == &Thread::current());
86     ASSERT(!m_completed);
87
88     m_client.startScriptFetchForJob(*this, cachePolicy);
89 }
90
91 void ServiceWorkerJob::fetchScriptWithContext(ScriptExecutionContext& context, FetchOptions::Cache cachePolicy)
92 {
93     ASSERT(m_creationThread.ptr() == &Thread::current());
94     ASSERT(!m_completed);
95
96     // FIXME: WorkerScriptLoader is the wrong loader class to use here, but there's nothing else better right now.
97     m_scriptLoader = WorkerScriptLoader::create();
98
99     ResourceRequest request { m_jobData.scriptURL };
100     request.setInitiatorIdentifier(context.resourceRequestIdentifier());
101     request.addHTTPHeaderField("Service-Worker"_s, "script"_s);
102
103     FetchOptions options;
104     options.mode = FetchOptions::Mode::SameOrigin;
105     options.cache = cachePolicy;
106     options.redirect = FetchOptions::Redirect::Error;
107     options.destination = FetchOptions::Destination::Serviceworker;
108     m_scriptLoader->loadAsynchronously(context, WTFMove(request), WTFMove(options), ContentSecurityPolicyEnforcement::DoNotEnforce, ServiceWorkersMode::None, *this);
109 }
110
111 void ServiceWorkerJob::didReceiveResponse(unsigned long, const ResourceResponse& response)
112 {
113     ASSERT(m_creationThread.ptr() == &Thread::current());
114     ASSERT(!m_completed);
115     ASSERT(m_scriptLoader);
116
117     // Extract a MIME type from the response's header list. If this MIME type (ignoring parameters) is not a JavaScript MIME type, then:
118     if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(response.mimeType())) {
119         m_scriptLoader->cancel();
120         m_scriptLoader = nullptr;
121
122         // Invoke Reject Job Promise with job and "SecurityError" DOMException.
123         Exception exception { SecurityError, "MIME Type is not a JavaScript MIME type"_s };
124         // Asynchronously complete these steps with a network error.
125         ResourceError error { errorDomainWebKitInternal, 0, response.url(), "Unexpected MIME type"_s };
126         m_client.jobFailedLoadingScript(*this, WTFMove(error), WTFMove(exception));
127         return;
128     }
129
130     String serviceWorkerAllowed = response.httpHeaderField(HTTPHeaderName::ServiceWorkerAllowed);
131     String maxScopeString;
132     if (serviceWorkerAllowed.isNull()) {
133         String path = m_jobData.scriptURL.path();
134         // Last part of the path is the script's filename.
135         maxScopeString = path.substring(0, path.reverseFind('/') + 1);
136     } else {
137         auto maxScope = URL(m_jobData.scriptURL, serviceWorkerAllowed);
138         if (SecurityOrigin::create(maxScope)->isSameOriginAs(SecurityOrigin::create(m_jobData.scriptURL)))
139             maxScopeString = maxScope.path();
140     }
141
142     String scopeString = m_jobData.scopeURL.path();
143     if (scopeString.startsWith(maxScopeString))
144         return;
145
146     m_scriptLoader->cancel();
147     m_scriptLoader = nullptr;
148
149     Exception exception { SecurityError, "Scope URL should start with the given script URL"_s };
150     ResourceError error { errorDomainWebKitInternal, 0, response.url(), "Scope URL should start with the given script URL"_s };
151     m_client.jobFailedLoadingScript(*this, WTFMove(error), WTFMove(exception));
152 }
153
154 void ServiceWorkerJob::notifyFinished()
155 {
156     ASSERT(m_creationThread.ptr() == &Thread::current());
157     ASSERT(m_scriptLoader);
158     
159     auto scriptLoader = WTFMove(m_scriptLoader);
160
161     if (!scriptLoader->failed()) {
162         m_client.jobFinishedLoadingScript(*this, scriptLoader->script(), scriptLoader->contentSecurityPolicy(), scriptLoader->referrerPolicy());
163         return;
164     }
165
166     auto& error = scriptLoader->error();
167     ASSERT(!error.isNull());
168
169     m_client.jobFailedLoadingScript(*this, error, Exception { error.isAccessControl() ? SecurityError : TypeError, makeString("Script ", scriptLoader->url().string(), " load failed") });
170 }
171
172 bool ServiceWorkerJob::cancelPendingLoad()
173 {
174     if (!m_scriptLoader)
175         return false;
176
177     m_scriptLoader->cancel();
178     m_scriptLoader = nullptr;
179     return true;
180 }
181
182 } // namespace WebCore
183
184 #endif // ENABLE(SERVICE_WORKER)