/* -*- 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 "nsProxyRelease.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" namespace mozilla { using namespace ipc; namespace dom { using workerinternals::ChannelFromScriptURLMainThread; class SelfHolder { public: MOZ_IMPLICIT SelfHolder(RemoteWorkerChild* aSelf) : mSelf(aSelf) { MOZ_ASSERT(mSelf); } SelfHolder(const SelfHolder&) = default; SelfHolder& operator=(const SelfHolder&) = default; SelfHolder(SelfHolder&&) = default; SelfHolder& operator=(SelfHolder&&) = default; ~SelfHolder() { if (!mSelf) { return; } nsCOMPtr target = mSelf->GetOwningEventTarget(); NS_ProxyRelease("SelfHolder::mSelf", target, mSelf.forget()); } RemoteWorkerChild* get() const { MOZ_ASSERT(mSelf); return mSelf.get(); } RemoteWorkerChild* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return get(); } bool operator!() { return !mSelf.get(); } private: RefPtr mSelf; }; namespace { class SharedWorkerInterfaceRequestor final : public nsIInterfaceRequestor { public: NS_DECL_ISUPPORTS SharedWorkerInterfaceRequestor() { // This check must match the code nsDocShell::Create. if (!ServiceWorkerParentInterceptEnabled() || 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, SelfHolder aActor, const MessagePortIdentifier& aPortIdentifier) : WorkerRunnable(aWorkerPrivate), mActor(std::move(aActor)), mPortIdentifier(aPortIdentifier) {} private: bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mActor->AddPortIdentifier(aCx, aWorkerPrivate, mPortIdentifier); return true; } SelfHolder mActor; UniqueMessagePortId mPortIdentifier; }; // This is used to release WeakWorkerRefs which can only have their refcount // modified on the owning thread (worker thread in this case). It also keeps // alive the associated WorkerPrivate until the WeakWorkerRef is released. class ReleaseWorkerRunnable final : public WorkerControlRunnable { public: ReleaseWorkerRunnable(RefPtr&& aWorkerPrivate, RefPtr&& aWeakRef) : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mWorkerPrivate(std::move(aWorkerPrivate)), mWeakRef(std::move(aWeakRef)) { MOZ_ASSERT(mWorkerPrivate); MOZ_ASSERT(!mWorkerPrivate->IsOnWorkerThread()); MOZ_ASSERT(mWeakRef); } private: ~ReleaseWorkerRunnable() { ReleaseMembers(); } bool WorkerRun(JSContext*, WorkerPrivate*) override { ReleaseMembers(); return true; } nsresult Cancel() override { ReleaseMembers(); return NS_OK; } void ReleaseMembers() { if (!mWorkerPrivate) { MOZ_ASSERT(!mWeakRef); return; } mWeakRef = nullptr; NS_ReleaseOnMainThread("ReleaseWorkerRunnable::mWorkerPrivate", mWorkerPrivate.forget()); } RefPtr mWorkerPrivate; RefPtr mWeakRef; }; } // anonymous namespace class RemoteWorkerChild::InitializeWorkerRunnable final : public WorkerRunnable { public: InitializeWorkerRunnable(WorkerPrivate* aWorkerPrivate, SelfHolder aActor) : WorkerRunnable(aWorkerPrivate), mActor(std::move(aActor)) { MOZ_ASSERT(mActor); } private: ~InitializeWorkerRunnable() { MaybeAbort(); } bool WorkerRun(JSContext*, WorkerPrivate*) override { MOZ_ASSERT(mActor); mActor->InitializeOnWorker(); SelfHolder holder = std::move(mActor); MOZ_ASSERT(!mActor); return true; } nsresult Cancel() override { MaybeAbort(); return WorkerRunnable::Cancel(); } // Slowly running out of synonyms for cancel, abort, terminate, etc... void MaybeAbort() { if (!mActor) { return; } mActor->TransitionStateToTerminated(); mActor->CreationFailedOnAnyThread(); mActor->ShutdownOnWorker(); SelfHolder holder = std::move(mActor); MOZ_ASSERT(!mActor); } // Falsy indicates that WorkerRun or MaybeAbort has already been called. SelfHolder mActor; }; RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData& aData) : mState(VariantType(), "RemoteWorkerChild::mState"), mIsServiceWorker(aData.serviceWorkerData().type() == OptionalServiceWorkerData::TServiceWorkerData), mOwningEventTarget(GetCurrentSerialEventTarget()) { MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); MOZ_ASSERT(mOwningEventTarget); } RemoteWorkerChild::~RemoteWorkerChild() { #ifdef DEBUG auto lock = mState.Lock(); MOZ_ASSERT(lock->is()); #endif } nsISerialEventTarget* RemoteWorkerChild::GetOwningEventTarget() const { return mOwningEventTarget; } void RemoteWorkerChild::ActorDestroy(ActorDestroyReason) { auto launcherData = mLauncherData.Access(); launcherData->mIPCActive = false; Unused << NS_WARN_IF(!launcherData->mTerminationPromise.IsEmpty()); launcherData->mTerminationPromise.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__); auto lock = mState.Lock(); Unused << NS_WARN_IF(!lock->is()); *lock = VariantType(); } void RemoteWorkerChild::ExecWorker(const RemoteWorkerData& aData) { #ifdef DEBUG MOZ_ASSERT(GetOwningEventTarget()->IsOnCurrentThread()); auto launcherData = mLauncherData.Access(); MOZ_ASSERT(launcherData->mIPCActive); #endif SelfHolder self = this; nsCOMPtr r = NS_NewRunnableFunction( __func__, [self = std::move(self), data = aData]() mutable { nsresult rv = self->ExecWorkerOnMainThread(std::move(data)); if (NS_WARN_IF(NS_FAILED(rv))) { self->CreationFailedOnAnyThread(); } }); MOZ_ALWAYS_SUCCEEDS( SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); } nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) { MOZ_ASSERT(NS_IsMainThread()); // Ensure that the IndexedDatabaseManager is initialized Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate()); auto scopeExit = MakeScopeExit([&] { TransitionStateToTerminated(); }); // 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.mPrincipal = principal; info.mPartitionedPrincipal = partitionedPrincipalOrErr.unwrap(); info.mLoadingPrincipal = loadingPrincipalOrErr.unwrap(); info.mStorageAccess = aData.storageAccess(); info.mUseRegularPrincipal = aData.useRegularPrincipal(); info.mHasStorageAccessPermissionGranted = aData.hasStorageAccessPermissionGranted(); info.mOriginAttributes = BasePrincipal::Cast(principal)->OriginAttributesRef(); net::CookieJarSettings::Deserialize(aData.cookieJarSettings(), getter_AddRefs(info.mCookieJarSettings)); // 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.mReportCSPViolations = 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, 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 workerPrivate = WorkerPrivate::Constructor( jsapi.cx(), aData.originalScriptURL(), false, mIsServiceWorker ? WorkerTypeService : WorkerTypeShared, aData.name(), VoidCString(), &info, error, std::move(workerPrivateId)); if (NS_WARN_IF(error.Failed())) { MOZ_ASSERT(!workerPrivate); rv = error.StealNSResult(); return rv; } if (mIsServiceWorker) { RefPtr self = this; workerPrivate->SetRemoteWorkerControllerWeakRef( ThreadSafeWeakPtr(self)); } else { workerPrivate->SetRemoteWorkerController(this); } RefPtr runnable = new InitializeWorkerRunnable(workerPrivate, SelfHolder(this)); { MOZ_ASSERT(workerPrivate); auto lock = mState.Lock(); lock->as().mWorkerPrivate = std::move(workerPrivate); } if (mIsServiceWorker) { SelfHolder self = this; nsCOMPtr r = NS_NewRunnableFunction( __func__, [initializeWorkerRunnable = std::move(runnable), self = std::move(self)] { // Checking RemoteWorkerChild.mState bool isPending; { auto lock = self->mState.Lock(); isPending = lock->is(); } if (NS_WARN_IF(!isPending || !initializeWorkerRunnable->Dispatch())) { self->TransitionStateToTerminated(); self->CreationFailedOnAnyThread(); } }); RefPtr permissionManager = PermissionManager::GetInstance(); if (!permissionManager) { return NS_ERROR_FAILURE; } permissionManager->WhenPermissionsAvailable(principal, r); } else { if (NS_WARN_IF(!runnable->Dispatch())) { rv = NS_ERROR_FAILURE; return rv; } } scopeExit.release(); return NS_OK; } void RemoteWorkerChild::InitializeOnWorker() { RefPtr workerPrivate; { auto lock = mState.Lock(); if (lock->is()) { TransitionStateToTerminated(lock.ref()); ShutdownOnWorker(); return; } workerPrivate = std::move(lock->as().mWorkerPrivate); } MOZ_ASSERT(workerPrivate); workerPrivate->AssertIsOnWorkerThread(); RefPtr self = this; ThreadSafeWeakPtr selfWeakRef(self); auto scopeExit = MakeScopeExit([&] { MOZ_ASSERT(self); NS_ProxyRelease(__func__, mOwningEventTarget, self.forget()); }); // Let RemoteWorkerChild own the WorkerPrivate; RemoteWorkerChild's state // transitions should guarantee the WorkerPrivate is cleaned up correctly. // This also reduces some complexity around thread lifetimes guarantees that // RemoteWorkerChild's state transitions rely on (e.g. the worker thread // terminating unexpectedly). RefPtr strongRef = StrongWorkerRef::Create(workerPrivate, __func__); RefPtr workerRef = WeakWorkerRef::Create( workerPrivate, [selfWeakRef = std::move(selfWeakRef), strongRef = std::move(strongRef)]() mutable { RefPtr self(selfWeakRef); if (NS_WARN_IF(!self)) { return; } self->TransitionStateToTerminated(); self->ShutdownOnWorker(); nsCOMPtr target = self->GetOwningEventTarget(); NS_ProxyRelease(__func__, target, self.forget()); }); if (NS_WARN_IF(!workerRef)) { TransitionStateToTerminated(); CreationFailedOnAnyThread(); ShutdownOnWorker(); return; } TransitionStateToRunning(workerPrivate.forget(), workerRef.forget()); CreationSucceededOnAnyThread(); } void RemoteWorkerChild::ShutdownOnWorker() { RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction(__func__, [self = std::move(self)] { auto launcherData = self->mLauncherData.Access(); if (!launcherData->mIPCActive) { return; } launcherData->mIPCActive = false; Unused << self->SendClose(); }); GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } 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 (!launcherData->mIPCActive) { return; } Unused << self->SendCreated(didCreationSucceed); }); GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void RemoteWorkerChild::CloseWorkerOnMainThread(State& aState) { AssertIsOnMainThread(); MOZ_ASSERT(!aState.is()); // WeakWorkerRef callback will be asynchronously invoked after // WorkerPrivate::Cancel. if (aState.is()) { aState.as().mWorkerPrivate->Cancel(); TransitionStateToPendingTerminated(aState); return; } if (aState.is()) { aState.as().mWorkerPrivate->Cancel(); } } /** * Error reporting method */ void RemoteWorkerChild::ErrorPropagation(const ErrorValue& aValue) { MOZ_ASSERT(GetOwningEventTarget()->IsOnCurrentThread()); auto launcherData = mLauncherData.Access(); if (!launcherData->mIPCActive) { 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); }); GetOwningEventTarget()->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); }); GetOwningEventTarget()->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() { // mWorkerPrivate can be safely released on the main thread. if (!mWorkerPrivate || NS_IsMainThread()) { return; } NS_ReleaseOnMainThread( "RemoteWorkerChild::WorkerPrivateAccessibleState::mWorkerPrivate", mWorkerPrivate.forget()); } RemoteWorkerChild::Running::~Running() { // This can occur if the current object is a temporary. if (!mWorkerPrivate) { return; } if (mWorkerPrivate->IsOnWorkerThread()) { return; } RefPtr runnable = new ReleaseWorkerRunnable( std::move(mWorkerPrivate), std::move(mWorkerRef)); nsCOMPtr dispatchWorkerRunnableRunnable = NS_NewRunnableFunction(__func__, [runnable = std::move(runnable)] { Unused << NS_WARN_IF(!runnable->Dispatch()); }); if (NS_IsMainThread()) { dispatchWorkerRunnableRunnable->Run(); } else { SchedulerGroup::Dispatch(TaskCategory::Other, dispatchWorkerRunnableRunnable.forget()); } } void RemoteWorkerChild::TransitionStateToPendingTerminated(State& aState) { MOZ_ASSERT(aState.is()); CancelAllPendingOps(aState); aState = VariantType(); } void RemoteWorkerChild::TransitionStateToRunning( already_AddRefed aWorkerPrivate, already_AddRefed aWorkerRef) { RefPtr workerPrivate = aWorkerPrivate; MOZ_ASSERT(workerPrivate); RefPtr workerRef = aWorkerRef; MOZ_ASSERT(workerRef); auto lock = mState.Lock(); MOZ_ASSERT(lock->is()); auto pendingOps = std::move(lock->as().mPendingOps); /** * I'd initialize the WorkerPrivate and WeakWorkerRef in the constructor, * but mozilla::Variant attempts to call the thread-unsafe `AddRef()` on * WorkerPrivate. */ *lock = VariantType(); lock->as().mWorkerPrivate = std::move(workerPrivate); lock->as().mWorkerRef = std::move(workerRef); SelfHolder self = this; nsCOMPtr r = NS_NewRunnableFunction( __func__, [pendingOps = std::move(pendingOps), self = std::move(self)]() { for (auto& op : pendingOps) { auto lock = self->mState.Lock(); DebugOnly started = op->MaybeStart(self.get(), lock.ref()); MOZ_ASSERT(started); } }); MOZ_ALWAYS_SUCCEEDS( mOwningEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); } void RemoteWorkerChild::TransitionStateToTerminated() { auto lock = mState.Lock(); TransitionStateToTerminated(lock.ref()); } void RemoteWorkerChild::TransitionStateToTerminated(State& aState) { if (aState.is()) { CancelAllPendingOps(aState); } nsCOMPtr r = NS_NewRunnableFunction(__func__, [self = SelfHolder(this)]() { auto launcherData = self->mLauncherData.Access(); launcherData->mTerminationPromise.ResolveIfExists(true, __func__); }); if (GetOwningEventTarget()->IsOnCurrentThread()) { r->Run(); } else { GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } aState = VariantType(); } /** * 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); auto launcherData = aOwner->mLauncherData.Access(); if (NS_WARN_IF(!launcherData->mIPCActive)) { Unused << NS_WARN_IF(!aState.is()); #ifdef DEBUG mStarted = true; #endif return true; } if (aState.is() && !IsTerminationOp()) { return false; } if (aState.is() || aState.is()) { #ifdef DEBUG mStarted = true; #endif return true; } MOZ_ASSERT(aState.is() || IsTerminationOp()); RefPtr self = this; SelfHolder 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())) { self->Cancel(); return; } } self->Exec(owner); }); MOZ_ALWAYS_SUCCEEDS( SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); #ifdef DEBUG mStarted = true; #endif return true; } void Cancel() override { #ifdef DEBUG mStarted = true; #endif } private: ~SharedWorkerOp() { MOZ_ASSERT(mStarted); } bool IsTerminationOp() const { return mOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp; } void Exec(SelfHolder& aOwner) { using Running = RemoteWorkerChild::Running; AssertIsOnMainThread(); auto lock = aOwner->mState.Lock(); MOZ_ASSERT(lock->is() || IsTerminationOp()); if (IsTerminationOp()) { aOwner->CloseWorkerOnMainThread(lock.ref()); 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!"); } } 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())) { 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::TServiceWorkerFetchEventOpArgs, "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] { auto launcherData = self->mLauncherData.Access(); if (!launcherData->mIPCActive) { 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__); }); }); GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); return promise; } /** * PFetchEventOpProxy methods */ PFetchEventOpProxyChild* RemoteWorkerChild::AllocPFetchEventOpProxyChild( const ServiceWorkerFetchEventOpArgs& aArgs) { RefPtr actor = new FetchEventOpProxyChild(); return actor.forget().take(); } IPCResult RemoteWorkerChild::RecvPFetchEventOpProxyConstructor( PFetchEventOpProxyChild* aActor, const ServiceWorkerFetchEventOpArgs& aArgs) { MOZ_ASSERT(aActor); (static_cast(aActor))->Initialize(aArgs); return IPC_OK(); } bool RemoteWorkerChild::DeallocPFetchEventOpProxyChild( PFetchEventOpProxyChild* aActor) { MOZ_ASSERT(aActor); RefPtr actor = dont_AddRef(static_cast(aActor)); return true; } } // namespace dom } // namespace mozilla