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 "Document.h" |
32 | #include "Event.h" |
33 | #include "EventNames.h" |
34 | #include "Exception.h" |
35 | #include "IDLTypes.h" |
36 | #include "JSDOMPromiseDeferred.h" |
37 | #include "JSServiceWorkerRegistration.h" |
38 | #include "Logging.h" |
39 | #include "MessageEvent.h" |
40 | #include "NavigatorBase.h" |
41 | #include "ResourceError.h" |
42 | #include "SchemeRegistry.h" |
43 | #include "ScriptExecutionContext.h" |
44 | #include "SecurityOrigin.h" |
45 | #include "ServiceWorker.h" |
46 | #include "ServiceWorkerFetchResult.h" |
47 | #include "ServiceWorkerGlobalScope.h" |
48 | #include "ServiceWorkerJob.h" |
49 | #include "ServiceWorkerJobData.h" |
50 | #include "ServiceWorkerProvider.h" |
51 | #include "ServiceWorkerThread.h" |
52 | #include <wtf/IsoMallocInlines.h> |
53 | #include <wtf/RunLoop.h> |
54 | #include <wtf/Scope.h> |
55 | #include <wtf/URL.h> |
56 | |
57 | #define CONTAINER_RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), ServiceWorker, "%p - ServiceWorkerContainer::" fmt, this, ##__VA_ARGS__) |
58 | #define CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED(fmt, ...) RELEASE_LOG_ERROR_IF(isAlwaysOnLoggingAllowed(), ServiceWorker, "%p - ServiceWorkerContainer::" fmt, this, ##__VA_ARGS__) |
59 | |
60 | namespace WebCore { |
61 | |
62 | WTF_MAKE_ISO_ALLOCATED_IMPL(ServiceWorkerContainer); |
63 | |
64 | ServiceWorkerContainer::ServiceWorkerContainer(ScriptExecutionContext* context, NavigatorBase& navigator) |
65 | : ActiveDOMObject(context) |
66 | , m_navigator(navigator) |
67 | { |
68 | suspendIfNeeded(); |
69 | } |
70 | |
71 | ServiceWorkerContainer::~ServiceWorkerContainer() |
72 | { |
73 | #ifndef NDEBUG |
74 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
75 | #endif |
76 | } |
77 | |
78 | void ServiceWorkerContainer::refEventTarget() |
79 | { |
80 | m_navigator.ref(); |
81 | } |
82 | |
83 | void ServiceWorkerContainer::derefEventTarget() |
84 | { |
85 | m_navigator.deref(); |
86 | } |
87 | |
88 | auto ServiceWorkerContainer::ready() -> ReadyPromise& |
89 | { |
90 | if (!m_readyPromise) { |
91 | m_readyPromise = std::make_unique<ReadyPromise>(); |
92 | |
93 | if (m_isStopped || !scriptExecutionContext()->sessionID().isValid()) |
94 | return *m_readyPromise; |
95 | |
96 | auto& context = *scriptExecutionContext(); |
97 | auto contextIdentifier = this->contextIdentifier(); |
98 | callOnMainThread([connection = makeRef(ensureSWClientConnection()), topOrigin = context.topOrigin().isolatedCopy(), clientURL = context.url().isolatedCopy(), contextIdentifier]() mutable { |
99 | connection->whenRegistrationReady(topOrigin, clientURL, [contextIdentifier](auto&& registrationData) { |
100 | ScriptExecutionContext::postTaskTo(contextIdentifier, [registrationData = crossThreadCopy(registrationData)](auto& context) mutable { |
101 | auto* serviceWorkerContainer = context.serviceWorkerContainer(); |
102 | if (!serviceWorkerContainer) |
103 | return; |
104 | if (serviceWorkerContainer->m_isStopped || !context.sessionID().isValid()) |
105 | return; |
106 | |
107 | auto registration = ServiceWorkerRegistration::getOrCreate(context, *serviceWorkerContainer, WTFMove(registrationData)); |
108 | serviceWorkerContainer->m_readyPromise->resolve(WTFMove(registration)); |
109 | }); |
110 | }); |
111 | }); |
112 | } |
113 | return *m_readyPromise; |
114 | } |
115 | |
116 | ServiceWorker* ServiceWorkerContainer::controller() const |
117 | { |
118 | auto* context = scriptExecutionContext(); |
119 | ASSERT_WITH_MESSAGE(!context || is<Document>(*context) || !context->activeServiceWorker(), "Only documents can have a controller at the moment." ); |
120 | return context ? context->activeServiceWorker() : nullptr; |
121 | } |
122 | |
123 | void ServiceWorkerContainer::addRegistration(const String& relativeScriptURL, const RegistrationOptions& options, Ref<DeferredPromise>&& promise) |
124 | { |
125 | auto* context = scriptExecutionContext(); |
126 | if (m_isStopped || !context->sessionID().isValid()) { |
127 | promise->reject(Exception(InvalidStateError)); |
128 | return; |
129 | } |
130 | |
131 | if (relativeScriptURL.isEmpty()) { |
132 | promise->reject(Exception { TypeError, "serviceWorker.register() cannot be called with an empty script URL"_s }); |
133 | return; |
134 | } |
135 | |
136 | ServiceWorkerJobData jobData(ensureSWClientConnection().serverConnectionIdentifier(), contextIdentifier()); |
137 | |
138 | jobData.scriptURL = context->completeURL(relativeScriptURL); |
139 | if (!jobData.scriptURL.isValid()) { |
140 | CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: Invalid scriptURL" ); |
141 | promise->reject(Exception { TypeError, "serviceWorker.register() must be called with a valid relative script URL"_s }); |
142 | return; |
143 | } |
144 | |
145 | if (!SchemeRegistry::canServiceWorkersHandleURLScheme(jobData.scriptURL.protocol().toStringWithoutCopying())) { |
146 | CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: Invalid scriptURL scheme is not HTTP or HTTPS" ); |
147 | promise->reject(Exception { TypeError, "serviceWorker.register() must be called with a script URL whose protocol is either HTTP or HTTPS"_s }); |
148 | return; |
149 | } |
150 | |
151 | String path = jobData.scriptURL.path(); |
152 | if (path.containsIgnoringASCIICase("%2f" ) || path.containsIgnoringASCIICase("%5c" )) { |
153 | CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: scriptURL contains invalid character" ); |
154 | promise->reject(Exception { TypeError, "serviceWorker.register() must be called with a script URL whose path does not contain '%2f' or '%5c'"_s }); |
155 | return; |
156 | } |
157 | |
158 | if (!options.scope.isEmpty()) |
159 | jobData.scopeURL = context->completeURL(options.scope); |
160 | else |
161 | jobData.scopeURL = URL(jobData.scriptURL, "./" ); |
162 | |
163 | if (!jobData.scopeURL.isNull() && !SchemeRegistry::canServiceWorkersHandleURLScheme(jobData.scopeURL.protocol().toStringWithoutCopying())) { |
164 | CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: scopeURL scheme is not HTTP or HTTPS" ); |
165 | promise->reject(Exception { TypeError, "Scope URL provided to serviceWorker.register() must be either HTTP or HTTPS"_s }); |
166 | return; |
167 | } |
168 | |
169 | path = jobData.scopeURL.path(); |
170 | if (path.containsIgnoringASCIICase("%2f" ) || path.containsIgnoringASCIICase("%5c" )) { |
171 | CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("addRegistration: scopeURL contains invalid character" ); |
172 | promise->reject(Exception { TypeError, "Scope URL provided to serviceWorker.register() cannot have a path that contains '%2f' or '%5c'"_s }); |
173 | return; |
174 | } |
175 | |
176 | CONTAINER_RELEASE_LOG_IF_ALLOWED("addRegistration: Registering service worker. Job ID: %" PRIu64, jobData.identifier().jobIdentifier.toUInt64()); |
177 | |
178 | jobData.clientCreationURL = context->url(); |
179 | jobData.topOrigin = context->topOrigin().data(); |
180 | jobData.type = ServiceWorkerJobType::Register; |
181 | jobData.registrationOptions = options; |
182 | |
183 | scheduleJob(std::make_unique<ServiceWorkerJob>(*this, WTFMove(promise), WTFMove(jobData))); |
184 | } |
185 | |
186 | void ServiceWorkerContainer::removeRegistration(const URL& scopeURL, Ref<DeferredPromise>&& promise) |
187 | { |
188 | auto* context = scriptExecutionContext(); |
189 | if (!context || !context->sessionID().isValid()) { |
190 | ASSERT_NOT_REACHED(); |
191 | promise->reject(Exception(InvalidStateError)); |
192 | return; |
193 | } |
194 | |
195 | if (!m_swConnection) { |
196 | ASSERT_NOT_REACHED(); |
197 | promise->reject(Exception(InvalidStateError)); |
198 | return; |
199 | } |
200 | |
201 | ServiceWorkerJobData jobData(m_swConnection->serverConnectionIdentifier(), contextIdentifier()); |
202 | jobData.clientCreationURL = context->url(); |
203 | jobData.topOrigin = context->topOrigin().data(); |
204 | jobData.type = ServiceWorkerJobType::Unregister; |
205 | jobData.scopeURL = scopeURL; |
206 | |
207 | CONTAINER_RELEASE_LOG_IF_ALLOWED("removeRegistration: Unregistering service worker. Job ID: %" PRIu64, jobData.identifier().jobIdentifier.toUInt64()); |
208 | |
209 | scheduleJob(std::make_unique<ServiceWorkerJob>(*this, WTFMove(promise), WTFMove(jobData))); |
210 | } |
211 | |
212 | void ServiceWorkerContainer::updateRegistration(const URL& scopeURL, const URL& scriptURL, WorkerType, RefPtr<DeferredPromise>&& promise) |
213 | { |
214 | ASSERT(!m_isStopped); |
215 | |
216 | auto& context = *scriptExecutionContext(); |
217 | ASSERT(context.sessionID().isValid()); |
218 | |
219 | if (!m_swConnection) { |
220 | ASSERT_NOT_REACHED(); |
221 | if (promise) |
222 | promise->reject(Exception(InvalidStateError)); |
223 | return; |
224 | } |
225 | |
226 | ServiceWorkerJobData jobData(m_swConnection->serverConnectionIdentifier(), contextIdentifier()); |
227 | jobData.clientCreationURL = context.url(); |
228 | jobData.topOrigin = context.topOrigin().data(); |
229 | jobData.type = ServiceWorkerJobType::Update; |
230 | jobData.scopeURL = scopeURL; |
231 | jobData.scriptURL = scriptURL; |
232 | |
233 | CONTAINER_RELEASE_LOG_IF_ALLOWED("removeRegistration: Updating service worker. Job ID: %" PRIu64, jobData.identifier().jobIdentifier.toUInt64()); |
234 | |
235 | scheduleJob(std::make_unique<ServiceWorkerJob>(*this, WTFMove(promise), WTFMove(jobData))); |
236 | } |
237 | |
238 | void ServiceWorkerContainer::scheduleJob(std::unique_ptr<ServiceWorkerJob>&& job) |
239 | { |
240 | #ifndef NDEBUG |
241 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
242 | #endif |
243 | |
244 | ASSERT(m_swConnection); |
245 | ASSERT(!isStopped()); |
246 | |
247 | auto& jobData = job->data(); |
248 | auto jobIdentifier = job->identifier(); |
249 | ASSERT(!m_jobMap.contains(jobIdentifier)); |
250 | m_jobMap.add(jobIdentifier, OngoingJob { WTFMove(job), makePendingActivity(*this) }); |
251 | |
252 | callOnMainThread([connection = m_swConnection, contextIdentifier = this->contextIdentifier(), jobData = jobData.isolatedCopy()] { |
253 | connection->scheduleJob(contextIdentifier, jobData); |
254 | }); |
255 | } |
256 | |
257 | void ServiceWorkerContainer::getRegistration(const String& clientURL, Ref<DeferredPromise>&& promise) |
258 | { |
259 | auto* context = scriptExecutionContext(); |
260 | if (m_isStopped || !context->sessionID().isValid()) { |
261 | promise->reject(Exception { InvalidStateError }); |
262 | return; |
263 | } |
264 | |
265 | URL parsedURL = context->completeURL(clientURL); |
266 | if (!protocolHostAndPortAreEqual(parsedURL, context->url())) { |
267 | promise->reject(Exception { SecurityError, "Origin of clientURL is not client's origin"_s }); |
268 | return; |
269 | } |
270 | |
271 | uint64_t pendingPromiseIdentifier = ++m_lastPendingPromiseIdentifier; |
272 | auto pendingPromise = std::make_unique<PendingPromise>(WTFMove(promise), makePendingActivity(*this)); |
273 | m_pendingPromises.add(pendingPromiseIdentifier, WTFMove(pendingPromise)); |
274 | |
275 | auto contextIdentifier = this->contextIdentifier(); |
276 | callOnMainThread([connection = makeRef(ensureSWClientConnection()), topOrigin = context->topOrigin().data().isolatedCopy(), parsedURL = parsedURL.isolatedCopy(), contextIdentifier, pendingPromiseIdentifier]() mutable { |
277 | connection->matchRegistration(WTFMove(topOrigin), parsedURL, [contextIdentifier, pendingPromiseIdentifier] (auto&& result) mutable { |
278 | ScriptExecutionContext::postTaskTo(contextIdentifier, [pendingPromiseIdentifier, result = crossThreadCopy(result)](auto& context) mutable { |
279 | auto* serviceWorkerContainer = context.serviceWorkerContainer(); |
280 | if (!serviceWorkerContainer) |
281 | return; |
282 | if (serviceWorkerContainer->m_isStopped || !context.sessionID().isValid()) |
283 | return; |
284 | |
285 | serviceWorkerContainer->didFinishGetRegistrationRequest(pendingPromiseIdentifier, WTFMove(result)); |
286 | }); |
287 | }); |
288 | }); |
289 | } |
290 | |
291 | void ServiceWorkerContainer::didFinishGetRegistrationRequest(uint64_t pendingPromiseIdentifier, Optional<ServiceWorkerRegistrationData>&& result) |
292 | { |
293 | #ifndef NDEBUG |
294 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
295 | #endif |
296 | |
297 | auto pendingPromise = m_pendingPromises.take(pendingPromiseIdentifier); |
298 | if (!pendingPromise) |
299 | return; |
300 | |
301 | if (m_isStopped || !scriptExecutionContext()->sessionID().isValid()) { |
302 | pendingPromise->promise->reject(Exception { InvalidStateError }); |
303 | return; |
304 | } |
305 | |
306 | if (!result) { |
307 | pendingPromise->promise->resolve(); |
308 | return; |
309 | } |
310 | |
311 | auto registration = ServiceWorkerRegistration::getOrCreate(*scriptExecutionContext(), *this, WTFMove(result.value())); |
312 | pendingPromise->promise->resolve<IDLInterface<ServiceWorkerRegistration>>(WTFMove(registration)); |
313 | } |
314 | |
315 | void ServiceWorkerContainer::updateRegistrationState(ServiceWorkerRegistrationIdentifier identifier, ServiceWorkerRegistrationState state, const Optional<ServiceWorkerData>& serviceWorkerData) |
316 | { |
317 | if (m_isStopped) |
318 | return; |
319 | |
320 | RefPtr<ServiceWorker> serviceWorker; |
321 | if (serviceWorkerData) |
322 | serviceWorker = ServiceWorker::getOrCreate(*scriptExecutionContext(), ServiceWorkerData { *serviceWorkerData }); |
323 | |
324 | if (auto* registration = m_registrations.get(identifier)) |
325 | registration->updateStateFromServer(state, WTFMove(serviceWorker)); |
326 | } |
327 | |
328 | void ServiceWorkerContainer::getRegistrations(Ref<DeferredPromise>&& promise) |
329 | { |
330 | auto* context = scriptExecutionContext(); |
331 | if (m_isStopped || !context->sessionID().isValid()) { |
332 | promise->reject(Exception { InvalidStateError }); |
333 | return; |
334 | } |
335 | |
336 | uint64_t pendingPromiseIdentifier = ++m_lastPendingPromiseIdentifier; |
337 | auto pendingPromise = std::make_unique<PendingPromise>(WTFMove(promise), makePendingActivity(*this)); |
338 | m_pendingPromises.add(pendingPromiseIdentifier, WTFMove(pendingPromise)); |
339 | |
340 | auto contextIdentifier = this->contextIdentifier(); |
341 | auto contextURL = context->url(); |
342 | callOnMainThread([connection = makeRef(ensureSWClientConnection()), topOrigin = context->topOrigin().data().isolatedCopy(), contextURL = contextURL.isolatedCopy(), contextIdentifier, pendingPromiseIdentifier]() mutable { |
343 | connection->getRegistrations(WTFMove(topOrigin), contextURL, [contextIdentifier, pendingPromiseIdentifier] (auto&& registrationDatas) mutable { |
344 | ScriptExecutionContext::postTaskTo(contextIdentifier, [pendingPromiseIdentifier, registrationDatas = crossThreadCopy(registrationDatas)](auto& context) mutable { |
345 | auto* serviceWorkerContainer = context.serviceWorkerContainer(); |
346 | if (!serviceWorkerContainer) |
347 | return; |
348 | if (serviceWorkerContainer->m_isStopped || !context.sessionID().isValid()) |
349 | return; |
350 | |
351 | serviceWorkerContainer->didFinishGetRegistrationsRequest(pendingPromiseIdentifier, WTFMove(registrationDatas)); |
352 | }); |
353 | }); |
354 | }); |
355 | } |
356 | |
357 | void ServiceWorkerContainer::didFinishGetRegistrationsRequest(uint64_t pendingPromiseIdentifier, Vector<ServiceWorkerRegistrationData>&& registrationDatas) |
358 | { |
359 | #ifndef NDEBUG |
360 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
361 | #endif |
362 | |
363 | auto pendingPromise = m_pendingPromises.take(pendingPromiseIdentifier); |
364 | if (!pendingPromise) |
365 | return; |
366 | |
367 | if (m_isStopped || !scriptExecutionContext()->sessionID().isValid()) { |
368 | pendingPromise->promise->reject(Exception { InvalidStateError }); |
369 | return; |
370 | } |
371 | |
372 | auto registrations = WTF::map(WTFMove(registrationDatas), [&] (auto&& registrationData) { |
373 | return ServiceWorkerRegistration::getOrCreate(*this->scriptExecutionContext(), *this, WTFMove(registrationData)); |
374 | }); |
375 | |
376 | pendingPromise->promise->resolve<IDLSequence<IDLInterface<ServiceWorkerRegistration>>>(WTFMove(registrations)); |
377 | } |
378 | |
379 | void ServiceWorkerContainer::startMessages() |
380 | { |
381 | } |
382 | |
383 | void ServiceWorkerContainer::jobFailedWithException(ServiceWorkerJob& job, const Exception& exception) |
384 | { |
385 | #ifndef NDEBUG |
386 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
387 | #endif |
388 | |
389 | ASSERT_WITH_MESSAGE(job.hasPromise() || job.data().type == ServiceWorkerJobType::Update, "Only soft updates have no promise" ); |
390 | |
391 | auto guard = WTF::makeScopeExit([this, &job] { |
392 | destroyJob(job); |
393 | }); |
394 | |
395 | CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("jobFailedWithException: Job %" PRIu64 " failed with error %s" , job.identifier().toUInt64(), exception.message().utf8().data()); |
396 | |
397 | auto promise = job.takePromise(); |
398 | if (!promise) |
399 | return; |
400 | |
401 | if (auto* context = scriptExecutionContext()) { |
402 | context->postTask([promise = WTFMove(promise), exception](auto&) mutable { |
403 | promise->reject(exception); |
404 | }); |
405 | } |
406 | } |
407 | |
408 | void ServiceWorkerContainer::fireUpdateFoundEvent(ServiceWorkerRegistrationIdentifier identifier) |
409 | { |
410 | #ifndef NDEBUG |
411 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
412 | #endif |
413 | |
414 | if (auto* registration = m_registrations.get(identifier)) |
415 | registration->fireUpdateFoundEvent(); |
416 | } |
417 | |
418 | void ServiceWorkerContainer::jobResolvedWithRegistration(ServiceWorkerJob& job, ServiceWorkerRegistrationData&& data, ShouldNotifyWhenResolved shouldNotifyWhenResolved) |
419 | { |
420 | #ifndef NDEBUG |
421 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
422 | #endif |
423 | ASSERT_WITH_MESSAGE(job.hasPromise() || job.data().type == ServiceWorkerJobType::Update, "Only soft updates have no promise" ); |
424 | |
425 | if (job.data().type == ServiceWorkerJobType::Register) |
426 | CONTAINER_RELEASE_LOG_IF_ALLOWED("jobResolvedWithRegistration: Registration job %" PRIu64 " succeeded" , job.identifier().toUInt64()); |
427 | else { |
428 | ASSERT(job.data().type == ServiceWorkerJobType::Update); |
429 | CONTAINER_RELEASE_LOG_IF_ALLOWED("jobResolvedWithRegistration: Update job %" PRIu64 " succeeded" , job.identifier().toUInt64()); |
430 | } |
431 | |
432 | auto guard = WTF::makeScopeExit([this, &job] { |
433 | destroyJob(job); |
434 | }); |
435 | |
436 | auto notifyIfExitEarly = WTF::makeScopeExit([this, &data, &shouldNotifyWhenResolved] { |
437 | if (shouldNotifyWhenResolved == ShouldNotifyWhenResolved::Yes) |
438 | notifyRegistrationIsSettled(data.key); |
439 | }); |
440 | |
441 | if (isStopped()) |
442 | return; |
443 | |
444 | auto promise = job.takePromise(); |
445 | if (!promise) |
446 | return; |
447 | |
448 | notifyIfExitEarly.release(); |
449 | |
450 | scriptExecutionContext()->postTask([this, protectedThis = RefPtr<ServiceWorkerContainer>(this), promise = WTFMove(promise), jobIdentifier = job.identifier(), data = WTFMove(data), shouldNotifyWhenResolved](ScriptExecutionContext& context) mutable { |
451 | if (isStopped() || !context.sessionID().isValid()) { |
452 | if (shouldNotifyWhenResolved == ShouldNotifyWhenResolved::Yes) |
453 | notifyRegistrationIsSettled(data.key); |
454 | return; |
455 | } |
456 | |
457 | auto registration = ServiceWorkerRegistration::getOrCreate(context, *this, WTFMove(data)); |
458 | |
459 | CONTAINER_RELEASE_LOG_IF_ALLOWED("jobResolvedWithRegistration: Resolving promise for job %" PRIu64 ". Registration ID: %" PRIu64, jobIdentifier.toUInt64(), registration->identifier().toUInt64()); |
460 | |
461 | if (shouldNotifyWhenResolved == ShouldNotifyWhenResolved::Yes) { |
462 | m_ongoingSettledRegistrations.add(++m_lastOngoingSettledRegistrationIdentifier, registration->data().key); |
463 | promise->whenSettled([this, protectedThis = WTFMove(protectedThis), identifier = m_lastOngoingSettledRegistrationIdentifier] { |
464 | notifyRegistrationIsSettled(m_ongoingSettledRegistrations.take(identifier)); |
465 | }); |
466 | } |
467 | |
468 | promise->resolve<IDLInterface<ServiceWorkerRegistration>>(WTFMove(registration)); |
469 | }); |
470 | } |
471 | |
472 | void ServiceWorkerContainer::postMessage(MessageWithMessagePorts&& message, ServiceWorkerData&& sourceData, String&& sourceOrigin) |
473 | { |
474 | auto& context = *scriptExecutionContext(); |
475 | MessageEventSource source = RefPtr<ServiceWorker> { ServiceWorker::getOrCreate(context, WTFMove(sourceData)) }; |
476 | |
477 | auto messageEvent = MessageEvent::create(MessagePort::entanglePorts(context, WTFMove(message.transferredPorts)), message.message.releaseNonNull(), sourceOrigin, { }, WTFMove(source)); |
478 | dispatchEvent(messageEvent); |
479 | } |
480 | |
481 | void ServiceWorkerContainer::notifyRegistrationIsSettled(const ServiceWorkerRegistrationKey& registrationKey) |
482 | { |
483 | callOnMainThread([connection = m_swConnection, registrationKey = registrationKey.isolatedCopy()] { |
484 | connection->didResolveRegistrationPromise(registrationKey); |
485 | }); |
486 | } |
487 | |
488 | void ServiceWorkerContainer::jobResolvedWithUnregistrationResult(ServiceWorkerJob& job, bool unregistrationResult) |
489 | { |
490 | #ifndef NDEBUG |
491 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
492 | #endif |
493 | |
494 | ASSERT(job.hasPromise()); |
495 | |
496 | auto guard = WTF::makeScopeExit([this, &job] { |
497 | destroyJob(job); |
498 | }); |
499 | |
500 | CONTAINER_RELEASE_LOG_IF_ALLOWED("jobResolvedWithUnregistrationResult: Unregister job %" PRIu64 " finished. Success? %d" , job.identifier().toUInt64(), unregistrationResult); |
501 | |
502 | auto* context = scriptExecutionContext(); |
503 | if (!context) { |
504 | LOG_ERROR("ServiceWorkerContainer::jobResolvedWithUnregistrationResult called but the containers ScriptExecutionContext is gone" ); |
505 | return; |
506 | } |
507 | |
508 | context->postTask([promise = job.takePromise(), unregistrationResult](auto&) mutable { |
509 | promise->resolve<IDLBoolean>(unregistrationResult); |
510 | }); |
511 | } |
512 | |
513 | void ServiceWorkerContainer::startScriptFetchForJob(ServiceWorkerJob& job, FetchOptions::Cache cachePolicy) |
514 | { |
515 | #ifndef NDEBUG |
516 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
517 | #endif |
518 | |
519 | CONTAINER_RELEASE_LOG_IF_ALLOWED("startScriptFetchForJob: Starting script fetch for job %" PRIu64, job.identifier().toUInt64()); |
520 | |
521 | auto* context = scriptExecutionContext(); |
522 | if (!context) { |
523 | LOG_ERROR("ServiceWorkerContainer::jobResolvedWithRegistration called but the container's ScriptExecutionContext is gone" ); |
524 | notifyFailedFetchingScript(job, { errorDomainWebKitInternal, 0, job.data().scriptURL, "Attempt to fetch service worker script with no ScriptExecutionContext"_s }); |
525 | destroyJob(job); |
526 | return; |
527 | } |
528 | |
529 | job.fetchScriptWithContext(*context, cachePolicy); |
530 | } |
531 | |
532 | void ServiceWorkerContainer::(ServiceWorkerJob& job, const String& script, const ContentSecurityPolicyResponseHeaders& contentSecurityPolicy, const String& referrerPolicy) |
533 | { |
534 | #ifndef NDEBUG |
535 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
536 | #endif |
537 | |
538 | CONTAINER_RELEASE_LOG_IF_ALLOWED("jobFinishedLoadingScript: Successfuly finished fetching script for job %" PRIu64, job.identifier().toUInt64()); |
539 | |
540 | callOnMainThread([connection = m_swConnection, jobDataIdentifier = job.data().identifier(), registrationKey = job.data().registrationKey().isolatedCopy(), script = script.isolatedCopy(), contentSecurityPolicy = contentSecurityPolicy.isolatedCopy(), referrerPolicy = referrerPolicy.isolatedCopy()] { |
541 | connection->finishFetchingScriptInServer({ jobDataIdentifier, registrationKey, script, contentSecurityPolicy, referrerPolicy, { } }); |
542 | }); |
543 | } |
544 | |
545 | void ServiceWorkerContainer::jobFailedLoadingScript(ServiceWorkerJob& job, const ResourceError& error, Exception&& exception) |
546 | { |
547 | #ifndef NDEBUG |
548 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
549 | #endif |
550 | ASSERT_WITH_MESSAGE(job.hasPromise() || job.data().type == ServiceWorkerJobType::Update, "Only soft updates have no promise" ); |
551 | |
552 | CONTAINER_RELEASE_LOG_ERROR_IF_ALLOWED("jobFinishedLoadingScript: Failed to fetch script for job %" PRIu64 ", error: %s" , job.identifier().toUInt64(), error.localizedDescription().utf8().data()); |
553 | |
554 | if (auto promise = job.takePromise()) |
555 | promise->reject(WTFMove(exception)); |
556 | |
557 | notifyFailedFetchingScript(job, error); |
558 | destroyJob(job); |
559 | } |
560 | |
561 | void ServiceWorkerContainer::notifyFailedFetchingScript(ServiceWorkerJob& job, const ResourceError& error) |
562 | { |
563 | callOnMainThread([connection = m_swConnection, jobIdentifier = job.identifier(), registrationKey = job.data().registrationKey().isolatedCopy(), error = error.isolatedCopy()] { |
564 | connection->failedFetchingScript(jobIdentifier, registrationKey, error); |
565 | }); |
566 | } |
567 | |
568 | void ServiceWorkerContainer::destroyJob(ServiceWorkerJob& job) |
569 | { |
570 | #ifndef NDEBUG |
571 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
572 | #endif |
573 | |
574 | ASSERT(m_jobMap.contains(job.identifier())); |
575 | m_jobMap.remove(job.identifier()); |
576 | } |
577 | |
578 | SWServerConnectionIdentifier ServiceWorkerContainer::connectionIdentifier() |
579 | { |
580 | ASSERT(m_swConnection); |
581 | return m_swConnection->serverConnectionIdentifier(); |
582 | } |
583 | |
584 | const char* ServiceWorkerContainer::activeDOMObjectName() const |
585 | { |
586 | return "ServiceWorkerContainer" ; |
587 | } |
588 | |
589 | bool ServiceWorkerContainer::canSuspendForDocumentSuspension() const |
590 | { |
591 | return !hasPendingActivity(); |
592 | } |
593 | |
594 | SWClientConnection& ServiceWorkerContainer::ensureSWClientConnection() |
595 | { |
596 | ASSERT(scriptExecutionContext()); |
597 | ASSERT(scriptExecutionContext()->sessionID().isValid()); |
598 | if (!m_swConnection) { |
599 | ASSERT(scriptExecutionContext()); |
600 | callOnMainThreadAndWait([this, sessionID = scriptExecutionContext()->sessionID()]() { |
601 | m_swConnection = &ServiceWorkerProvider::singleton().serviceWorkerConnectionForSession(sessionID); |
602 | }); |
603 | } |
604 | return *m_swConnection; |
605 | } |
606 | |
607 | void ServiceWorkerContainer::addRegistration(ServiceWorkerRegistration& registration) |
608 | { |
609 | #ifndef NDEBUG |
610 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
611 | #endif |
612 | |
613 | ensureSWClientConnection().addServiceWorkerRegistrationInServer(registration.identifier()); |
614 | m_registrations.add(registration.identifier(), ®istration); |
615 | } |
616 | |
617 | void ServiceWorkerContainer::removeRegistration(ServiceWorkerRegistration& registration) |
618 | { |
619 | #ifndef NDEBUG |
620 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
621 | #endif |
622 | |
623 | m_swConnection->removeServiceWorkerRegistrationInServer(registration.identifier()); |
624 | m_registrations.remove(registration.identifier()); |
625 | } |
626 | |
627 | void ServiceWorkerContainer::fireControllerChangeEvent() |
628 | { |
629 | #ifndef NDEBUG |
630 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
631 | #endif |
632 | |
633 | if (m_isStopped) |
634 | return; |
635 | |
636 | dispatchEvent(Event::create(eventNames().controllerchangeEvent, Event::CanBubble::No, Event::IsCancelable::No)); |
637 | } |
638 | |
639 | void ServiceWorkerContainer::stop() |
640 | { |
641 | m_isStopped = true; |
642 | removeAllEventListeners(); |
643 | m_pendingPromises.clear(); |
644 | m_readyPromise = nullptr; |
645 | auto jobMap = WTFMove(m_jobMap); |
646 | for (auto& ongoingJob : jobMap.values()) { |
647 | if (ongoingJob.job->cancelPendingLoad()) |
648 | notifyFailedFetchingScript(*ongoingJob.job.get(), ResourceError { errorDomainWebKitInternal, 0, ongoingJob.job->data().scriptURL, "Job cancelled"_s , ResourceError::Type::Cancellation }); |
649 | } |
650 | |
651 | auto registrationMap = WTFMove(m_ongoingSettledRegistrations); |
652 | for (auto& registration : registrationMap.values()) |
653 | notifyRegistrationIsSettled(registration); |
654 | } |
655 | |
656 | DocumentOrWorkerIdentifier ServiceWorkerContainer::contextIdentifier() |
657 | { |
658 | #ifndef NDEBUG |
659 | ASSERT(m_creationThread.ptr() == &Thread::current()); |
660 | #endif |
661 | |
662 | ASSERT(scriptExecutionContext()); |
663 | if (is<ServiceWorkerGlobalScope>(*scriptExecutionContext())) |
664 | return downcast<ServiceWorkerGlobalScope>(*scriptExecutionContext()).thread().identifier(); |
665 | return downcast<Document>(*scriptExecutionContext()).identifier(); |
666 | } |
667 | |
668 | ServiceWorkerJob* ServiceWorkerContainer::job(ServiceWorkerJobIdentifier identifier) |
669 | { |
670 | auto iterator = m_jobMap.find(identifier); |
671 | if (iterator == m_jobMap.end()) |
672 | return nullptr; |
673 | return iterator->value.job.get(); |
674 | } |
675 | |
676 | bool ServiceWorkerContainer::isAlwaysOnLoggingAllowed() const |
677 | { |
678 | auto* context = scriptExecutionContext(); |
679 | if (!context) |
680 | return false; |
681 | |
682 | if (is<Document>(*context)) |
683 | return downcast<Document>(*context).sessionID().isAlwaysOnLoggingAllowed(); |
684 | |
685 | // FIXME: No logging inside service workers for now. |
686 | return false; |
687 | } |
688 | |
689 | } // namespace WebCore |
690 | |
691 | #endif // ENABLE(SERVICE_WORKER) |
692 | |