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 "SWClientConnection.h" |
28 | |
29 | #if ENABLE(SERVICE_WORKER) |
30 | |
31 | #include "Document.h" |
32 | #include "ExceptionData.h" |
33 | #include "MessageEvent.h" |
34 | #include "Microtasks.h" |
35 | #include "SWContextManager.h" |
36 | #include "ServiceWorkerContainer.h" |
37 | #include "ServiceWorkerFetchResult.h" |
38 | #include "ServiceWorkerJobData.h" |
39 | #include "ServiceWorkerRegistration.h" |
40 | #include <wtf/CrossThreadCopier.h> |
41 | |
42 | namespace WebCore { |
43 | |
44 | SWClientConnection::SWClientConnection() = default; |
45 | |
46 | SWClientConnection::~SWClientConnection() = default; |
47 | |
48 | void SWClientConnection::scheduleJob(DocumentOrWorkerIdentifier contextIdentifier, const ServiceWorkerJobData& jobData) |
49 | { |
50 | ASSERT(isMainThread()); |
51 | |
52 | auto addResult = m_scheduledJobSources.add(jobData.identifier().jobIdentifier, contextIdentifier); |
53 | ASSERT_UNUSED(addResult, addResult.isNewEntry); |
54 | |
55 | scheduleJobInServer(jobData); |
56 | } |
57 | |
58 | void SWClientConnection::failedFetchingScript(ServiceWorkerJobIdentifier jobIdentifier, const ServiceWorkerRegistrationKey& registrationKey, const ResourceError& error) |
59 | { |
60 | ASSERT(isMainThread()); |
61 | |
62 | finishFetchingScriptInServer({ { serverConnectionIdentifier(), jobIdentifier }, registrationKey, { }, { }, { }, error }); |
63 | } |
64 | |
65 | bool SWClientConnection::postTaskForJob(ServiceWorkerJobIdentifier jobIdentifier, IsJobComplete isJobComplete, WTF::Function<void(ServiceWorkerJob&)>&& task) |
66 | { |
67 | ASSERT(isMainThread()); |
68 | |
69 | auto iterator = m_scheduledJobSources.find(jobIdentifier); |
70 | if (iterator == m_scheduledJobSources.end()) { |
71 | LOG_ERROR("Job %s was not found" , jobIdentifier.loggingString().utf8().data()); |
72 | return false; |
73 | } |
74 | auto isPosted = ScriptExecutionContext::postTaskTo(iterator->value, [jobIdentifier, task = WTFMove(task)] (ScriptExecutionContext& context) mutable { |
75 | if (auto* container = context.serviceWorkerContainer()) { |
76 | if (auto* job = container->job(jobIdentifier)) |
77 | task(*job); |
78 | } |
79 | }); |
80 | if (isJobComplete == IsJobComplete::Yes) |
81 | m_scheduledJobSources.remove(iterator); |
82 | return isPosted; |
83 | } |
84 | |
85 | void SWClientConnection::jobRejectedInServer(ServiceWorkerJobIdentifier jobIdentifier, const ExceptionData& exceptionData) |
86 | { |
87 | postTaskForJob(jobIdentifier, IsJobComplete::Yes, [exceptionData = exceptionData.isolatedCopy()] (auto& job) { |
88 | job.failedWithException(exceptionData.toException()); |
89 | }); |
90 | } |
91 | |
92 | void SWClientConnection::registrationJobResolvedInServer(ServiceWorkerJobIdentifier jobIdentifier, ServiceWorkerRegistrationData&& registrationData, ShouldNotifyWhenResolved shouldNotifyWhenResolved) |
93 | { |
94 | bool isPosted = postTaskForJob(jobIdentifier, IsJobComplete::Yes, [registrationData = registrationData.isolatedCopy(), shouldNotifyWhenResolved] (auto& job) mutable { |
95 | job.resolvedWithRegistration(WTFMove(registrationData), shouldNotifyWhenResolved); |
96 | }); |
97 | |
98 | if (!isPosted && shouldNotifyWhenResolved == ShouldNotifyWhenResolved::Yes) |
99 | didResolveRegistrationPromise(registrationData.key); |
100 | } |
101 | |
102 | void SWClientConnection::unregistrationJobResolvedInServer(ServiceWorkerJobIdentifier jobIdentifier, bool unregistrationResult) |
103 | { |
104 | postTaskForJob(jobIdentifier, IsJobComplete::Yes, [unregistrationResult] (auto& job) { |
105 | job.resolvedWithUnregistrationResult(unregistrationResult); |
106 | }); |
107 | } |
108 | |
109 | void SWClientConnection::startScriptFetchForServer(ServiceWorkerJobIdentifier jobIdentifier, const ServiceWorkerRegistrationKey& registrationKey, FetchOptions::Cache cachePolicy) |
110 | { |
111 | bool isPosted = postTaskForJob(jobIdentifier, IsJobComplete::No, [cachePolicy] (auto& job) { |
112 | job.startScriptFetch(cachePolicy); |
113 | }); |
114 | if (!isPosted) |
115 | failedFetchingScript(jobIdentifier, registrationKey, ResourceError { errorDomainWebKitInternal, 0, { }, makeString("Failed to fetch script for service worker with scope " , registrationKey.scope().string()) }); |
116 | } |
117 | |
118 | |
119 | void SWClientConnection::postMessageToServiceWorkerClient(DocumentIdentifier destinationContextIdentifier, MessageWithMessagePorts&& message, ServiceWorkerData&& sourceData, String&& sourceOrigin) |
120 | { |
121 | ASSERT(isMainThread()); |
122 | |
123 | // FIXME: destinationContextIdentifier can only identify a Document at the moment. |
124 | auto* destinationDocument = Document::allDocumentsMap().get(destinationContextIdentifier); |
125 | if (!destinationDocument) |
126 | return; |
127 | |
128 | destinationDocument->postTask([message = WTFMove(message), sourceData = WTFMove(sourceData), sourceOrigin = WTFMove(sourceOrigin)](auto& context) mutable { |
129 | if (auto* container = context.serviceWorkerContainer()) |
130 | container->postMessage(WTFMove(message), WTFMove(sourceData), WTFMove(sourceOrigin)); |
131 | }); |
132 | } |
133 | |
134 | void SWClientConnection::updateRegistrationState(ServiceWorkerRegistrationIdentifier identifier, ServiceWorkerRegistrationState state, const Optional<ServiceWorkerData>& serviceWorkerData) |
135 | { |
136 | ASSERT(isMainThread()); |
137 | |
138 | SWContextManager::singleton().forEachServiceWorkerThread([identifier, state, &serviceWorkerData] (auto& workerThread) { |
139 | workerThread.thread().runLoop().postTask([identifier, state, serviceWorkerData = crossThreadCopy(serviceWorkerData)](ScriptExecutionContext& context) mutable { |
140 | if (auto* container = context.serviceWorkerContainer()) |
141 | container->updateRegistrationState(identifier, state, WTFMove(serviceWorkerData)); |
142 | }); |
143 | }); |
144 | |
145 | for (auto* document : Document::allDocuments()) { |
146 | document->postTask([identifier, state, serviceWorkerData, document](auto&) { |
147 | if (auto* container = document->serviceWorkerContainer()) |
148 | container->updateRegistrationState(identifier, state, serviceWorkerData); |
149 | }); |
150 | } |
151 | } |
152 | |
153 | void SWClientConnection::updateWorkerState(ServiceWorkerIdentifier identifier, ServiceWorkerState state) |
154 | { |
155 | ASSERT(isMainThread()); |
156 | |
157 | SWContextManager::singleton().forEachServiceWorkerThread([identifier, state] (auto& workerThread) { |
158 | workerThread.thread().runLoop().postTask([identifier, state](ScriptExecutionContext& context) { |
159 | if (auto* serviceWorker = context.serviceWorker(identifier)) |
160 | serviceWorker->updateState(state); |
161 | }); |
162 | }); |
163 | |
164 | for (auto* document : Document::allDocuments()) { |
165 | document->postTask([identifier, document, state](auto&) { |
166 | if (auto* serviceWorker = document->serviceWorker(identifier)) |
167 | serviceWorker->updateState(state); |
168 | }); |
169 | } |
170 | } |
171 | |
172 | void SWClientConnection::fireUpdateFoundEvent(ServiceWorkerRegistrationIdentifier identifier) |
173 | { |
174 | ASSERT(isMainThread()); |
175 | |
176 | SWContextManager::singleton().forEachServiceWorkerThread([identifier] (auto& workerThread) { |
177 | workerThread.thread().runLoop().postTask([identifier](ScriptExecutionContext& context) { |
178 | if (auto* container = context.serviceWorkerContainer()) |
179 | container->fireUpdateFoundEvent(identifier); |
180 | }); |
181 | }); |
182 | |
183 | for (auto* document : Document::allDocuments()) { |
184 | document->postTask([document, identifier](auto&) { |
185 | if (auto* container = document->serviceWorkerContainer()) |
186 | container->fireUpdateFoundEvent(identifier); |
187 | }); |
188 | } |
189 | } |
190 | |
191 | void SWClientConnection::setRegistrationLastUpdateTime(ServiceWorkerRegistrationIdentifier identifier, WallTime lastUpdateTime) |
192 | { |
193 | ASSERT(isMainThread()); |
194 | |
195 | SWContextManager::singleton().forEachServiceWorkerThread([identifier, lastUpdateTime] (auto& workerThread) { |
196 | workerThread.thread().runLoop().postTask([identifier, lastUpdateTime](ScriptExecutionContext& context) { |
197 | if (auto* container = context.serviceWorkerContainer()) { |
198 | if (auto* registration = container->registration(identifier)) |
199 | registration->setLastUpdateTime(lastUpdateTime); |
200 | } |
201 | }); |
202 | }); |
203 | |
204 | for (auto* document : Document::allDocuments()) { |
205 | if (auto* container = document->serviceWorkerContainer()) { |
206 | if (auto* registration = container->registration(identifier)) |
207 | registration->setLastUpdateTime(lastUpdateTime); |
208 | } |
209 | } |
210 | } |
211 | |
212 | void SWClientConnection::setRegistrationUpdateViaCache(ServiceWorkerRegistrationIdentifier identifier, ServiceWorkerUpdateViaCache updateViaCache) |
213 | { |
214 | ASSERT(isMainThread()); |
215 | |
216 | SWContextManager::singleton().forEachServiceWorkerThread([identifier, updateViaCache] (auto& workerThread) { |
217 | workerThread.thread().runLoop().postTask([identifier, updateViaCache](ScriptExecutionContext& context) { |
218 | if (auto* container = context.serviceWorkerContainer()) { |
219 | if (auto* registration = container->registration(identifier)) |
220 | registration->setUpdateViaCache(updateViaCache); |
221 | } |
222 | }); |
223 | }); |
224 | |
225 | for (auto* document : Document::allDocuments()) { |
226 | if (auto* container = document->serviceWorkerContainer()) { |
227 | if (auto* registration = container->registration(identifier)) |
228 | registration->setUpdateViaCache(updateViaCache); |
229 | } |
230 | } |
231 | } |
232 | |
233 | void SWClientConnection::notifyClientsOfControllerChange(const HashSet<DocumentIdentifier>& contextIdentifiers, ServiceWorkerData&& newController) |
234 | { |
235 | ASSERT(isMainThread()); |
236 | ASSERT(!contextIdentifiers.isEmpty()); |
237 | |
238 | for (auto& clientIdentifier : contextIdentifiers) { |
239 | // FIXME: Support worker contexts. |
240 | auto* client = Document::allDocumentsMap().get(clientIdentifier); |
241 | if (!client) |
242 | continue; |
243 | |
244 | client->postTask([client, contextIdentifiers, newController = WTFMove(newController)](auto&) { |
245 | ASSERT(!client->activeServiceWorker() || client->activeServiceWorker()->identifier() != newController.identifier); |
246 | client->setActiveServiceWorker(ServiceWorker::getOrCreate(*client, ServiceWorkerData { newController })); |
247 | if (auto* container = client->serviceWorkerContainer()) |
248 | container->fireControllerChangeEvent(); |
249 | }); |
250 | } |
251 | } |
252 | |
253 | void SWClientConnection::clearPendingJobs() |
254 | { |
255 | ASSERT(isMainThread()); |
256 | |
257 | auto jobSources = WTFMove(m_scheduledJobSources); |
258 | for (auto& keyValue : jobSources) { |
259 | ScriptExecutionContext::postTaskTo(keyValue.value, [identifier = keyValue.key] (auto& context) { |
260 | if (auto* container = context.serviceWorkerContainer()) { |
261 | if (auto* job = container->job(identifier)) |
262 | job->failedWithException(Exception { TypeError, "Internal error"_s }); |
263 | } |
264 | }); |
265 | } |
266 | } |
267 | |
268 | } // namespace WebCore |
269 | |
270 | #endif // ENABLE(SERVICE_WORKER) |
271 | |