Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 <utility>
#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/dom/WorkerScope.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<ServiceWorkerInterceptController> swController = mSWController;
swController.forget(aSink);
return NS_OK;
}
return NS_NOINTERFACE;
}
private:
~SharedWorkerInterfaceRequestor() = default;
RefPtr<ServiceWorkerInterceptController> 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, "MessagePortIdentifierRunnable"),
mActor(aActor),
mPortIdentifier(aPortIdentifier) {}
private:
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
if (aWorkerPrivate->GlobalScope()->IsDying()) {
mPortIdentifier.ForceClose();
return true;
}
mActor->AddPortIdentifier(aCx, aWorkerPrivate, mPortIdentifier);
return true;
}
RefPtr<RemoteWorkerChild> mActor;
UniqueMessagePortId mPortIdentifier;
};
// This is used to propagate the CSP violation when loading the SharedWorker
// main-script and nothing else.
class RemoteWorkerCSPEventListener final : public nsICSPEventListener {
public:
NS_DECL_ISUPPORTS
explicit RemoteWorkerCSPEventListener(RemoteWorkerChild* aActor)
: mActor(aActor){};
NS_IMETHOD OnCSPViolationEvent(const nsAString& aJSON) override {
mActor->CSPViolationPropagationOnMainThread(aJSON);
return NS_OK;
}
private:
~RemoteWorkerCSPEventListener() = default;
RefPtr<RemoteWorkerChild> mActor;
};
NS_IMPL_ISUPPORTS(RemoteWorkerCSPEventListener, nsICSPEventListener)
} // anonymous namespace
RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData& aData)
: mState(VariantType<Pending>(), "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<Killed>());
#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<Killed>() && !lock->is<Canceled>())) {
// 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<nsIRunnable> runnable =
NewRunnableMethod("RequestWorkerCancellation", this,
&RemoteWorkerChild::RequestWorkerCancellation);
MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(runnable.forget()));
}
}
void RemoteWorkerChild::ExecWorker(const RemoteWorkerData& aData) {
#ifdef DEBUG
MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread());
auto launcherData = mLauncherData.Access();
MOZ_ASSERT(CanSend());
#endif
RefPtr<RemoteWorkerChild> self = this;
nsCOMPtr<nsIRunnable> 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(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.
IndexedDatabaseManager* idm = IndexedDatabaseManager::GetOrCreate();
if (idm) {
Unused << NS_WARN_IF(NS_FAILED(idm->EnsureLocale()));
} else {
NS_WARNING("Failed to get IndexedDatabaseManager!");
}
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<nsIPrincipal> 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<PrincipalInfo>(aData.principalInfo());
info.mPartitionedPrincipalInfo =
MakeUnique<PrincipalInfo>(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.mUsingStorageAccess = aData.usingStorageAccess();
info.mIsThirdPartyContext = aData.isThirdPartyContext();
info.mOriginAttributes =
BasePrincipal::Cast(principal)->OriginAttributesRef();
info.mShouldResistFingerprinting = aData.shouldResistFingerprinting();
Maybe<RFPTarget> overriddenFingerprintingSettings;
if (aData.overriddenFingerprintingSettings().isSome()) {
overriddenFingerprintingSettings.emplace(
RFPTarget(aData.overriddenFingerprintingSettings().ref()));
}
info.mOverriddenFingerprintingSettings = overriddenFingerprintingSettings;
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<SharedWorkerInterfaceRequestor> requestor =
new SharedWorkerInterfaceRequestor();
info.mInterfaceRequestor->SetOuterRequestor(requestor);
Maybe<ClientInfo> clientInfo;
if (aData.clientInfo().isSome()) {
clientInfo.emplace(ClientInfo(aData.clientInfo().ref()));
}
nsresult rv = NS_OK;
if (clientInfo.isSome()) {
Maybe<mozilla::ipc::CSPInfo> cspInfo = clientInfo.ref().GetCspInfo();
if (cspInfo.isSome()) {
info.mCSP = CSPInfoToCSP(cspInfo.ref(), nullptr);
info.mCSPInfo = MakeUnique<CSPInfo>();
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<nsLoadFlags>(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;
}
nsCOMPtr<nsILoadInfo> loadInfo = info.mChannel->LoadInfo();
auto* cspEventListener = new RemoteWorkerCSPEventListener(this);
rv = loadInfo->SetCspEventListener(cspEventListener);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
info.mAgentClusterId = aData.agentClusterId();
AutoJSAPI jsapi;
jsapi.Init();
ErrorResult error;
RefPtr<RemoteWorkerChild> self = this;
RefPtr<WorkerPrivate> 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<nsISerialEventTarget> workerTarget =
workerPrivate->HybridEventTarget();
nsCOMPtr<nsIRunnable> 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<Pending>().mWorkerPrivate = std::move(workerPrivate);
}
if (mIsServiceWorker) {
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__, [workerTarget,
initializeWorkerRunnable = std::move(runnable)]() mutable {
Unused << NS_WARN_IF(NS_FAILED(
workerTarget->Dispatch(initializeWorkerRunnable.forget())));
});
RefPtr<PermissionManager> 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<WorkerPrivate> cancelWith;
{
auto lock = mState.Lock();
if (lock->is<Pending>()) {
cancelWith = lock->as<Pending>().mWorkerPrivate;
} else if (lock->is<Running>()) {
cancelWith = lock->as<Running>().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.
//
// cancellation, and the documentation around that bug / in design documents
// helps provide more context about this.)
void RemoteWorkerChild::InitializeOnWorker() {
nsCOMPtr<nsIRunnable> r =
NewRunnableMethod("TransitionStateToRunning", this,
&RemoteWorkerChild::TransitionStateToRunning);
MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
}
RefPtr<GenericNonExclusivePromise> 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<Running>());
MOZ_ASSERT_IF(!aDidCreationSucceed, lock->is<Killed>());
}
#endif
RefPtr<RemoteWorkerChild> self = this;
nsCOMPtr<nsIRunnable> 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<WorkerPrivate> cancelWith;
{
auto lock = mState.Lock();
if (lock->is<Pending>()) {
cancelWith = lock->as<Pending>().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<Running>()) {
cancelWith = lock->as<Running>().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<RemoteWorkerChild> self = this;
nsCOMPtr<nsIRunnable> 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<RemoteWorkerChild> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
"RemoteWorkerChild::ErrorPropagationOnMainThread",
[self = std::move(self), value]() { self->ErrorPropagation(value); });
GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}
void RemoteWorkerChild::CSPViolationPropagationOnMainThread(
const nsAString& aJSON) {
AssertIsOnMainThread();
RefPtr<RemoteWorkerChild> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
"RemoteWorkerChild::ErrorPropagationDispatch",
[self = std::move(self), json = nsString(aJSON)]() {
CSPViolation violation(json);
self->ErrorPropagation(violation);
});
GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}
void RemoteWorkerChild::NotifyLock(bool aCreated) {
nsCOMPtr<nsIRunnable> 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<nsIRunnable> 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<Pending>()) {
TransitionStateFromPendingToCanceled(lock.ref());
} else if (lock->is<Running>()) {
*lock = VariantType<Canceled>();
} else {
MOZ_ASSERT(false, "State should have been Pending or Running");
}
}
void RemoteWorkerChild::TransitionStateFromPendingToCanceled(State& aState) {
AssertIsOnMainThread();
MOZ_ASSERT(aState.is<Pending>());
LOG(("TransitionStateFromPendingToCanceled[this=%p]", this));
CancelAllPendingOps(aState);
aState = VariantType<Canceled>();
}
void RemoteWorkerChild::TransitionStateFromCanceledToKilled() {
AssertIsOnMainThread();
LOG(("TransitionStateFromCanceledToKilled[this=%p]", this));
auto lock = mState.Lock();
MOZ_ASSERT(lock->is<Canceled>());
*lock = VariantType<Killed>();
RefPtr<RemoteWorkerChild> self = this;
nsCOMPtr<nsIRunnable> 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<RefPtr<Op>> 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<Pending>()) {
LOG(("State is already not pending in TransitionStateToRunning[this=%p]!",
this));
return;
}
RefPtr<WorkerPrivate> workerPrivate =
std::move(lock->as<Pending>().mWorkerPrivate);
pendingOps = std::move(lock->as<Pending>().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<Running>();
lock->as<Running>().mWorkerPrivate = std::move(workerPrivate);
}
CreationSucceededOnAnyThread();
RefPtr<RemoteWorkerChild> 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<WorkerPrivate> cancelWith;
{
auto lock = mState.Lock();
MOZ_ASSERT(lock->is<Pending>());
if (lock->is<Pending>()) {
cancelWith = lock->as<Pending>().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<Pending>() && !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<Canceled>() || aState.is<Killed>()) {
#ifdef DEBUG
mStarted = true;
#endif
if (mOp.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) {
MessagePort::ForceClose(
mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier());
}
return true;
}
MOZ_ASSERT(aState.is<Running>() || IsTerminationOp());
RefPtr<SharedWorkerOp> self = this;
RefPtr<RemoteWorkerChild> owner = aOwner;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__, [self = std::move(self), owner = std::move(owner)]() mutable {
{
auto lock = owner->mState.Lock();
if (NS_WARN_IF(lock->is<Canceled>() || lock->is<Killed>())) {
self->Cancel();
// Worker has already canceled, force close the MessagePort.
if (self->mOp.type() ==
RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) {
MessagePort::ForceClose(
self->mOp.get_RemoteWorkerPortIdentifierOp()
.portIdentifier());
}
return;
}
}
self->StartOnMainThread(owner);
});
MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
#ifdef DEBUG
mStarted = true;
#endif
return true;
}
void StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) final {
using Running = RemoteWorkerChild::Running;
AssertIsOnMainThread();
if (IsTerminationOp()) {
aOwner->CloseWorkerOnMainThread();
return;
}
auto lock = aOwner->mState.Lock();
MOZ_ASSERT(lock->is<Running>());
if (!lock->is<Running>()) {
aOwner->ErrorPropagationDispatch(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
RefPtr<WorkerPrivate> workerPrivate = lock->as<Running>().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<MessagePortIdentifierRunnable> 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<Pending>());
auto pendingOps = std::move(aState.as<Pending>().mPendingOps);
for (auto& op : pendingOps) {
op->Cancel();
}
}
void RemoteWorkerChild::MaybeStartOp(RefPtr<Op>&& aOp) {
MOZ_ASSERT(aOp);
auto lock = mState.Lock();
if (!aOp->MaybeStart(this, lock.ref())) {
// Maybestart returns false only if we are <Pending>.
lock->as<Pending>().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<GenericPromise>
RemoteWorkerChild::MaybeSendSetServiceWorkerSkipWaitingFlag() {
RefPtr<GenericPromise::Private> promise =
new GenericPromise::Private(__func__);
RefPtr<RemoteWorkerChild> self = this;
nsCOMPtr<nsIRunnable> 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<PFetchEventOpProxyChild>
RemoteWorkerChild::AllocPFetchEventOpProxyChild(
const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
return RefPtr{new FetchEventOpProxyChild()}.forget();
}
IPCResult RemoteWorkerChild::RecvPFetchEventOpProxyConstructor(
PFetchEventOpProxyChild* aActor,
const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
MOZ_ASSERT(aActor);
(static_cast<FetchEventOpProxyChild*>(aActor))->Initialize(aArgs);
return IPC_OK();
}
} // namespace dom
} // namespace mozilla