/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "RemoteWorkerChild.h" #include #include "MainThreadUtils.h" #include "nsCOMPtr.h" #include "nsDebug.h" #include "nsError.h" #include "nsIConsoleReportCollector.h" #include "nsIInterfaceRequestor.h" #include "nsIPrincipal.h" #include "nsNetUtil.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" #include "RemoteWorkerService.h" #include "mozilla/ArrayAlgorithm.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/BasePrincipal.h" #include "mozilla/ErrorResult.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/Services.h" #include "mozilla/ScopeExit.h" #include "mozilla/Unused.h" #include "mozilla/dom/FetchEventOpProxyChild.h" #include "mozilla/dom/IndexedDatabaseManager.h" #include "mozilla/dom/MessagePort.h" #include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::IsRemoteTypeAllowed #include "mozilla/dom/RemoteWorkerTypes.h" #include "mozilla/dom/ServiceWorkerDescriptor.h" #include "mozilla/dom/ServiceWorkerInterceptController.h" #include "mozilla/dom/ServiceWorkerOp.h" #include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h" #include "mozilla/dom/ServiceWorkerShutdownState.h" #include "mozilla/dom/ServiceWorkerUtils.h" #include "mozilla/dom/workerinternals/ScriptLoader.h" #include "mozilla/dom/WorkerError.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/ipc/URIUtils.h" #include "mozilla/net/CookieJarSettings.h" #include "mozilla/PermissionManager.h" mozilla::LazyLogModule gRemoteWorkerChildLog("RemoteWorkerChild"); #ifdef LOG # undef LOG #endif #define LOG(fmt) MOZ_LOG(gRemoteWorkerChildLog, mozilla::LogLevel::Verbose, fmt) namespace mozilla { using namespace ipc; namespace dom { using workerinternals::ChannelFromScriptURLMainThread; namespace { class SharedWorkerInterfaceRequestor final : public nsIInterfaceRequestor { public: NS_DECL_ISUPPORTS SharedWorkerInterfaceRequestor() { // This check must match the code nsDocShell::Create. if (XRE_IsParentProcess()) { mSWController = new ServiceWorkerInterceptController(); } } NS_IMETHOD GetInterface(const nsIID& aIID, void** aSink) override { MOZ_ASSERT(NS_IsMainThread()); if (mSWController && aIID.Equals(NS_GET_IID(nsINetworkInterceptController))) { // If asked for the network intercept controller, ask the outer requestor, // which could be the docshell. RefPtr swController = mSWController; swController.forget(aSink); return NS_OK; } return NS_NOINTERFACE; } private: ~SharedWorkerInterfaceRequestor() = default; RefPtr mSWController; }; NS_IMPL_ADDREF(SharedWorkerInterfaceRequestor) NS_IMPL_RELEASE(SharedWorkerInterfaceRequestor) NS_IMPL_QUERY_INTERFACE(SharedWorkerInterfaceRequestor, nsIInterfaceRequestor) // Normal runnable because AddPortIdentifier() is going to exec JS code. class MessagePortIdentifierRunnable final : public WorkerRunnable { public: MessagePortIdentifierRunnable(WorkerPrivate* aWorkerPrivate, RemoteWorkerChild* aActor, const MessagePortIdentifier& aPortIdentifier) : WorkerRunnable(aWorkerPrivate), mActor(aActor), mPortIdentifier(aPortIdentifier) {} private: bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mActor->AddPortIdentifier(aCx, aWorkerPrivate, mPortIdentifier); return true; } RefPtr mActor; UniqueMessagePortId mPortIdentifier; }; } // anonymous namespace RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData& aData) : mState(VariantType(), "RemoteWorkerChild::mState"), mServiceKeepAlive(RemoteWorkerService::MaybeGetKeepAlive()), mIsServiceWorker(aData.serviceWorkerData().type() == OptionalServiceWorkerData::TServiceWorkerData) { MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); } RemoteWorkerChild::~RemoteWorkerChild() { #ifdef DEBUG auto lock = mState.Lock(); MOZ_ASSERT(lock->is()); #endif } void RemoteWorkerChild::ActorDestroy(ActorDestroyReason) { auto launcherData = mLauncherData.Access(); Unused << NS_WARN_IF(!launcherData->mTerminationPromise.IsEmpty()); launcherData->mTerminationPromise.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__); auto lock = mState.Lock(); // If the worker hasn't shutdown or begun shutdown, we need to ensure it gets // canceled. if (NS_WARN_IF(!lock->is() && !lock->is())) { // In terms of strong references to this RemoteWorkerChild, at this moment: // - IPC is holding a strong reference that will be dropped in the near // future after this method returns. // - If the worker has been started by ExecWorkerOnMainThread, then // WorkerPrivate::mRemoteWorkerController is a strong reference to us. // If the worker has not been started, ExecWorker's runnable lambda will // have a strong reference that will cover the call to // ExecWorkerOnMainThread. // - The WorkerPrivate cancellation and termination callbacks will also // hold strong references, but those callbacks will not outlive the // WorkerPrivate and are not exposed to callers like // mRemoteWorkerController is. // // Note that this call to RequestWorkerCancellation can still race worker // cancellation, in which case the strong reference obtained by // NewRunnableMethod can end up being the last strong reference. // (RequestWorkerCancellation handles the case that the Worker is already // canceled if this happens.) RefPtr runnable = NewRunnableMethod("RequestWorkerCancellation", this, &RemoteWorkerChild::RequestWorkerCancellation); MOZ_ALWAYS_SUCCEEDS( SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget())); } } void RemoteWorkerChild::ExecWorker(const RemoteWorkerData& aData) { #ifdef DEBUG MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread()); auto launcherData = mLauncherData.Access(); MOZ_ASSERT(CanSend()); #endif RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction( __func__, [self = std::move(self), data = aData]() mutable { nsresult rv = self->ExecWorkerOnMainThread(std::move(data)); // Creation failure will already have been reported via the method // above internally using ScopeExit. Unused << NS_WARN_IF(NS_FAILED(rv)); }); MOZ_ALWAYS_SUCCEEDS( SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); } nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) { MOZ_ASSERT(NS_IsMainThread()); // Ensure that the IndexedDatabaseManager is initialized so that if any // workers do any IndexedDB calls that all of IDB's prefs/etc. are // initialized. Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate()); auto scopeExit = MakeScopeExit([&] { ExceptionalErrorTransitionDuringExecWorker(); }); // Verify the the RemoteWorker should be really allowed to run in this // process, and fail if it shouldn't (This shouldn't normally happen, // unless the RemoteWorkerData has been tempered in the process it was // sent from). if (!RemoteWorkerManager::IsRemoteTypeAllowed(aData)) { return NS_ERROR_UNEXPECTED; } auto principalOrErr = PrincipalInfoToPrincipal(aData.principalInfo()); if (NS_WARN_IF(principalOrErr.isErr())) { return principalOrErr.unwrapErr(); } nsCOMPtr principal = principalOrErr.unwrap(); auto loadingPrincipalOrErr = PrincipalInfoToPrincipal(aData.loadingPrincipalInfo()); if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) { return loadingPrincipalOrErr.unwrapErr(); } auto partitionedPrincipalOrErr = PrincipalInfoToPrincipal(aData.partitionedPrincipalInfo()); if (NS_WARN_IF(partitionedPrincipalOrErr.isErr())) { return partitionedPrincipalOrErr.unwrapErr(); } WorkerLoadInfo info; info.mBaseURI = DeserializeURI(aData.baseScriptURL()); info.mResolvedScriptURI = DeserializeURI(aData.resolvedScriptURL()); info.mPrincipalInfo = MakeUnique(aData.principalInfo()); info.mPartitionedPrincipalInfo = MakeUnique(aData.partitionedPrincipalInfo()); info.mReferrerInfo = aData.referrerInfo(); info.mDomain = aData.domain(); info.mTrials = aData.originTrials(); info.mPrincipal = principal; info.mPartitionedPrincipal = partitionedPrincipalOrErr.unwrap(); info.mLoadingPrincipal = loadingPrincipalOrErr.unwrap(); info.mStorageAccess = aData.storageAccess(); info.mUseRegularPrincipal = aData.useRegularPrincipal(); info.mHasStorageAccessPermissionGranted = aData.hasStorageAccessPermissionGranted(); info.mIsThirdPartyContextToTopWindow = aData.isThirdPartyContextToTopWindow(); info.mOriginAttributes = BasePrincipal::Cast(principal)->OriginAttributesRef(); info.mShouldResistFingerprinting = aData.shouldResistFingerprinting(); net::CookieJarSettings::Deserialize(aData.cookieJarSettings(), getter_AddRefs(info.mCookieJarSettings)); info.mCookieJarSettingsArgs = aData.cookieJarSettings(); // Default CSP permissions for now. These will be overrided if necessary // based on the script CSP headers during load in ScriptLoader. info.mEvalAllowed = true; info.mReportEvalCSPViolations = false; info.mWasmEvalAllowed = true; info.mReportWasmEvalCSPViolations = false; info.mSecureContext = aData.isSecureContext() ? WorkerLoadInfo::eSecureContext : WorkerLoadInfo::eInsecureContext; WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mLoadingPrincipal); RefPtr requestor = new SharedWorkerInterfaceRequestor(); info.mInterfaceRequestor->SetOuterRequestor(requestor); Maybe clientInfo; if (aData.clientInfo().isSome()) { clientInfo.emplace(ClientInfo(aData.clientInfo().ref())); } nsresult rv = NS_OK; if (clientInfo.isSome()) { Maybe cspInfo = clientInfo.ref().GetCspInfo(); if (cspInfo.isSome()) { info.mCSP = CSPInfoToCSP(cspInfo.ref(), nullptr); info.mCSPInfo = MakeUnique(); rv = CSPToCSPInfo(info.mCSP, info.mCSPInfo.get()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } rv = info.SetPrincipalsAndCSPOnMainThread( info.mPrincipal, info.mPartitionedPrincipal, info.mLoadGroup, info.mCSP); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsString workerPrivateId; if (mIsServiceWorker) { ServiceWorkerData& data = aData.serviceWorkerData().get_ServiceWorkerData(); MOZ_ASSERT(!data.id().IsEmpty()); workerPrivateId = std::move(data.id()); info.mServiceWorkerCacheName = data.cacheName(); info.mServiceWorkerDescriptor.emplace(data.descriptor()); info.mServiceWorkerRegistrationDescriptor.emplace( data.registrationDescriptor()); info.mLoadFlags = static_cast(data.loadFlags()); } else { // Top level workers' main script use the document charset for the script // uri encoding. rv = ChannelFromScriptURLMainThread( info.mLoadingPrincipal, nullptr /* parent document */, info.mLoadGroup, info.mResolvedScriptURI, aData.type(), aData.credentials(), clientInfo, nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER, info.mCookieJarSettings, info.mReferrerInfo, getter_AddRefs(info.mChannel)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } info.mAgentClusterId = aData.agentClusterId(); AutoJSAPI jsapi; jsapi.Init(); ErrorResult error; RefPtr self = this; RefPtr workerPrivate = WorkerPrivate::Constructor( jsapi.cx(), aData.originalScriptURL(), false, mIsServiceWorker ? WorkerKindService : WorkerKindShared, aData.credentials(), aData.type(), aData.name(), VoidCString(), &info, error, std::move(workerPrivateId), [self](bool aEverRan) { self->OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled(); }, // This will be invoked here on the main thread when the worker is already // fully shutdown. This replaces a prior approach where we would // begin to transition when the worker thread would reach the Canceling // state. This lambda ensures that we not only wait for the Killing state // to be reached but that the global shutdown has already occurred. [self]() { self->TransitionStateFromCanceledToKilled(); }); if (NS_WARN_IF(error.Failed())) { MOZ_ASSERT(!workerPrivate); rv = error.StealNSResult(); return rv; } workerPrivate->SetRemoteWorkerController(this); // This wants to run as a normal task sequentially after the top level script // evaluation, so the hybrid target is the correct choice between hybrid and // `ControlEventTarget`. nsCOMPtr workerTarget = workerPrivate->HybridEventTarget(); nsCOMPtr runnable = NewCancelableRunnableMethod( "InitialzeOnWorker", this, &RemoteWorkerChild::InitializeOnWorker); { MOZ_ASSERT(workerPrivate); auto lock = mState.Lock(); // We MUST be pending here, so direct access is ok. lock->as().mWorkerPrivate = std::move(workerPrivate); } if (mIsServiceWorker) { nsCOMPtr r = NS_NewRunnableFunction( __func__, [workerTarget, initializeWorkerRunnable = std::move(runnable)]() mutable { Unused << NS_WARN_IF(NS_FAILED( workerTarget->Dispatch(initializeWorkerRunnable.forget()))); }); RefPtr permissionManager = PermissionManager::GetInstance(); if (!permissionManager) { return NS_ERROR_FAILURE; } permissionManager->WhenPermissionsAvailable(principal, r); } else { if (NS_WARN_IF(NS_FAILED(workerTarget->Dispatch(runnable.forget())))) { rv = NS_ERROR_FAILURE; return rv; } } scopeExit.release(); return NS_OK; } void RemoteWorkerChild::RequestWorkerCancellation() { MOZ_ASSERT(NS_IsMainThread()); LOG(("RequestWorkerCancellation[this=%p]", this)); // We want to ensure that we've requested the worker be canceled. So if the // worker is running, cancel it. We can't do this with the lock held, // however, because our lambdas will want to manipulate the state. RefPtr cancelWith; { auto lock = mState.Lock(); if (lock->is()) { cancelWith = lock->as().mWorkerPrivate; } else if (lock->is()) { cancelWith = lock->as().mWorkerPrivate; } } if (cancelWith) { cancelWith->Cancel(); } } // This method will be invoked on the worker after the top-level // CompileScriptRunnable task has succeeded and as long as the worker has not // been closed/canceled. There are edge-cases related to cancellation, but we // have our caller ensure that we are only called as long as the worker's state // is Running. // // (https://bugzilla.mozilla.org/show_bug.cgi?id=1800659 will eliminate // cancellation, and the documentation around that bug / in design documents // helps provide more context about this.) void RemoteWorkerChild::InitializeOnWorker() { nsCOMPtr r = NewRunnableMethod("TransitionStateToRunning", this, &RemoteWorkerChild::TransitionStateToRunning); MOZ_ALWAYS_SUCCEEDS( SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); } RefPtr RemoteWorkerChild::GetTerminationPromise() { auto launcherData = mLauncherData.Access(); return launcherData->mTerminationPromise.Ensure(__func__); } void RemoteWorkerChild::CreationSucceededOnAnyThread() { CreationSucceededOrFailedOnAnyThread(true); } void RemoteWorkerChild::CreationFailedOnAnyThread() { CreationSucceededOrFailedOnAnyThread(false); } void RemoteWorkerChild::CreationSucceededOrFailedOnAnyThread( bool aDidCreationSucceed) { #ifdef DEBUG { auto lock = mState.Lock(); MOZ_ASSERT_IF(aDidCreationSucceed, lock->is()); MOZ_ASSERT_IF(!aDidCreationSucceed, lock->is()); } #endif RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction( __func__, [self = std::move(self), didCreationSucceed = aDidCreationSucceed] { auto launcherData = self->mLauncherData.Access(); if (!self->CanSend() || launcherData->mDidSendCreated) { return; } Unused << self->SendCreated(didCreationSucceed); launcherData->mDidSendCreated = true; }); GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void RemoteWorkerChild::CloseWorkerOnMainThread() { AssertIsOnMainThread(); LOG(("CloseWorkerOnMainThread[this=%p]", this)); // We can't hold the state lock while calling WorkerPrivate::Cancel because // the lambda callback will want to touch the state, so save off the // WorkerPrivate so we can cancel it (if we need to cancel it). RefPtr cancelWith; { auto lock = mState.Lock(); if (lock->is()) { cancelWith = lock->as().mWorkerPrivate; // There should be no way for this code to run before we // ExecWorkerOnMainThread runs, which means that either it should have // set a WorkerPrivate on Pending, or its error handling should already // have transitioned us to Canceled and Killing in that order. (It's // also possible that it assigned a WorkerPrivate and subsequently we // transitioned to Running, which would put us in the next branch.) MOZ_DIAGNOSTIC_ASSERT(cancelWith); } else if (lock->is()) { cancelWith = lock->as().mWorkerPrivate; } } // It's very okay for us to not have a WorkerPrivate here if we've already // canceled the worker or if errors happened. if (cancelWith) { cancelWith->Cancel(); } } /** * Error reporting method */ void RemoteWorkerChild::ErrorPropagation(const ErrorValue& aValue) { MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread()); if (!CanSend()) { return; } Unused << SendError(aValue); } void RemoteWorkerChild::ErrorPropagationDispatch(nsresult aError) { MOZ_ASSERT(NS_FAILED(aError)); RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction( "RemoteWorkerChild::ErrorPropagationDispatch", [self = std::move(self), aError]() { self->ErrorPropagation(aError); }); GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void RemoteWorkerChild::ErrorPropagationOnMainThread( const WorkerErrorReport* aReport, bool aIsErrorEvent) { AssertIsOnMainThread(); ErrorValue value; if (aIsErrorEvent) { ErrorData data( aReport->mIsWarning, aReport->mLineNumber, aReport->mColumnNumber, aReport->mMessage, aReport->mFilename, aReport->mLine, TransformIntoNewArray(aReport->mNotes, [](const WorkerErrorNote& note) { return ErrorDataNote(note.mLineNumber, note.mColumnNumber, note.mMessage, note.mFilename); })); value = data; } else { value = void_t(); } RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction( "RemoteWorkerChild::ErrorPropagationOnMainThread", [self = std::move(self), value]() { self->ErrorPropagation(value); }); GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void RemoteWorkerChild::NotifyLock(bool aCreated) { nsCOMPtr r = NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] { if (!self->CanSend()) { return; } Unused << self->SendNotifyLock(aCreated); }); GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void RemoteWorkerChild::NotifyWebTransport(bool aCreated) { nsCOMPtr r = NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] { if (!self->CanSend()) { return; } Unused << self->SendNotifyWebTransport(aCreated); }); GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void RemoteWorkerChild::FlushReportsOnMainThread( nsIConsoleReportCollector* aReporter) { AssertIsOnMainThread(); bool reportErrorToBrowserConsole = true; // Flush the reports. for (uint32_t i = 0, len = mWindowIDs.Length(); i < len; ++i) { aReporter->FlushReportsToConsole( mWindowIDs[i], nsIConsoleReportCollector::ReportAction::Save); reportErrorToBrowserConsole = false; } // Finally report to browser console if there is no any window. if (reportErrorToBrowserConsole) { aReporter->FlushReportsToConsole(0); return; } aReporter->ClearConsoleReports(); } /** * Worker state transition methods */ RemoteWorkerChild::WorkerPrivateAccessibleState:: ~WorkerPrivateAccessibleState() { // We should now only be performing state transitions on the main thread, so // we should assert we're only releasing on the main thread. MOZ_ASSERT(!mWorkerPrivate || NS_IsMainThread()); // mWorkerPrivate can be safely released on the main thread. if (!mWorkerPrivate || NS_IsMainThread()) { return; } // But as a backstop, do proxy the release to the main thread. NS_ReleaseOnMainThread( "RemoteWorkerChild::WorkerPrivateAccessibleState::mWorkerPrivate", mWorkerPrivate.forget()); } void RemoteWorkerChild:: OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled() { auto lock = mState.Lock(); LOG(("TransitionStateFromPendingOrRunningToCanceled[this=%p]", this)); if (lock->is()) { TransitionStateFromPendingToCanceled(lock.ref()); } else if (lock->is()) { *lock = VariantType(); } else { MOZ_ASSERT(false, "State should have been Pending or Running"); } } void RemoteWorkerChild::TransitionStateFromPendingToCanceled(State& aState) { AssertIsOnMainThread(); MOZ_ASSERT(aState.is()); LOG(("TransitionStateFromPendingToCanceled[this=%p]", this)); CancelAllPendingOps(aState); aState = VariantType(); } void RemoteWorkerChild::TransitionStateFromCanceledToKilled() { AssertIsOnMainThread(); LOG(("TransitionStateFromCanceledToKilled[this=%p]", this)); auto lock = mState.Lock(); MOZ_ASSERT(lock->is()); *lock = VariantType(); RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction(__func__, [self]() { auto launcherData = self->mLauncherData.Access(); // (We maintain the historical ordering of resolving this promise prior to // calling SendClose, however the previous code used 2 separate dispatches // to this thread for the resolve and SendClose, and there inherently // would be a race between the runnables resulting from the resolved // promise and the promise containing the call to SendClose. Now it's // entirely clear that our call to SendClose will effectively run before // any of the resolved promises are able to do anything.) launcherData->mTerminationPromise.ResolveIfExists(true, __func__); if (self->CanSend()) { Unused << self->SendClose(); } }); GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void RemoteWorkerChild::TransitionStateToRunning() { AssertIsOnMainThread(); LOG(("TransitionStateToRunning[this=%p]", this)); nsTArray> pendingOps; { auto lock = mState.Lock(); // Because this is an async notification sent from the worker to the main // thread, it's very possible that we've already decided on the main thread // to transition to the Canceled state, in which case there is nothing for // us to do here. if (!lock->is()) { LOG(("State is already not pending in TransitionStateToRunning[this=%p]!", this)); return; } RefPtr workerPrivate = std::move(lock->as().mWorkerPrivate); pendingOps = std::move(lock->as().mPendingOps); // Move the worker private into place to avoid gratuitous ref churn; prior // comments here suggest the Variant can't accept a move. *lock = VariantType(); lock->as().mWorkerPrivate = std::move(workerPrivate); } CreationSucceededOnAnyThread(); RefPtr self = this; for (auto& op : pendingOps) { op->StartOnMainThread(self); } } void RemoteWorkerChild::ExceptionalErrorTransitionDuringExecWorker() { AssertIsOnMainThread(); LOG(("ExceptionalErrorTransitionDuringExecWorker[this=%p]", this)); // This method is called synchronously by ExecWorkerOnMainThread in the event // of any error. Because we only transition to Running on the main thread // as the result of a notification from the worker, we know our state will be // Pending, but mWorkerPrivate may or may not be null, as we may not have // gotten to spawning the worker. // // In the event the worker exists, we need to Cancel() it. We must do this // without the lock held because our call to Cancel() will invoke the // cancellation callback we created which will call TransitionStateToCanceled, // and we can't be holding the lock when that happens. RefPtr cancelWith; { auto lock = mState.Lock(); MOZ_ASSERT(lock->is()); if (lock->is()) { cancelWith = lock->as().mWorkerPrivate; if (!cancelWith) { // The worker wasn't actually created, so we should synthetically // transition to canceled and onward. Since we have the lock, // perform the transition now for clarity, but we'll handle the rest of // this case after dropping the lock. TransitionStateFromPendingToCanceled(lock.ref()); } } } if (cancelWith) { cancelWith->Cancel(); } else { TransitionStateFromCanceledToKilled(); CreationFailedOnAnyThread(); } } /** * Operation execution classes/methods */ class RemoteWorkerChild::SharedWorkerOp : public RemoteWorkerChild::Op { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerOp, override) explicit SharedWorkerOp(RemoteWorkerOp&& aOp) : mOp(std::move(aOp)) {} bool MaybeStart(RemoteWorkerChild* aOwner, RemoteWorkerChild::State& aState) override { MOZ_ASSERT(!mStarted); MOZ_ASSERT(aOwner); // Thread: We are on the Worker Launcher thread. // Return false, indicating we should queue this op if our current state is // pending and this isn't a termination op (which should skip the line). if (aState.is() && !IsTerminationOp()) { return false; } // If the worker is already shutting down (which should be unexpected // because we should be told new operations after a termination op), just // return true to indicate the op should be discarded. if (aState.is() || aState.is()) { #ifdef DEBUG mStarted = true; #endif return true; } MOZ_ASSERT(aState.is() || IsTerminationOp()); RefPtr self = this; RefPtr owner = aOwner; nsCOMPtr r = NS_NewRunnableFunction( __func__, [self = std::move(self), owner = std::move(owner)]() mutable { { auto lock = owner->mState.Lock(); if (NS_WARN_IF(lock->is() || lock->is())) { self->Cancel(); return; } } self->StartOnMainThread(owner); }); MOZ_ALWAYS_SUCCEEDS( SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); #ifdef DEBUG mStarted = true; #endif return true; } void StartOnMainThread(RefPtr& aOwner) final { using Running = RemoteWorkerChild::Running; AssertIsOnMainThread(); if (IsTerminationOp()) { aOwner->CloseWorkerOnMainThread(); return; } auto lock = aOwner->mState.Lock(); MOZ_ASSERT(lock->is()); if (!lock->is()) { aOwner->ErrorPropagationDispatch(NS_ERROR_DOM_INVALID_STATE_ERR); return; } RefPtr workerPrivate = lock->as().mWorkerPrivate; MOZ_ASSERT(workerPrivate); if (mOp.type() == RemoteWorkerOp::TRemoteWorkerSuspendOp) { workerPrivate->ParentWindowPaused(); } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerResumeOp) { workerPrivate->ParentWindowResumed(); } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerFreezeOp) { workerPrivate->Freeze(nullptr); } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerThawOp) { workerPrivate->Thaw(nullptr); } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) { RefPtr r = new MessagePortIdentifierRunnable( workerPrivate, aOwner, mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier()); if (NS_WARN_IF(!r->Dispatch())) { aOwner->ErrorPropagationDispatch(NS_ERROR_FAILURE); } } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerAddWindowIDOp) { aOwner->mWindowIDs.AppendElement( mOp.get_RemoteWorkerAddWindowIDOp().windowID()); } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerRemoveWindowIDOp) { aOwner->mWindowIDs.RemoveElement( mOp.get_RemoteWorkerRemoveWindowIDOp().windowID()); } else { MOZ_CRASH("Unknown RemoteWorkerOp type!"); } } void Cancel() override { #ifdef DEBUG mStarted = true; #endif } private: ~SharedWorkerOp() { MOZ_ASSERT(mStarted); } bool IsTerminationOp() const { return mOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp; } RemoteWorkerOp mOp; #ifdef DEBUG bool mStarted = false; #endif }; void RemoteWorkerChild::AddPortIdentifier( JSContext* aCx, WorkerPrivate* aWorkerPrivate, UniqueMessagePortId& aPortIdentifier) { if (NS_WARN_IF(!aWorkerPrivate->ConnectMessagePort(aCx, aPortIdentifier))) { ErrorPropagationDispatch(NS_ERROR_FAILURE); } } void RemoteWorkerChild::CancelAllPendingOps(State& aState) { MOZ_ASSERT(aState.is()); auto pendingOps = std::move(aState.as().mPendingOps); for (auto& op : pendingOps) { op->Cancel(); } } void RemoteWorkerChild::MaybeStartOp(RefPtr&& aOp) { MOZ_ASSERT(aOp); auto lock = mState.Lock(); if (!aOp->MaybeStart(this, lock.ref())) { // Maybestart returns false only if we are . lock->as().mPendingOps.AppendElement(std::move(aOp)); } } IPCResult RemoteWorkerChild::RecvExecOp(RemoteWorkerOp&& aOp) { MOZ_ASSERT(!mIsServiceWorker); MaybeStartOp(new SharedWorkerOp(std::move(aOp))); return IPC_OK(); } IPCResult RemoteWorkerChild::RecvExecServiceWorkerOp( ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve) { MOZ_ASSERT(mIsServiceWorker); MOZ_ASSERT( aArgs.type() != ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs, "FetchEvent operations should be sent via PFetchEventOp(Proxy) actors!"); MaybeReportServiceWorkerShutdownProgress(aArgs); MaybeStartOp(ServiceWorkerOp::Create(std::move(aArgs), std::move(aResolve))); return IPC_OK(); } RefPtr RemoteWorkerChild::MaybeSendSetServiceWorkerSkipWaitingFlag() { RefPtr promise = new GenericPromise::Private(__func__); RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction(__func__, [self = std::move( self), promise] { if (!self->CanSend()) { promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); return; } self->SendSetServiceWorkerSkipWaitingFlag()->Then( GetCurrentSerialEventTarget(), __func__, [promise]( const SetServiceWorkerSkipWaitingFlagPromise::ResolveOrRejectValue& aResult) { if (NS_WARN_IF(aResult.IsReject())) { promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); return; } promise->Resolve(aResult.ResolveValue(), __func__); }); }); GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); return promise; } /** * PFetchEventOpProxy methods */ already_AddRefed RemoteWorkerChild::AllocPFetchEventOpProxyChild( const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) { return RefPtr{new FetchEventOpProxyChild()}.forget(); } IPCResult RemoteWorkerChild::RecvPFetchEventOpProxyConstructor( PFetchEventOpProxyChild* aActor, const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) { MOZ_ASSERT(aActor); (static_cast(aActor))->Initialize(aArgs); return IPC_OK(); } } // namespace dom } // namespace mozilla