gecko-dev/dom/workers/sharedworkers/SharedWorkerManager.cpp
Eden Chuang bdef9d325d Bug 1849269 - Add SharedWorkerManager::mRemoteWorkerController nullptr checking in SharedWorkerManager::Terminate(). r=dom-worker-reviewers,smaug,asuth
Here comes a possible might cause the crash stack.

    RemoteWorkerController creation fails. If it is Shutdown phase or OOM. The point is ServiceWorkerManager::mRemoteWorkerController is kept as nullptr.

    WorkerManagerCreatedRunnable::mManagerWrapper is the only holder of the ServiceWorkerManager. And it would be released once the runnable finished.

    When releasing WorkerManagerCreatedRunnable::mManagerWrapper, ServiceWorkerManager::UnregisterHolder() is called. In the end SharedWorkerManager::Terminate() is triggered to release the SharedWorker by calling SharedWorkerManager::mRemoteWorkerController::Terminate. However, SharedWorkerManager::mRemoteWorkerController is reasonably a nullptr here, then we meet a de-reference on nullptr.

Differential Revision: https://phabricator.services.mozilla.com/D187269
2023-09-04 08:11:43 +00:00

349 lines
10 KiB
C++

/* -*- 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 "SharedWorkerManager.h"
#include "SharedWorkerParent.h"
#include "SharedWorkerService.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/PSharedWorker.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/dom/RemoteWorkerController.h"
#include "nsIConsoleReportCollector.h"
#include "nsIPrincipal.h"
#include "nsProxyRelease.h"
namespace mozilla::dom {
// static
already_AddRefed<SharedWorkerManagerHolder> SharedWorkerManager::Create(
SharedWorkerService* aService, nsIEventTarget* aPBackgroundEventTarget,
const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal,
const OriginAttributes& aEffectiveStoragePrincipalAttrs) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<SharedWorkerManager> manager =
new SharedWorkerManager(aPBackgroundEventTarget, aData, aLoadingPrincipal,
aEffectiveStoragePrincipalAttrs);
RefPtr<SharedWorkerManagerHolder> holder =
new SharedWorkerManagerHolder(manager, aService);
return holder.forget();
}
SharedWorkerManager::SharedWorkerManager(
nsIEventTarget* aPBackgroundEventTarget, const RemoteWorkerData& aData,
nsIPrincipal* aLoadingPrincipal,
const OriginAttributes& aEffectiveStoragePrincipalAttrs)
: mPBackgroundEventTarget(aPBackgroundEventTarget),
mLoadingPrincipal(aLoadingPrincipal),
mDomain(aData.domain()),
mEffectiveStoragePrincipalAttrs(aEffectiveStoragePrincipalAttrs),
mResolvedScriptURL(DeserializeURI(aData.resolvedScriptURL())),
mName(aData.name()),
mIsSecureContext(aData.isSecureContext()),
mSuspended(false),
mFrozen(false) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aLoadingPrincipal);
}
SharedWorkerManager::~SharedWorkerManager() {
NS_ReleaseOnMainThread("SharedWorkerManager::mLoadingPrincipal",
mLoadingPrincipal.forget());
NS_ProxyRelease("SharedWorkerManager::mRemoteWorkerController",
mPBackgroundEventTarget, mRemoteWorkerController.forget());
}
bool SharedWorkerManager::MaybeCreateRemoteWorker(
const RemoteWorkerData& aData, uint64_t aWindowID,
UniqueMessagePortId& aPortIdentifier, base::ProcessId aProcessId) {
::mozilla::ipc::AssertIsOnBackgroundThread();
// Creating remote workers may result in creating new processes, but during
// parent shutdown that would add just noise, so better bail out.
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
return false;
}
if (!mRemoteWorkerController) {
mRemoteWorkerController =
RemoteWorkerController::Create(aData, this, aProcessId);
if (NS_WARN_IF(!mRemoteWorkerController)) {
return false;
}
}
if (aWindowID) {
mRemoteWorkerController->AddWindowID(aWindowID);
}
mRemoteWorkerController->AddPortIdentifier(aPortIdentifier.release());
return true;
}
already_AddRefed<SharedWorkerManagerHolder>
SharedWorkerManager::MatchOnMainThread(
SharedWorkerService* aService, const nsACString& aDomain,
nsIURI* aScriptURL, const nsAString& aName, nsIPrincipal* aLoadingPrincipal,
const OriginAttributes& aEffectiveStoragePrincipalAttrs) {
MOZ_ASSERT(NS_IsMainThread());
bool urlEquals;
if (NS_FAILED(aScriptURL->Equals(mResolvedScriptURL, &urlEquals))) {
return nullptr;
}
bool match =
aDomain == mDomain && urlEquals && aName == mName &&
// We want to be sure that the window's principal subsumes the
// SharedWorker's loading principal and vice versa.
mLoadingPrincipal->Subsumes(aLoadingPrincipal) &&
aLoadingPrincipal->Subsumes(mLoadingPrincipal) &&
mEffectiveStoragePrincipalAttrs == aEffectiveStoragePrincipalAttrs;
if (!match) {
return nullptr;
}
RefPtr<SharedWorkerManagerHolder> holder =
new SharedWorkerManagerHolder(this, aService);
return holder.forget();
}
void SharedWorkerManager::AddActor(SharedWorkerParent* aParent) {
::mozilla::ipc::AssertIsOnBackgroundThread();
MOZ_ASSERT(aParent);
MOZ_ASSERT(!mActors.Contains(aParent));
mActors.AppendElement(aParent);
if (mLockCount) {
Unused << aParent->SendNotifyLock(true);
}
if (mWebTransportCount) {
Unused << aParent->SendNotifyWebTransport(true);
}
// NB: We don't update our Suspended/Frozen state here, yet. The aParent is
// responsible for doing so from SharedWorkerParent::ManagerCreated.
// XXX But we could avoid iterating all of our actors because if aParent is
// not frozen and we are, we would just need to thaw ourselves.
}
void SharedWorkerManager::RemoveActor(SharedWorkerParent* aParent) {
::mozilla::ipc::AssertIsOnBackgroundThread();
MOZ_ASSERT(aParent);
MOZ_ASSERT(mActors.Contains(aParent));
uint64_t windowID = aParent->WindowID();
if (windowID) {
mRemoteWorkerController->RemoveWindowID(windowID);
}
mActors.RemoveElement(aParent);
if (!mActors.IsEmpty()) {
// Our remaining actors could be all suspended or frozen.
UpdateSuspend();
UpdateFrozen();
return;
}
}
void SharedWorkerManager::Terminate() {
::mozilla::ipc::AssertIsOnBackgroundThread();
MOZ_ASSERT(mActors.IsEmpty());
MOZ_ASSERT(mHolders.IsEmpty());
// mRemoteWorkerController creation can fail. If the creation fails
// mRemoteWorkerController is nullptr and we should stop termination here.
if (!mRemoteWorkerController) {
return;
}
mRemoteWorkerController->Terminate();
mRemoteWorkerController = nullptr;
}
void SharedWorkerManager::UpdateSuspend() {
::mozilla::ipc::AssertIsOnBackgroundThread();
MOZ_ASSERT(mRemoteWorkerController);
uint32_t suspended = 0;
for (SharedWorkerParent* actor : mActors) {
if (actor->IsSuspended()) {
++suspended;
}
}
// Call Suspend only when all of our actors' windows are suspended and call
// Resume only when one of them resumes.
if ((mSuspended && suspended == mActors.Length()) ||
(!mSuspended && suspended != mActors.Length())) {
return;
}
if (!mSuspended) {
mSuspended = true;
mRemoteWorkerController->Suspend();
} else {
mSuspended = false;
mRemoteWorkerController->Resume();
}
}
void SharedWorkerManager::UpdateFrozen() {
::mozilla::ipc::AssertIsOnBackgroundThread();
MOZ_ASSERT(mRemoteWorkerController);
uint32_t frozen = 0;
for (SharedWorkerParent* actor : mActors) {
if (actor->IsFrozen()) {
++frozen;
}
}
// Similar to UpdateSuspend, above, we only want to be frozen when all of our
// actors are frozen.
if ((mFrozen && frozen == mActors.Length()) ||
(!mFrozen && frozen != mActors.Length())) {
return;
}
if (!mFrozen) {
mFrozen = true;
mRemoteWorkerController->Freeze();
} else {
mFrozen = false;
mRemoteWorkerController->Thaw();
}
}
bool SharedWorkerManager::IsSecureContext() const { return mIsSecureContext; }
void SharedWorkerManager::CreationFailed() {
::mozilla::ipc::AssertIsOnBackgroundThread();
for (SharedWorkerParent* actor : mActors) {
Unused << actor->SendError(NS_ERROR_FAILURE);
}
}
void SharedWorkerManager::CreationSucceeded() {
::mozilla::ipc::AssertIsOnBackgroundThread();
// Nothing to do here.
}
void SharedWorkerManager::ErrorReceived(const ErrorValue& aValue) {
::mozilla::ipc::AssertIsOnBackgroundThread();
for (SharedWorkerParent* actor : mActors) {
Unused << actor->SendError(aValue);
}
}
void SharedWorkerManager::LockNotified(bool aCreated) {
::mozilla::ipc::AssertIsOnBackgroundThread();
MOZ_ASSERT_IF(!aCreated, mLockCount > 0);
mLockCount += aCreated ? 1 : -1;
// Notify only when we either:
// 1. Got a new lock when nothing were there
// 2. Lost all locks
if ((aCreated && mLockCount == 1) || !mLockCount) {
for (SharedWorkerParent* actor : mActors) {
Unused << actor->SendNotifyLock(aCreated);
}
}
};
void SharedWorkerManager::WebTransportNotified(bool aCreated) {
::mozilla::ipc::AssertIsOnBackgroundThread();
MOZ_ASSERT_IF(!aCreated, mWebTransportCount > 0);
mWebTransportCount += aCreated ? 1 : -1;
// Notify only when we either:
// 1. Got a first WebTransport
// 2. The last WebTransport goes away
if ((aCreated && mWebTransportCount == 1) || mWebTransportCount == 0) {
for (SharedWorkerParent* actor : mActors) {
Unused << actor->SendNotifyWebTransport(aCreated);
}
}
};
void SharedWorkerManager::Terminated() {
::mozilla::ipc::AssertIsOnBackgroundThread();
for (SharedWorkerParent* actor : mActors) {
Unused << actor->SendTerminate();
}
}
void SharedWorkerManager::RegisterHolder(SharedWorkerManagerHolder* aHolder) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aHolder);
MOZ_ASSERT(!mHolders.Contains(aHolder));
mHolders.AppendElement(aHolder);
}
void SharedWorkerManager::UnregisterHolder(SharedWorkerManagerHolder* aHolder) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aHolder);
MOZ_ASSERT(mHolders.Contains(aHolder));
mHolders.RemoveElement(aHolder);
if (!mHolders.IsEmpty()) {
return;
}
// Time to go.
aHolder->Service()->RemoveWorkerManagerOnMainThread(this);
RefPtr<SharedWorkerManager> self = this;
mPBackgroundEventTarget->Dispatch(
NS_NewRunnableFunction(
"SharedWorkerService::RemoveWorkerManagerOnMainThread",
[self]() { self->Terminate(); }),
NS_DISPATCH_NORMAL);
}
SharedWorkerManagerHolder::SharedWorkerManagerHolder(
SharedWorkerManager* aManager, SharedWorkerService* aService)
: mManager(aManager), mService(aService) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aManager);
MOZ_ASSERT(aService);
aManager->RegisterHolder(this);
}
SharedWorkerManagerHolder::~SharedWorkerManagerHolder() {
MOZ_ASSERT(NS_IsMainThread());
mManager->UnregisterHolder(this);
}
SharedWorkerManagerWrapper::SharedWorkerManagerWrapper(
already_AddRefed<SharedWorkerManagerHolder> aHolder)
: mHolder(aHolder) {
MOZ_ASSERT(NS_IsMainThread());
}
SharedWorkerManagerWrapper::~SharedWorkerManagerWrapper() {
NS_ReleaseOnMainThread("SharedWorkerManagerWrapper::mHolder",
mHolder.forget());
}
} // namespace mozilla::dom