1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "RemoteWorkerChild.h"
11 #include "MainThreadUtils.h"
15 #include "nsIConsoleReportCollector.h"
16 #include "nsIInterfaceRequestor.h"
17 #include "nsIPrincipal.h"
18 #include "nsNetUtil.h"
19 #include "nsThreadUtils.h"
20 #include "nsXULAppAPI.h"
22 #include "RemoteWorkerService.h"
23 #include "mozilla/ArrayAlgorithm.h"
24 #include "mozilla/Assertions.h"
25 #include "mozilla/Attributes.h"
26 #include "mozilla/BasePrincipal.h"
27 #include "mozilla/ErrorResult.h"
28 #include "mozilla/SchedulerGroup.h"
29 #include "mozilla/Services.h"
30 #include "mozilla/ScopeExit.h"
31 #include "mozilla/Unused.h"
32 #include "mozilla/dom/FetchEventOpProxyChild.h"
33 #include "mozilla/dom/IndexedDatabaseManager.h"
34 #include "mozilla/dom/MessagePort.h"
35 #include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::IsRemoteTypeAllowed
36 #include "mozilla/dom/RemoteWorkerTypes.h"
37 #include "mozilla/dom/ServiceWorkerDescriptor.h"
38 #include "mozilla/dom/ServiceWorkerInterceptController.h"
39 #include "mozilla/dom/ServiceWorkerOp.h"
40 #include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h"
41 #include "mozilla/dom/ServiceWorkerShutdownState.h"
42 #include "mozilla/dom/ServiceWorkerUtils.h"
43 #include "mozilla/dom/workerinternals/ScriptLoader.h"
44 #include "mozilla/dom/WorkerError.h"
45 #include "mozilla/dom/WorkerPrivate.h"
46 #include "mozilla/dom/WorkerRef.h"
47 #include "mozilla/dom/WorkerRunnable.h"
48 #include "mozilla/dom/WorkerScope.h"
49 #include "mozilla/ipc/BackgroundUtils.h"
50 #include "mozilla/ipc/URIUtils.h"
51 #include "mozilla/net/CookieJarSettings.h"
52 #include "mozilla/PermissionManager.h"
54 mozilla::LazyLogModule
gRemoteWorkerChildLog("RemoteWorkerChild");
59 #define LOG(fmt) MOZ_LOG(gRemoteWorkerChildLog, mozilla::LogLevel::Verbose, fmt)
67 using workerinternals::ChannelFromScriptURLMainThread
;
71 class SharedWorkerInterfaceRequestor final
: public nsIInterfaceRequestor
{
75 SharedWorkerInterfaceRequestor() {
76 // This check must match the code nsDocShell::Create.
77 if (XRE_IsParentProcess()) {
78 mSWController
= new ServiceWorkerInterceptController();
83 GetInterface(const nsIID
& aIID
, void** aSink
) override
{
84 MOZ_ASSERT(NS_IsMainThread());
87 aIID
.Equals(NS_GET_IID(nsINetworkInterceptController
))) {
88 // If asked for the network intercept controller, ask the outer requestor,
89 // which could be the docshell.
90 RefPtr
<ServiceWorkerInterceptController
> swController
= mSWController
;
91 swController
.forget(aSink
);
95 return NS_NOINTERFACE
;
99 ~SharedWorkerInterfaceRequestor() = default;
101 RefPtr
<ServiceWorkerInterceptController
> mSWController
;
104 NS_IMPL_ADDREF(SharedWorkerInterfaceRequestor
)
105 NS_IMPL_RELEASE(SharedWorkerInterfaceRequestor
)
106 NS_IMPL_QUERY_INTERFACE(SharedWorkerInterfaceRequestor
, nsIInterfaceRequestor
)
108 // Normal runnable because AddPortIdentifier() is going to exec JS code.
109 class MessagePortIdentifierRunnable final
: public WorkerRunnable
{
111 MessagePortIdentifierRunnable(WorkerPrivate
* aWorkerPrivate
,
112 RemoteWorkerChild
* aActor
,
113 const MessagePortIdentifier
& aPortIdentifier
)
114 : WorkerRunnable(aWorkerPrivate
, "MessagePortIdentifierRunnable"),
116 mPortIdentifier(aPortIdentifier
) {}
119 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
120 if (aWorkerPrivate
->GlobalScope()->IsDying()) {
121 mPortIdentifier
.ForceClose();
124 mActor
->AddPortIdentifier(aCx
, aWorkerPrivate
, mPortIdentifier
);
128 RefPtr
<RemoteWorkerChild
> mActor
;
129 UniqueMessagePortId mPortIdentifier
;
132 // This is used to propagate the CSP violation when loading the SharedWorker
133 // main-script and nothing else.
134 class RemoteWorkerCSPEventListener final
: public nsICSPEventListener
{
138 explicit RemoteWorkerCSPEventListener(RemoteWorkerChild
* aActor
)
141 NS_IMETHOD
OnCSPViolationEvent(const nsAString
& aJSON
) override
{
142 mActor
->CSPViolationPropagationOnMainThread(aJSON
);
147 ~RemoteWorkerCSPEventListener() = default;
149 RefPtr
<RemoteWorkerChild
> mActor
;
152 NS_IMPL_ISUPPORTS(RemoteWorkerCSPEventListener
, nsICSPEventListener
)
154 } // anonymous namespace
156 RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData
& aData
)
157 : mState(VariantType
<Pending
>(), "RemoteWorkerChild::mState"),
158 mServiceKeepAlive(RemoteWorkerService::MaybeGetKeepAlive()),
159 mIsServiceWorker(aData
.serviceWorkerData().type() ==
160 OptionalServiceWorkerData::TServiceWorkerData
) {
161 MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
164 RemoteWorkerChild::~RemoteWorkerChild() {
166 auto lock
= mState
.Lock();
167 MOZ_ASSERT(lock
->is
<Killed
>());
171 void RemoteWorkerChild::ActorDestroy(ActorDestroyReason
) {
172 auto launcherData
= mLauncherData
.Access();
174 Unused
<< NS_WARN_IF(!launcherData
->mTerminationPromise
.IsEmpty());
175 launcherData
->mTerminationPromise
.RejectIfExists(NS_ERROR_DOM_ABORT_ERR
,
178 auto lock
= mState
.Lock();
180 // If the worker hasn't shutdown or begun shutdown, we need to ensure it gets
182 if (NS_WARN_IF(!lock
->is
<Killed
>() && !lock
->is
<Canceled
>())) {
183 // In terms of strong references to this RemoteWorkerChild, at this moment:
184 // - IPC is holding a strong reference that will be dropped in the near
185 // future after this method returns.
186 // - If the worker has been started by ExecWorkerOnMainThread, then
187 // WorkerPrivate::mRemoteWorkerController is a strong reference to us.
188 // If the worker has not been started, ExecWorker's runnable lambda will
189 // have a strong reference that will cover the call to
190 // ExecWorkerOnMainThread.
191 // - The WorkerPrivate cancellation and termination callbacks will also
192 // hold strong references, but those callbacks will not outlive the
193 // WorkerPrivate and are not exposed to callers like
194 // mRemoteWorkerController is.
196 // Note that this call to RequestWorkerCancellation can still race worker
197 // cancellation, in which case the strong reference obtained by
198 // NewRunnableMethod can end up being the last strong reference.
199 // (RequestWorkerCancellation handles the case that the Worker is already
200 // canceled if this happens.)
201 RefPtr
<nsIRunnable
> runnable
=
202 NewRunnableMethod("RequestWorkerCancellation", this,
203 &RemoteWorkerChild::RequestWorkerCancellation
);
204 MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(runnable
.forget()));
208 void RemoteWorkerChild::ExecWorker(const RemoteWorkerData
& aData
) {
210 MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread());
211 auto launcherData
= mLauncherData
.Access();
212 MOZ_ASSERT(CanSend());
215 RefPtr
<RemoteWorkerChild
> self
= this;
217 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
218 __func__
, [self
= std::move(self
), data
= aData
]() mutable {
219 nsresult rv
= self
->ExecWorkerOnMainThread(std::move(data
));
221 // Creation failure will already have been reported via the method
222 // above internally using ScopeExit.
223 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
226 MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r
.forget()));
229 nsresult
RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData
&& aData
) {
230 MOZ_ASSERT(NS_IsMainThread());
232 // Ensure that the IndexedDatabaseManager is initialized so that if any
233 // workers do any IndexedDB calls that all of IDB's prefs/etc. are
235 IndexedDatabaseManager
* idm
= IndexedDatabaseManager::GetOrCreate();
237 Unused
<< NS_WARN_IF(NS_FAILED(idm
->EnsureLocale()));
239 NS_WARNING("Failed to get IndexedDatabaseManager!");
243 MakeScopeExit([&] { ExceptionalErrorTransitionDuringExecWorker(); });
245 // Verify the the RemoteWorker should be really allowed to run in this
246 // process, and fail if it shouldn't (This shouldn't normally happen,
247 // unless the RemoteWorkerData has been tempered in the process it was
249 if (!RemoteWorkerManager::IsRemoteTypeAllowed(aData
)) {
250 return NS_ERROR_UNEXPECTED
;
253 auto principalOrErr
= PrincipalInfoToPrincipal(aData
.principalInfo());
254 if (NS_WARN_IF(principalOrErr
.isErr())) {
255 return principalOrErr
.unwrapErr();
258 nsCOMPtr
<nsIPrincipal
> principal
= principalOrErr
.unwrap();
260 auto loadingPrincipalOrErr
=
261 PrincipalInfoToPrincipal(aData
.loadingPrincipalInfo());
262 if (NS_WARN_IF(loadingPrincipalOrErr
.isErr())) {
263 return loadingPrincipalOrErr
.unwrapErr();
266 auto partitionedPrincipalOrErr
=
267 PrincipalInfoToPrincipal(aData
.partitionedPrincipalInfo());
268 if (NS_WARN_IF(partitionedPrincipalOrErr
.isErr())) {
269 return partitionedPrincipalOrErr
.unwrapErr();
273 info
.mBaseURI
= DeserializeURI(aData
.baseScriptURL());
274 info
.mResolvedScriptURI
= DeserializeURI(aData
.resolvedScriptURL());
276 info
.mPrincipalInfo
= MakeUnique
<PrincipalInfo
>(aData
.principalInfo());
277 info
.mPartitionedPrincipalInfo
=
278 MakeUnique
<PrincipalInfo
>(aData
.partitionedPrincipalInfo());
280 info
.mReferrerInfo
= aData
.referrerInfo();
281 info
.mDomain
= aData
.domain();
282 info
.mTrials
= aData
.originTrials();
283 info
.mPrincipal
= principal
;
284 info
.mPartitionedPrincipal
= partitionedPrincipalOrErr
.unwrap();
285 info
.mLoadingPrincipal
= loadingPrincipalOrErr
.unwrap();
286 info
.mStorageAccess
= aData
.storageAccess();
287 info
.mUseRegularPrincipal
= aData
.useRegularPrincipal();
288 info
.mUsingStorageAccess
= aData
.usingStorageAccess();
289 info
.mIsThirdPartyContextToTopWindow
= aData
.isThirdPartyContextToTopWindow();
290 info
.mOriginAttributes
=
291 BasePrincipal::Cast(principal
)->OriginAttributesRef();
292 info
.mShouldResistFingerprinting
= aData
.shouldResistFingerprinting();
293 Maybe
<RFPTarget
> overriddenFingerprintingSettings
;
294 if (aData
.overriddenFingerprintingSettings().isSome()) {
295 overriddenFingerprintingSettings
.emplace(
296 RFPTarget(aData
.overriddenFingerprintingSettings().ref()));
298 info
.mOverriddenFingerprintingSettings
= overriddenFingerprintingSettings
;
299 net::CookieJarSettings::Deserialize(aData
.cookieJarSettings(),
300 getter_AddRefs(info
.mCookieJarSettings
));
301 info
.mCookieJarSettingsArgs
= aData
.cookieJarSettings();
303 // Default CSP permissions for now. These will be overrided if necessary
304 // based on the script CSP headers during load in ScriptLoader.
305 info
.mEvalAllowed
= true;
306 info
.mReportEvalCSPViolations
= false;
307 info
.mWasmEvalAllowed
= true;
308 info
.mReportWasmEvalCSPViolations
= false;
309 info
.mSecureContext
= aData
.isSecureContext()
310 ? WorkerLoadInfo::eSecureContext
311 : WorkerLoadInfo::eInsecureContext
;
313 WorkerPrivate::OverrideLoadInfoLoadGroup(info
, info
.mLoadingPrincipal
);
315 RefPtr
<SharedWorkerInterfaceRequestor
> requestor
=
316 new SharedWorkerInterfaceRequestor();
317 info
.mInterfaceRequestor
->SetOuterRequestor(requestor
);
319 Maybe
<ClientInfo
> clientInfo
;
320 if (aData
.clientInfo().isSome()) {
321 clientInfo
.emplace(ClientInfo(aData
.clientInfo().ref()));
326 if (clientInfo
.isSome()) {
327 Maybe
<mozilla::ipc::CSPInfo
> cspInfo
= clientInfo
.ref().GetCspInfo();
328 if (cspInfo
.isSome()) {
329 info
.mCSP
= CSPInfoToCSP(cspInfo
.ref(), nullptr);
330 info
.mCSPInfo
= MakeUnique
<CSPInfo
>();
331 rv
= CSPToCSPInfo(info
.mCSP
, info
.mCSPInfo
.get());
332 if (NS_WARN_IF(NS_FAILED(rv
))) {
338 rv
= info
.SetPrincipalsAndCSPOnMainThread(
339 info
.mPrincipal
, info
.mPartitionedPrincipal
, info
.mLoadGroup
, info
.mCSP
);
340 if (NS_WARN_IF(NS_FAILED(rv
))) {
344 nsString workerPrivateId
;
346 if (mIsServiceWorker
) {
347 ServiceWorkerData
& data
= aData
.serviceWorkerData().get_ServiceWorkerData();
349 MOZ_ASSERT(!data
.id().IsEmpty());
350 workerPrivateId
= std::move(data
.id());
352 info
.mServiceWorkerCacheName
= data
.cacheName();
353 info
.mServiceWorkerDescriptor
.emplace(data
.descriptor());
354 info
.mServiceWorkerRegistrationDescriptor
.emplace(
355 data
.registrationDescriptor());
356 info
.mLoadFlags
= static_cast<nsLoadFlags
>(data
.loadFlags());
358 // Top level workers' main script use the document charset for the script
360 rv
= ChannelFromScriptURLMainThread(
361 info
.mLoadingPrincipal
, nullptr /* parent document */, info
.mLoadGroup
,
362 info
.mResolvedScriptURI
, aData
.type(), aData
.credentials(), clientInfo
,
363 nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER
, info
.mCookieJarSettings
,
364 info
.mReferrerInfo
, getter_AddRefs(info
.mChannel
));
365 if (NS_WARN_IF(NS_FAILED(rv
))) {
369 nsCOMPtr
<nsILoadInfo
> loadInfo
= info
.mChannel
->LoadInfo();
371 auto* cspEventListener
= new RemoteWorkerCSPEventListener(this);
372 rv
= loadInfo
->SetCspEventListener(cspEventListener
);
373 if (NS_WARN_IF(NS_FAILED(rv
))) {
378 info
.mAgentClusterId
= aData
.agentClusterId();
384 RefPtr
<RemoteWorkerChild
> self
= this;
385 RefPtr
<WorkerPrivate
> workerPrivate
= WorkerPrivate::Constructor(
386 jsapi
.cx(), aData
.originalScriptURL(), false,
387 mIsServiceWorker
? WorkerKindService
: WorkerKindShared
,
388 aData
.credentials(), aData
.type(), aData
.name(), VoidCString(), &info
,
389 error
, std::move(workerPrivateId
),
390 [self
](bool aEverRan
) {
391 self
->OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled();
393 // This will be invoked here on the main thread when the worker is already
394 // fully shutdown. This replaces a prior approach where we would
395 // begin to transition when the worker thread would reach the Canceling
396 // state. This lambda ensures that we not only wait for the Killing state
397 // to be reached but that the global shutdown has already occurred.
398 [self
]() { self
->TransitionStateFromCanceledToKilled(); });
400 if (NS_WARN_IF(error
.Failed())) {
401 MOZ_ASSERT(!workerPrivate
);
403 rv
= error
.StealNSResult();
407 workerPrivate
->SetRemoteWorkerController(this);
409 // This wants to run as a normal task sequentially after the top level script
410 // evaluation, so the hybrid target is the correct choice between hybrid and
411 // `ControlEventTarget`.
412 nsCOMPtr
<nsISerialEventTarget
> workerTarget
=
413 workerPrivate
->HybridEventTarget();
415 nsCOMPtr
<nsIRunnable
> runnable
= NewCancelableRunnableMethod(
416 "InitialzeOnWorker", this, &RemoteWorkerChild::InitializeOnWorker
);
419 MOZ_ASSERT(workerPrivate
);
420 auto lock
= mState
.Lock();
421 // We MUST be pending here, so direct access is ok.
422 lock
->as
<Pending
>().mWorkerPrivate
= std::move(workerPrivate
);
425 if (mIsServiceWorker
) {
426 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
427 __func__
, [workerTarget
,
428 initializeWorkerRunnable
= std::move(runnable
)]() mutable {
429 Unused
<< NS_WARN_IF(NS_FAILED(
430 workerTarget
->Dispatch(initializeWorkerRunnable
.forget())));
433 RefPtr
<PermissionManager
> permissionManager
=
434 PermissionManager::GetInstance();
435 if (!permissionManager
) {
436 return NS_ERROR_FAILURE
;
438 permissionManager
->WhenPermissionsAvailable(principal
, r
);
440 if (NS_WARN_IF(NS_FAILED(workerTarget
->Dispatch(runnable
.forget())))) {
441 rv
= NS_ERROR_FAILURE
;
451 void RemoteWorkerChild::RequestWorkerCancellation() {
452 MOZ_ASSERT(NS_IsMainThread());
454 LOG(("RequestWorkerCancellation[this=%p]", this));
456 // We want to ensure that we've requested the worker be canceled. So if the
457 // worker is running, cancel it. We can't do this with the lock held,
458 // however, because our lambdas will want to manipulate the state.
459 RefPtr
<WorkerPrivate
> cancelWith
;
461 auto lock
= mState
.Lock();
462 if (lock
->is
<Pending
>()) {
463 cancelWith
= lock
->as
<Pending
>().mWorkerPrivate
;
464 } else if (lock
->is
<Running
>()) {
465 cancelWith
= lock
->as
<Running
>().mWorkerPrivate
;
470 cancelWith
->Cancel();
474 // This method will be invoked on the worker after the top-level
475 // CompileScriptRunnable task has succeeded and as long as the worker has not
476 // been closed/canceled. There are edge-cases related to cancellation, but we
477 // have our caller ensure that we are only called as long as the worker's state
480 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1800659 will eliminate
481 // cancellation, and the documentation around that bug / in design documents
482 // helps provide more context about this.)
483 void RemoteWorkerChild::InitializeOnWorker() {
484 nsCOMPtr
<nsIRunnable
> r
=
485 NewRunnableMethod("TransitionStateToRunning", this,
486 &RemoteWorkerChild::TransitionStateToRunning
);
487 MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r
.forget()));
490 RefPtr
<GenericNonExclusivePromise
> RemoteWorkerChild::GetTerminationPromise() {
491 auto launcherData
= mLauncherData
.Access();
492 return launcherData
->mTerminationPromise
.Ensure(__func__
);
495 void RemoteWorkerChild::CreationSucceededOnAnyThread() {
496 CreationSucceededOrFailedOnAnyThread(true);
499 void RemoteWorkerChild::CreationFailedOnAnyThread() {
500 CreationSucceededOrFailedOnAnyThread(false);
503 void RemoteWorkerChild::CreationSucceededOrFailedOnAnyThread(
504 bool aDidCreationSucceed
) {
507 auto lock
= mState
.Lock();
508 MOZ_ASSERT_IF(aDidCreationSucceed
, lock
->is
<Running
>());
509 MOZ_ASSERT_IF(!aDidCreationSucceed
, lock
->is
<Killed
>());
513 RefPtr
<RemoteWorkerChild
> self
= this;
515 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
517 [self
= std::move(self
), didCreationSucceed
= aDidCreationSucceed
] {
518 auto launcherData
= self
->mLauncherData
.Access();
520 if (!self
->CanSend() || launcherData
->mDidSendCreated
) {
524 Unused
<< self
->SendCreated(didCreationSucceed
);
525 launcherData
->mDidSendCreated
= true;
528 GetActorEventTarget()->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
531 void RemoteWorkerChild::CloseWorkerOnMainThread() {
532 AssertIsOnMainThread();
534 LOG(("CloseWorkerOnMainThread[this=%p]", this));
536 // We can't hold the state lock while calling WorkerPrivate::Cancel because
537 // the lambda callback will want to touch the state, so save off the
538 // WorkerPrivate so we can cancel it (if we need to cancel it).
539 RefPtr
<WorkerPrivate
> cancelWith
;
541 auto lock
= mState
.Lock();
543 if (lock
->is
<Pending
>()) {
544 cancelWith
= lock
->as
<Pending
>().mWorkerPrivate
;
545 // There should be no way for this code to run before we
546 // ExecWorkerOnMainThread runs, which means that either it should have
547 // set a WorkerPrivate on Pending, or its error handling should already
548 // have transitioned us to Canceled and Killing in that order. (It's
549 // also possible that it assigned a WorkerPrivate and subsequently we
550 // transitioned to Running, which would put us in the next branch.)
551 MOZ_DIAGNOSTIC_ASSERT(cancelWith
);
552 } else if (lock
->is
<Running
>()) {
553 cancelWith
= lock
->as
<Running
>().mWorkerPrivate
;
557 // It's very okay for us to not have a WorkerPrivate here if we've already
558 // canceled the worker or if errors happened.
560 cancelWith
->Cancel();
565 * Error reporting method
567 void RemoteWorkerChild::ErrorPropagation(const ErrorValue
& aValue
) {
568 MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread());
574 Unused
<< SendError(aValue
);
577 void RemoteWorkerChild::ErrorPropagationDispatch(nsresult aError
) {
578 MOZ_ASSERT(NS_FAILED(aError
));
580 RefPtr
<RemoteWorkerChild
> self
= this;
581 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
582 "RemoteWorkerChild::ErrorPropagationDispatch",
583 [self
= std::move(self
), aError
]() { self
->ErrorPropagation(aError
); });
585 GetActorEventTarget()->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
588 void RemoteWorkerChild::ErrorPropagationOnMainThread(
589 const WorkerErrorReport
* aReport
, bool aIsErrorEvent
) {
590 AssertIsOnMainThread();
595 aReport
->mIsWarning
, aReport
->mLineNumber
, aReport
->mColumnNumber
,
596 aReport
->mMessage
, aReport
->mFilename
, aReport
->mLine
,
597 TransformIntoNewArray(aReport
->mNotes
, [](const WorkerErrorNote
& note
) {
598 return ErrorDataNote(note
.mLineNumber
, note
.mColumnNumber
,
599 note
.mMessage
, note
.mFilename
);
606 RefPtr
<RemoteWorkerChild
> self
= this;
607 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
608 "RemoteWorkerChild::ErrorPropagationOnMainThread",
609 [self
= std::move(self
), value
]() { self
->ErrorPropagation(value
); });
611 GetActorEventTarget()->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
614 void RemoteWorkerChild::CSPViolationPropagationOnMainThread(
615 const nsAString
& aJSON
) {
616 AssertIsOnMainThread();
618 RefPtr
<RemoteWorkerChild
> self
= this;
619 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
620 "RemoteWorkerChild::ErrorPropagationDispatch",
621 [self
= std::move(self
), json
= nsString(aJSON
)]() {
622 CSPViolation
violation(json
);
623 self
->ErrorPropagation(violation
);
626 GetActorEventTarget()->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
629 void RemoteWorkerChild::NotifyLock(bool aCreated
) {
630 nsCOMPtr
<nsIRunnable
> r
=
631 NS_NewRunnableFunction(__func__
, [self
= RefPtr(this), aCreated
] {
632 if (!self
->CanSend()) {
636 Unused
<< self
->SendNotifyLock(aCreated
);
639 GetActorEventTarget()->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
642 void RemoteWorkerChild::NotifyWebTransport(bool aCreated
) {
643 nsCOMPtr
<nsIRunnable
> r
=
644 NS_NewRunnableFunction(__func__
, [self
= RefPtr(this), aCreated
] {
645 if (!self
->CanSend()) {
649 Unused
<< self
->SendNotifyWebTransport(aCreated
);
652 GetActorEventTarget()->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
655 void RemoteWorkerChild::FlushReportsOnMainThread(
656 nsIConsoleReportCollector
* aReporter
) {
657 AssertIsOnMainThread();
659 bool reportErrorToBrowserConsole
= true;
661 // Flush the reports.
662 for (uint32_t i
= 0, len
= mWindowIDs
.Length(); i
< len
; ++i
) {
663 aReporter
->FlushReportsToConsole(
664 mWindowIDs
[i
], nsIConsoleReportCollector::ReportAction::Save
);
665 reportErrorToBrowserConsole
= false;
668 // Finally report to browser console if there is no any window.
669 if (reportErrorToBrowserConsole
) {
670 aReporter
->FlushReportsToConsole(0);
674 aReporter
->ClearConsoleReports();
678 * Worker state transition methods
680 RemoteWorkerChild::WorkerPrivateAccessibleState::
681 ~WorkerPrivateAccessibleState() {
682 // We should now only be performing state transitions on the main thread, so
683 // we should assert we're only releasing on the main thread.
684 MOZ_ASSERT(!mWorkerPrivate
|| NS_IsMainThread());
685 // mWorkerPrivate can be safely released on the main thread.
686 if (!mWorkerPrivate
|| NS_IsMainThread()) {
690 // But as a backstop, do proxy the release to the main thread.
691 NS_ReleaseOnMainThread(
692 "RemoteWorkerChild::WorkerPrivateAccessibleState::mWorkerPrivate",
693 mWorkerPrivate
.forget());
696 void RemoteWorkerChild::
697 OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled() {
698 auto lock
= mState
.Lock();
700 LOG(("TransitionStateFromPendingOrRunningToCanceled[this=%p]", this));
702 if (lock
->is
<Pending
>()) {
703 TransitionStateFromPendingToCanceled(lock
.ref());
704 } else if (lock
->is
<Running
>()) {
705 *lock
= VariantType
<Canceled
>();
707 MOZ_ASSERT(false, "State should have been Pending or Running");
711 void RemoteWorkerChild::TransitionStateFromPendingToCanceled(State
& aState
) {
712 AssertIsOnMainThread();
713 MOZ_ASSERT(aState
.is
<Pending
>());
714 LOG(("TransitionStateFromPendingToCanceled[this=%p]", this));
716 CancelAllPendingOps(aState
);
718 aState
= VariantType
<Canceled
>();
721 void RemoteWorkerChild::TransitionStateFromCanceledToKilled() {
722 AssertIsOnMainThread();
724 LOG(("TransitionStateFromCanceledToKilled[this=%p]", this));
726 auto lock
= mState
.Lock();
727 MOZ_ASSERT(lock
->is
<Canceled
>());
729 *lock
= VariantType
<Killed
>();
731 RefPtr
<RemoteWorkerChild
> self
= this;
732 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(__func__
, [self
]() {
733 auto launcherData
= self
->mLauncherData
.Access();
735 // (We maintain the historical ordering of resolving this promise prior to
736 // calling SendClose, however the previous code used 2 separate dispatches
737 // to this thread for the resolve and SendClose, and there inherently
738 // would be a race between the runnables resulting from the resolved
739 // promise and the promise containing the call to SendClose. Now it's
740 // entirely clear that our call to SendClose will effectively run before
741 // any of the resolved promises are able to do anything.)
742 launcherData
->mTerminationPromise
.ResolveIfExists(true, __func__
);
744 if (self
->CanSend()) {
745 Unused
<< self
->SendClose();
749 GetActorEventTarget()->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
752 void RemoteWorkerChild::TransitionStateToRunning() {
753 AssertIsOnMainThread();
755 LOG(("TransitionStateToRunning[this=%p]", this));
757 nsTArray
<RefPtr
<Op
>> pendingOps
;
760 auto lock
= mState
.Lock();
762 // Because this is an async notification sent from the worker to the main
763 // thread, it's very possible that we've already decided on the main thread
764 // to transition to the Canceled state, in which case there is nothing for
766 if (!lock
->is
<Pending
>()) {
767 LOG(("State is already not pending in TransitionStateToRunning[this=%p]!",
772 RefPtr
<WorkerPrivate
> workerPrivate
=
773 std::move(lock
->as
<Pending
>().mWorkerPrivate
);
774 pendingOps
= std::move(lock
->as
<Pending
>().mPendingOps
);
776 // Move the worker private into place to avoid gratuitous ref churn; prior
777 // comments here suggest the Variant can't accept a move.
778 *lock
= VariantType
<Running
>();
779 lock
->as
<Running
>().mWorkerPrivate
= std::move(workerPrivate
);
782 CreationSucceededOnAnyThread();
784 RefPtr
<RemoteWorkerChild
> self
= this;
785 for (auto& op
: pendingOps
) {
786 op
->StartOnMainThread(self
);
790 void RemoteWorkerChild::ExceptionalErrorTransitionDuringExecWorker() {
791 AssertIsOnMainThread();
793 LOG(("ExceptionalErrorTransitionDuringExecWorker[this=%p]", this));
795 // This method is called synchronously by ExecWorkerOnMainThread in the event
796 // of any error. Because we only transition to Running on the main thread
797 // as the result of a notification from the worker, we know our state will be
798 // Pending, but mWorkerPrivate may or may not be null, as we may not have
799 // gotten to spawning the worker.
801 // In the event the worker exists, we need to Cancel() it. We must do this
802 // without the lock held because our call to Cancel() will invoke the
803 // cancellation callback we created which will call TransitionStateToCanceled,
804 // and we can't be holding the lock when that happens.
806 RefPtr
<WorkerPrivate
> cancelWith
;
809 auto lock
= mState
.Lock();
811 MOZ_ASSERT(lock
->is
<Pending
>());
812 if (lock
->is
<Pending
>()) {
813 cancelWith
= lock
->as
<Pending
>().mWorkerPrivate
;
815 // The worker wasn't actually created, so we should synthetically
816 // transition to canceled and onward. Since we have the lock,
817 // perform the transition now for clarity, but we'll handle the rest of
818 // this case after dropping the lock.
819 TransitionStateFromPendingToCanceled(lock
.ref());
825 cancelWith
->Cancel();
827 TransitionStateFromCanceledToKilled();
828 CreationFailedOnAnyThread();
833 * Operation execution classes/methods
835 class RemoteWorkerChild::SharedWorkerOp
: public RemoteWorkerChild::Op
{
837 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerOp
, override
)
839 explicit SharedWorkerOp(RemoteWorkerOp
&& aOp
) : mOp(std::move(aOp
)) {}
841 bool MaybeStart(RemoteWorkerChild
* aOwner
,
842 RemoteWorkerChild::State
& aState
) override
{
843 MOZ_ASSERT(!mStarted
);
845 // Thread: We are on the Worker Launcher thread.
847 // Return false, indicating we should queue this op if our current state is
848 // pending and this isn't a termination op (which should skip the line).
849 if (aState
.is
<Pending
>() && !IsTerminationOp()) {
853 // If the worker is already shutting down (which should be unexpected
854 // because we should be told new operations after a termination op), just
855 // return true to indicate the op should be discarded.
856 if (aState
.is
<Canceled
>() || aState
.is
<Killed
>()) {
860 if (mOp
.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp
) {
861 MessagePort::ForceClose(
862 mOp
.get_RemoteWorkerPortIdentifierOp().portIdentifier());
867 MOZ_ASSERT(aState
.is
<Running
>() || IsTerminationOp());
869 RefPtr
<SharedWorkerOp
> self
= this;
870 RefPtr
<RemoteWorkerChild
> owner
= aOwner
;
872 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
873 __func__
, [self
= std::move(self
), owner
= std::move(owner
)]() mutable {
875 auto lock
= owner
->mState
.Lock();
877 if (NS_WARN_IF(lock
->is
<Canceled
>() || lock
->is
<Killed
>())) {
879 // Worker has already canceled, force close the MessagePort.
880 if (self
->mOp
.type() ==
881 RemoteWorkerOp::TRemoteWorkerPortIdentifierOp
) {
882 MessagePort::ForceClose(
883 self
->mOp
.get_RemoteWorkerPortIdentifierOp()
890 self
->StartOnMainThread(owner
);
893 MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r
.forget()));
902 void StartOnMainThread(RefPtr
<RemoteWorkerChild
>& aOwner
) final
{
903 using Running
= RemoteWorkerChild::Running
;
905 AssertIsOnMainThread();
907 if (IsTerminationOp()) {
908 aOwner
->CloseWorkerOnMainThread();
912 auto lock
= aOwner
->mState
.Lock();
913 MOZ_ASSERT(lock
->is
<Running
>());
914 if (!lock
->is
<Running
>()) {
915 aOwner
->ErrorPropagationDispatch(NS_ERROR_DOM_INVALID_STATE_ERR
);
919 RefPtr
<WorkerPrivate
> workerPrivate
= lock
->as
<Running
>().mWorkerPrivate
;
921 MOZ_ASSERT(workerPrivate
);
923 if (mOp
.type() == RemoteWorkerOp::TRemoteWorkerSuspendOp
) {
924 workerPrivate
->ParentWindowPaused();
925 } else if (mOp
.type() == RemoteWorkerOp::TRemoteWorkerResumeOp
) {
926 workerPrivate
->ParentWindowResumed();
927 } else if (mOp
.type() == RemoteWorkerOp::TRemoteWorkerFreezeOp
) {
928 workerPrivate
->Freeze(nullptr);
929 } else if (mOp
.type() == RemoteWorkerOp::TRemoteWorkerThawOp
) {
930 workerPrivate
->Thaw(nullptr);
931 } else if (mOp
.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp
) {
932 RefPtr
<MessagePortIdentifierRunnable
> r
=
933 new MessagePortIdentifierRunnable(
934 workerPrivate
, aOwner
,
935 mOp
.get_RemoteWorkerPortIdentifierOp().portIdentifier());
936 if (NS_WARN_IF(!r
->Dispatch())) {
937 aOwner
->ErrorPropagationDispatch(NS_ERROR_FAILURE
);
939 } else if (mOp
.type() == RemoteWorkerOp::TRemoteWorkerAddWindowIDOp
) {
940 aOwner
->mWindowIDs
.AppendElement(
941 mOp
.get_RemoteWorkerAddWindowIDOp().windowID());
942 } else if (mOp
.type() == RemoteWorkerOp::TRemoteWorkerRemoveWindowIDOp
) {
943 aOwner
->mWindowIDs
.RemoveElement(
944 mOp
.get_RemoteWorkerRemoveWindowIDOp().windowID());
946 MOZ_CRASH("Unknown RemoteWorkerOp type!");
950 void Cancel() override
{
957 ~SharedWorkerOp() { MOZ_ASSERT(mStarted
); }
959 bool IsTerminationOp() const {
960 return mOp
.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp
;
966 bool mStarted
= false;
970 void RemoteWorkerChild::AddPortIdentifier(
971 JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
,
972 UniqueMessagePortId
& aPortIdentifier
) {
973 if (NS_WARN_IF(!aWorkerPrivate
->ConnectMessagePort(aCx
, aPortIdentifier
))) {
974 ErrorPropagationDispatch(NS_ERROR_FAILURE
);
978 void RemoteWorkerChild::CancelAllPendingOps(State
& aState
) {
979 MOZ_ASSERT(aState
.is
<Pending
>());
981 auto pendingOps
= std::move(aState
.as
<Pending
>().mPendingOps
);
983 for (auto& op
: pendingOps
) {
988 void RemoteWorkerChild::MaybeStartOp(RefPtr
<Op
>&& aOp
) {
991 auto lock
= mState
.Lock();
993 if (!aOp
->MaybeStart(this, lock
.ref())) {
994 // Maybestart returns false only if we are <Pending>.
995 lock
->as
<Pending
>().mPendingOps
.AppendElement(std::move(aOp
));
999 IPCResult
RemoteWorkerChild::RecvExecOp(RemoteWorkerOp
&& aOp
) {
1000 MOZ_ASSERT(!mIsServiceWorker
);
1002 MaybeStartOp(new SharedWorkerOp(std::move(aOp
)));
1007 IPCResult
RemoteWorkerChild::RecvExecServiceWorkerOp(
1008 ServiceWorkerOpArgs
&& aArgs
, ExecServiceWorkerOpResolver
&& aResolve
) {
1009 MOZ_ASSERT(mIsServiceWorker
);
1012 ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs
,
1013 "FetchEvent operations should be sent via PFetchEventOp(Proxy) actors!");
1015 MaybeReportServiceWorkerShutdownProgress(aArgs
);
1017 MaybeStartOp(ServiceWorkerOp::Create(std::move(aArgs
), std::move(aResolve
)));
1022 RefPtr
<GenericPromise
>
1023 RemoteWorkerChild::MaybeSendSetServiceWorkerSkipWaitingFlag() {
1024 RefPtr
<GenericPromise::Private
> promise
=
1025 new GenericPromise::Private(__func__
);
1027 RefPtr
<RemoteWorkerChild
> self
= this;
1029 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(__func__
, [self
= std::move(
1032 if (!self
->CanSend()) {
1033 promise
->Reject(NS_ERROR_DOM_ABORT_ERR
, __func__
);
1037 self
->SendSetServiceWorkerSkipWaitingFlag()->Then(
1038 GetCurrentSerialEventTarget(), __func__
,
1040 const SetServiceWorkerSkipWaitingFlagPromise::ResolveOrRejectValue
&
1042 if (NS_WARN_IF(aResult
.IsReject())) {
1043 promise
->Reject(NS_ERROR_DOM_ABORT_ERR
, __func__
);
1047 promise
->Resolve(aResult
.ResolveValue(), __func__
);
1051 GetActorEventTarget()->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
1057 * PFetchEventOpProxy methods
1059 already_AddRefed
<PFetchEventOpProxyChild
>
1060 RemoteWorkerChild::AllocPFetchEventOpProxyChild(
1061 const ParentToChildServiceWorkerFetchEventOpArgs
& aArgs
) {
1062 return RefPtr
{new FetchEventOpProxyChild()}.forget();
1065 IPCResult
RemoteWorkerChild::RecvPFetchEventOpProxyConstructor(
1066 PFetchEventOpProxyChild
* aActor
,
1067 const ParentToChildServiceWorkerFetchEventOpArgs
& aArgs
) {
1070 (static_cast<FetchEventOpProxyChild
*>(aActor
))->Initialize(aArgs
);
1076 } // namespace mozilla