/* -*- 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/. */ #ifndef mozilla_dom_RemoteWorkerChild_h #define mozilla_dom_RemoteWorkerChild_h #include "nsCOMPtr.h" #include "nsISupportsImpl.h" #include "nsTArray.h" #include "mozilla/DataMutex.h" #include "mozilla/MozPromise.h" #include "mozilla/RefPtr.h" #include "mozilla/ThreadBound.h" #include "mozilla/dom/PRemoteWorkerChild.h" #include "mozilla/dom/ServiceWorkerOpArgs.h" class nsISerialEventTarget; class nsIConsoleReportCollector; namespace mozilla::dom { class ErrorValue; class FetchEventOpProxyChild; class RemoteWorkerData; class RemoteWorkerServiceKeepAlive; class ServiceWorkerOp; class UniqueMessagePortId; class WeakWorkerRef; class WorkerErrorReport; class WorkerPrivate; /** * Background-managed "Worker Launcher"-thread-resident created via the * RemoteWorkerManager to actually spawn the worker. Currently, the worker will * be spawned from the main thread due to nsIPrincipal not being able to be * created on background threads and other ownership invariants, most of which * can be relaxed in the future. */ class RemoteWorkerChild final : public PRemoteWorkerChild { friend class FetchEventOpProxyChild; friend class PRemoteWorkerChild; friend class ServiceWorkerOp; ~RemoteWorkerChild(); public: // Note that all IPC-using methods must only be invoked on the // RemoteWorkerService thread which the inherited // IProtocol::GetActorEventTarget() will return for us. NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteWorkerChild, final) explicit RemoteWorkerChild(const RemoteWorkerData& aData); void ExecWorker(const RemoteWorkerData& aData); void ErrorPropagationOnMainThread(const WorkerErrorReport* aReport, bool aIsErrorEvent); void NotifyLock(bool aCreated); void NotifyWebTransport(bool aCreated); void FlushReportsOnMainThread(nsIConsoleReportCollector* aReporter); void AddPortIdentifier(JSContext* aCx, WorkerPrivate* aWorkerPrivate, UniqueMessagePortId& aPortIdentifier); RefPtr GetTerminationPromise(); RefPtr MaybeSendSetServiceWorkerSkipWaitingFlag(); const nsTArray& WindowIDs() const { return mWindowIDs; } private: class InitializeWorkerRunnable; class Op; class SharedWorkerOp; struct WorkerPrivateAccessibleState { ~WorkerPrivateAccessibleState(); RefPtr mWorkerPrivate; }; // Initial state, mWorkerPrivate is initially null but will be initialized on // the main thread by ExecWorkerOnMainThread when the WorkerPrivate is // created. The state will transition to Running or Canceled, also from the // main thread. struct Pending : WorkerPrivateAccessibleState { nsTArray> mPendingOps; }; // Running, with the state transition happening on the main thread as a result // of the worker successfully processing our initialization runnable, // indicating that top-level script execution successfully completed. Because // all of our state transitions happen on the main thread and are posed in // terms of the main thread's perspective of the worker's state, it's very // possible for us to skip directly from Pending to Canceled because we decide // to cancel/terminate the worker prior to it finishing script loading or // reporting back to us. struct Running : WorkerPrivateAccessibleState {}; // Cancel() has been called on the WorkerPrivate on the main thread by a // TerminationOp, top-level script evaluation has failed and canceled the // worker, or in the case of a SharedWorker, close() has been called on // the global scope by content code and the worker has advanced to the // Canceling state. (Dedicated Workers can also self close, but they will // never be RemoteWorkers. Although a SharedWorker can own DedicatedWorkers.) // Browser shutdown will result in a TerminationOp thanks to use of a shutdown // blocker in the parent, so the RuntimeService shouldn't get involved, but we // would also handle that case acceptably too. // // Because worker self-closing is still handled by dispatching a runnable to // the main thread to effectively call WorkerPrivate::Cancel(), there isn't // a race between a worker deciding to self-close and our termination ops. // // In this state, we have dropped the reference to the WorkerPrivate and will // no longer be dispatching runnables to the worker. We wait in this state // until the termination lambda is invoked letting us know that the worker has // entirely shutdown and we can advanced to the Killed state. struct Canceled {}; // The worker termination lambda has been invoked and we know the Worker is // entirely shutdown. (Inherently it is possible for us to advance to this // state while the nsThread for the worker is still in the process of // shutting down, but no more worker code will run on it.) // // This name is chosen to match the Worker's own state model. struct Killed {}; using State = Variant; // The state of the WorkerPrivate as perceived by the owner on the main // thread. All state transitions now happen on the main thread, but the // Worker Launcher thread will consult the state and will directly append ops // to the Pending queue DataMutex mState; const RefPtr mServiceKeepAlive; class Op { public: NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING virtual ~Op() = default; virtual bool MaybeStart(RemoteWorkerChild* aOwner, State& aState) = 0; virtual void StartOnMainThread(RefPtr& aOwner) = 0; virtual void Cancel() = 0; }; void ActorDestroy(ActorDestroyReason) override; mozilla::ipc::IPCResult RecvExecOp(RemoteWorkerOp&& aOp); mozilla::ipc::IPCResult RecvExecServiceWorkerOp( ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve); already_AddRefed AllocPFetchEventOpProxyChild( const ParentToChildServiceWorkerFetchEventOpArgs& aArgs); mozilla::ipc::IPCResult RecvPFetchEventOpProxyConstructor( PFetchEventOpProxyChild* aActor, const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) override; nsresult ExecWorkerOnMainThread(RemoteWorkerData&& aData); void ExceptionalErrorTransitionDuringExecWorker(); void RequestWorkerCancellation(); void InitializeOnWorker(); void CreationSucceededOnAnyThread(); void CreationFailedOnAnyThread(); void CreationSucceededOrFailedOnAnyThread(bool aDidCreationSucceed); // Cancels the worker if it has been started and ensures that we transition // to the Terminated state once the worker has been terminated or we have // ensured that it will never start. void CloseWorkerOnMainThread(); void ErrorPropagation(const ErrorValue& aValue); void ErrorPropagationDispatch(nsresult aError); // When the WorkerPrivate Cancellation lambda is invoked, it's possible that // we have not yet advanced to running from pending, so we could be in either // state. This method is expected to be called by the Workers' cancellation // lambda and will obtain the lock and call the // TransitionStateFromPendingToCanceled if appropriate. Otherwise it will // directly move from the running state to the canceled state which does not // require additional cleanup. void OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled(); // A helper used by the above method by the worker cancellation lambda if the // the worker hasn't started running, or in exceptional cases where we bail // out of the ExecWorker method early. The caller must be holding the lock // (in order to pass in the state). void TransitionStateFromPendingToCanceled(State& aState); void TransitionStateFromCanceledToKilled(); void TransitionStateToRunning(); void TransitionStateToTerminated(); void TransitionStateToTerminated(State& aState); void CancelAllPendingOps(State& aState); void MaybeStartOp(RefPtr&& aOp); const bool mIsServiceWorker; // Touched on main-thread only. nsTArray mWindowIDs; struct LauncherBoundData { MozPromiseHolder mTerminationPromise; // Flag to ensure we report creation at most once. This could be cleaned up // further. bool mDidSendCreated = false; }; ThreadBound mLauncherData; }; } // namespace mozilla::dom #endif // mozilla_dom_RemoteWorkerChild_h