Bug 1855742 - Part 5. Refactor canvas shutdown to be synchronized with CanvasManagerChild. r=lsalzman

Because we cannot control the ordering of shutdown notification from
WorkerRef, we need to choose one centralized point to begin shutdown of
the main thread or the owning DOM worker. Since there are other objects
owned by CanvasManagerChild, we also switch to a ThreadSafeWorkerRef.

Differential Revision: https://phabricator.services.mozilla.com/D195120
This commit is contained in:
Andrew Osmond 2023-12-19 05:10:33 +00:00
parent fca521d398
commit 696dee1a1a
11 changed files with 123 additions and 104 deletions

View File

@ -24,6 +24,7 @@
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/GeneratePlaceholderCanvasData.h"
#include "mozilla/dom/VideoFrame.h"
#include "mozilla/gfx/CanvasManagerChild.h"
#include "nsPresContext.h"
#include "nsIInterfaceRequestorUtils.h"
@ -1130,7 +1131,13 @@ CanvasRenderingContext2D::~CanvasRenderingContext2D() {
}
}
void CanvasRenderingContext2D::Initialize() { AddShutdownObserver(); }
nsresult CanvasRenderingContext2D::Initialize() {
if (NS_WARN_IF(!AddShutdownObserver())) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
JSObject* CanvasRenderingContext2D::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
@ -1218,8 +1225,6 @@ void CanvasRenderingContext2D::ResetBitmap(bool aFreeBuffer) {
}
void CanvasRenderingContext2D::OnShutdown() {
mShutdownObserver = nullptr;
RefPtr<PersistentBufferProvider> provider = mBufferProvider;
ResetBitmap();
@ -1227,21 +1232,44 @@ void CanvasRenderingContext2D::OnShutdown() {
if (provider) {
provider->OnShutdown();
}
if (mOffscreenCanvas) {
mOffscreenCanvas->Destroy();
}
mHasShutdown = true;
}
void CanvasRenderingContext2D::AddShutdownObserver() {
MOZ_ASSERT(!mShutdownObserver);
MOZ_ASSERT(NS_IsMainThread());
bool CanvasRenderingContext2D::AddShutdownObserver() {
auto* const canvasManager = CanvasManagerChild::Get();
if (NS_WARN_IF(!canvasManager)) {
if (NS_IsMainThread()) {
mShutdownObserver = new CanvasShutdownObserver(this);
nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
return true;
}
mShutdownObserver = new CanvasShutdownObserver(this);
nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
mHasShutdown = true;
return false;
}
canvasManager->AddShutdownObserver(this);
return true;
}
void CanvasRenderingContext2D::RemoveShutdownObserver() {
if (mShutdownObserver) {
mShutdownObserver->OnShutdown();
mShutdownObserver = nullptr;
return;
}
auto* const canvasManager = CanvasManagerChild::MaybeGet();
if (!canvasManager) {
return;
}
canvasManager->RemoveShutdownObserver(this);
}
void CanvasRenderingContext2D::SetStyleFromString(const nsACString& aStr,
@ -1864,6 +1892,10 @@ void CanvasRenderingContext2D::ReturnTarget(bool aForceReset) {
NS_IMETHODIMP
CanvasRenderingContext2D::InitializeWithDrawTarget(
nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) {
if (NS_WARN_IF(!AddShutdownObserver())) {
return NS_ERROR_FAILURE;
}
RemovePostRefreshObserver();
mDocShell = aShell;
AddPostRefreshObserverIfNecessary();

View File

@ -464,7 +464,7 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
* Gets the pres shell from either the canvas element or the doc shell
*/
PresShell* GetPresShell() final;
void Initialize() override;
nsresult Initialize() override;
NS_IMETHOD SetDimensions(int32_t aWidth, int32_t aHeight) override;
NS_IMETHOD InitializeWithDrawTarget(
nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) override;
@ -565,7 +565,7 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
virtual UniquePtr<uint8_t[]> GetImageBuffer(
int32_t* out_format, gfx::IntSize* out_imageSize) override;
virtual void OnShutdown();
void OnShutdown();
/**
* Update CurrentState().filter with the filter description for
@ -831,11 +831,13 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
// Whether the application expects to use operations that perform poorly with
// acceleration.
bool mWillReadFrequently = false;
// Whether or not we have already shutdown.
bool mHasShutdown = false;
RefPtr<CanvasShutdownObserver> mShutdownObserver;
virtual void AddShutdownObserver();
virtual void RemoveShutdownObserver();
virtual bool AlreadyShutDown() const { return !mShutdownObserver; }
bool AddShutdownObserver();
void RemoveShutdownObserver();
bool AlreadyShutDown() const { return mHasShutdown; }
/**
* Flag to avoid duplicate calls to InvalidateFrame. Set to true whenever

View File

@ -184,7 +184,9 @@ CanvasRenderingContextHelper::CreateContextHelper(
}
MOZ_ASSERT(ret);
ret->Initialize();
if (NS_WARN_IF(NS_FAILED(ret->Initialize()))) {
return nullptr;
}
return ret.forget();
}
@ -206,6 +208,7 @@ already_AddRefed<nsISupports> CanvasRenderingContextHelper::GetOrCreateContext(
RefPtr<nsICanvasRenderingContextInternal> context;
context = CreateContext(aContextType);
if (!context) {
aRv.ThrowUnknownError("Failed to create context");
return nullptr;
}

View File

@ -66,11 +66,15 @@ OffscreenCanvas::OffscreenCanvas(
mDisplay(aDisplay) {}
OffscreenCanvas::~OffscreenCanvas() {
Destroy();
NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
mExpandedReader.forget());
}
void OffscreenCanvas::Destroy() {
if (mDisplay) {
mDisplay->DestroyCanvas();
}
NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
mExpandedReader.forget());
}
JSObject* OffscreenCanvas::WrapObject(JSContext* aCx,
@ -270,6 +274,9 @@ already_AddRefed<nsICanvasRenderingContextInternal>
OffscreenCanvas::CreateContext(CanvasContextType aContextType) {
RefPtr<nsICanvasRenderingContextInternal> ret =
CanvasRenderingContextHelper::CreateContext(aContextType);
if (NS_WARN_IF(!ret)) {
return nullptr;
}
ret->SetOffscreenCanvas(this);
return ret.forget();

View File

@ -79,6 +79,8 @@ class OffscreenCanvas final : public DOMEventTargetHelper,
layers::TextureType aTextureType,
already_AddRefed<OffscreenCanvasDisplayHelper> aDisplay);
void Destroy();
nsIGlobalObject* GetParentObject() const { return GetOwnerGlobal(); }
virtual JSObject* WrapObject(JSContext* aCx,

View File

@ -8,36 +8,11 @@
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/dom/OffscreenCanvasRenderingContext2DBinding.h"
#include "mozilla/dom/OffscreenCanvas.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerRef.h"
using namespace mozilla;
namespace mozilla::dom {
class OffscreenCanvasShutdownObserver final {
NS_INLINE_DECL_REFCOUNTING(OffscreenCanvasShutdownObserver)
public:
explicit OffscreenCanvasShutdownObserver(
OffscreenCanvasRenderingContext2D* aOwner)
: mOwner(aOwner) {}
void OnShutdown() {
if (mOwner) {
mOwner->OnShutdown();
mOwner = nullptr;
}
}
void ClearOwner() { mOwner = nullptr; }
private:
~OffscreenCanvasShutdownObserver() = default;
OffscreenCanvasRenderingContext2D* mOwner;
};
NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvasRenderingContext2D,
CanvasRenderingContext2D)
@ -91,45 +66,6 @@ NS_IMETHODIMP OffscreenCanvasRenderingContext2D::InitializeWithDrawTarget(
return NS_ERROR_NOT_IMPLEMENTED;
}
void OffscreenCanvasRenderingContext2D::AddShutdownObserver() {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
if (!workerPrivate) {
// We may be using OffscreenCanvas on the main thread.
CanvasRenderingContext2D::AddShutdownObserver();
return;
}
mOffscreenShutdownObserver =
MakeAndAddRef<OffscreenCanvasShutdownObserver>(this);
mWorkerRef = WeakWorkerRef::Create(
workerPrivate,
[observer = mOffscreenShutdownObserver] { observer->OnShutdown(); });
}
void OffscreenCanvasRenderingContext2D::RemoveShutdownObserver() {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
if (!workerPrivate) {
// We may be using OffscreenCanvas on the main thread.
CanvasRenderingContext2D::RemoveShutdownObserver();
return;
}
if (mOffscreenShutdownObserver) {
mOffscreenShutdownObserver->ClearOwner();
}
mOffscreenShutdownObserver = nullptr;
mWorkerRef = nullptr;
}
void OffscreenCanvasRenderingContext2D::OnShutdown() {
if (mOffscreenShutdownObserver) {
mOffscreenShutdownObserver->ClearOwner();
mOffscreenShutdownObserver = nullptr;
}
CanvasRenderingContext2D::OnShutdown();
}
void OffscreenCanvasRenderingContext2D::Commit(ErrorResult& aRv) {
if (!mOffscreenCanvas->IsTransferredFromElement()) {
return;

View File

@ -15,8 +15,6 @@ class nsIGlobalObject;
namespace mozilla::dom {
class OffscreenCanvas;
class OffscreenCanvasShutdownObserver;
class WeakWorkerRef;
class OffscreenCanvasRenderingContext2D final
: public CanvasRenderingContext2D {
@ -39,27 +37,15 @@ class OffscreenCanvasRenderingContext2D final
void Commit(ErrorResult& aRv);
void OnShutdown() override;
NS_IMETHOD InitializeWithDrawTarget(
nsIDocShell* aShell, NotNull<gfx::DrawTarget*> aTarget) override;
private:
void AddShutdownObserver() override;
void RemoveShutdownObserver() override;
bool AlreadyShutDown() const override {
return !mOffscreenShutdownObserver &&
CanvasRenderingContext2D::AlreadyShutDown();
}
void AddZoneWaitingForGC() override;
void AddAssociatedMemory() override;
void RemoveAssociatedMemory() override;
~OffscreenCanvasRenderingContext2D() override;
RefPtr<OffscreenCanvasShutdownObserver> mOffscreenShutdownObserver;
RefPtr<WeakWorkerRef> mWorkerRef;
};
size_t BindingJSObjectMallocBytes(OffscreenCanvasRenderingContext2D* aContext);

View File

@ -101,7 +101,7 @@ class nsICanvasRenderingContextInternal : public nsISupports,
NS_IMETHOD SetDimensions(int32_t width, int32_t height) = 0;
// Initializes the canvas after the object is constructed.
virtual void Initialize() {}
virtual nsresult Initialize() { return NS_OK; }
// Initializes with an nsIDocShell and DrawTarget. The size is taken from the
// DrawTarget.

View File

@ -535,6 +535,9 @@ HTMLCanvasElement::CreateContext(CanvasContextType aContextType) {
// Note that the compositor backend will be LAYERS_NONE if there is no widget.
RefPtr<nsICanvasRenderingContextInternal> ret =
CreateContextHelper(aContextType, GetCompositorBackendType());
if (NS_WARN_IF(!ret)) {
return nullptr;
}
// Add Observer for webgl canvas.
if (aContextType == CanvasContextType::WebGL1 ||

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CanvasManagerChild.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/dom/CanvasRenderingContext2D.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/gfx/2D.h"
@ -23,7 +25,7 @@ namespace mozilla::gfx {
// The IPDL actor holds a strong reference to CanvasManagerChild which we use
// to keep it alive. The owning thread will tell us to close when it is
// shutdown, either via CanvasManagerChild::Shutdown for the main thread, or
// via a shutdown callback from IPCWorkerRef for worker threads.
// via a shutdown callback from ThreadSafeWorkerRef for worker threads.
MOZ_THREAD_LOCAL(CanvasManagerChild*) CanvasManagerChild::sLocalManager;
Atomic<uint32_t> CanvasManagerChild::sNextId(1);
@ -32,13 +34,19 @@ CanvasManagerChild::CanvasManagerChild(uint32_t aId) : mId(aId) {}
CanvasManagerChild::~CanvasManagerChild() = default;
void CanvasManagerChild::ActorDestroy(ActorDestroyReason aReason) {
DestroyInternal();
if (sLocalManager.get() == this) {
sLocalManager.set(nullptr);
}
mWorkerRef = nullptr;
}
void CanvasManagerChild::Destroy() {
void CanvasManagerChild::DestroyInternal() {
std::set<CanvasRenderingContext2D*> activeCanvas = std::move(mActiveCanvas);
for (const auto& i : activeCanvas) {
i->OnShutdown();
}
if (mActiveResourceTracker) {
mActiveResourceTracker->AgeAllGenerations();
mActiveResourceTracker.reset();
@ -48,6 +56,10 @@ void CanvasManagerChild::Destroy() {
mCanvasChild->Destroy();
mCanvasChild = nullptr;
}
}
void CanvasManagerChild::Destroy() {
DestroyInternal();
// The caller has a strong reference. ActorDestroy will clear sLocalManager
// and mWorkerRef.
@ -112,14 +124,24 @@ void CanvasManagerChild::Destroy() {
auto manager = MakeRefPtr<CanvasManagerChild>(sNextId++);
if (worker) {
// The IPCWorkerRef will let us know when the worker is shutting down. This
// will let us clear our threadlocal reference and close the actor. We rely
// upon an explicit shutdown for the main thread.
manager->mWorkerRef = IPCWorkerRef::Create(
// The ThreadSafeWorkerRef will let us know when the worker is shutting
// down. This will let us clear our threadlocal reference and close the
// actor. We rely upon an explicit shutdown for the main thread.
RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
worker, "CanvasManager", [manager]() { manager->Destroy(); });
if (NS_WARN_IF(!manager->mWorkerRef)) {
if (NS_WARN_IF(!workerRef)) {
return nullptr;
}
manager->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
} else if (NS_IsMainThread()) {
if (NS_WARN_IF(
AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed))) {
return nullptr;
}
} else {
MOZ_ASSERT_UNREACHABLE("Can only be used on main or DOM worker threads!");
return nullptr;
}
if (NS_WARN_IF(!childEndpoint.Bind(manager))) {
@ -145,6 +167,24 @@ void CanvasManagerChild::Destroy() {
return manager;
}
/* static */ CanvasManagerChild* CanvasManagerChild::MaybeGet() {
if (!sLocalManager.initialized()) {
return nullptr;
}
return sLocalManager.get();
}
void CanvasManagerChild::AddShutdownObserver(
dom::CanvasRenderingContext2D* aCanvas) {
mActiveCanvas.insert(aCanvas);
}
void CanvasManagerChild::RemoveShutdownObserver(
dom::CanvasRenderingContext2D* aCanvas) {
mActiveCanvas.erase(aCanvas);
}
void CanvasManagerChild::EndCanvasTransaction() {
if (!mCanvasChild) {
return;

View File

@ -10,10 +10,12 @@
#include "mozilla/gfx/PCanvasManagerChild.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/ThreadLocal.h"
#include <set>
namespace mozilla {
namespace dom {
class IPCWorkerRef;
class CanvasRenderingContext2D;
class ThreadSafeWorkerRef;
class WorkerPrivate;
} // namespace dom
@ -42,10 +44,14 @@ class CanvasManagerChild final : public PCanvasManagerChild {
void ActorDestroy(ActorDestroyReason aReason) override;
static CanvasManagerChild* Get();
static CanvasManagerChild* MaybeGet();
static void Shutdown();
static bool CreateParent(
mozilla::ipc::Endpoint<PCanvasManagerParent>&& aEndpoint);
void AddShutdownObserver(dom::CanvasRenderingContext2D* aCanvas);
void RemoveShutdownObserver(dom::CanvasRenderingContext2D* aCanvas);
bool IsCanvasActive() { return mActive; }
void EndCanvasTransaction();
void ClearCachedResources();
@ -60,12 +66,14 @@ class CanvasManagerChild final : public PCanvasManagerChild {
private:
~CanvasManagerChild();
void DestroyInternal();
void Destroy();
RefPtr<mozilla::dom::IPCWorkerRef> mWorkerRef;
RefPtr<mozilla::dom::ThreadSafeWorkerRef> mWorkerRef;
RefPtr<layers::CanvasChild> mCanvasChild;
RefPtr<webgpu::WebGPUChild> mWebGPUChild;
UniquePtr<layers::ActiveResourceTracker> mActiveResourceTracker;
std::set<dom::CanvasRenderingContext2D*> mActiveCanvas;
const uint32_t mId;
bool mActive = true;
bool mBlocked = false;