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
42namespace WebCore {
43
44SWClientConnection::SWClientConnection() = default;
45
46SWClientConnection::~SWClientConnection() = default;
47
48void 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
58void SWClientConnection::failedFetchingScript(ServiceWorkerJobIdentifier jobIdentifier, const ServiceWorkerRegistrationKey& registrationKey, const ResourceError& error)
59{
60 ASSERT(isMainThread());
61
62 finishFetchingScriptInServer({ { serverConnectionIdentifier(), jobIdentifier }, registrationKey, { }, { }, { }, error });
63}
64
65bool 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
85void 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
92void 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
102void SWClientConnection::unregistrationJobResolvedInServer(ServiceWorkerJobIdentifier jobIdentifier, bool unregistrationResult)
103{
104 postTaskForJob(jobIdentifier, IsJobComplete::Yes, [unregistrationResult] (auto& job) {
105 job.resolvedWithUnregistrationResult(unregistrationResult);
106 });
107}
108
109void 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
119void 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
134void 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
153void 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
172void 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
191void 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
212void 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
233void 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
253void 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