From 5af1cef3ff27f3c782e967b9b5b09babad6e7c8c Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Wed, 15 Jan 2020 14:22:04 +0000 Subject: [PATCH] Bug 1596756 - Support FinalizationGroup objects in the browser r=mccr8 Add browser support for FinalizationGroup by setting the HostCleanupFinalizationGroupCallback in CycleCollectedJSContext. The callback adds groups pending cleanup to a vector stored in a PersistentRooted. A runnable is dispatched to call back into the JS engine and perform cleanup at a later time as a separate task. Using AutoEntryScript reports errors to the console. Differential Revision: https://phabricator.services.mozilla.com/D53248 --HG-- extra : moz-landing-system : lando --- xpcom/base/CycleCollectedJSContext.cpp | 59 ++++++++++++++++++++++++++ xpcom/base/CycleCollectedJSContext.h | 13 ++++++ 2 files changed, 72 insertions(+) diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp index 299e5da80bcf..b70c92033cdc 100644 --- a/xpcom/base/CycleCollectedJSContext.cpp +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -13,6 +13,7 @@ #include "mozilla/Move.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Sprintf.h" +#include "mozilla/SystemGroup.h" #include "mozilla/Telemetry.h" #include "mozilla/TimelineConsumers.h" #include "mozilla/TimelineMarker.h" @@ -77,6 +78,10 @@ CycleCollectedJSContext::~CycleCollectedJSContext() { return; } + JS::SetHostCleanupFinalizationGroupCallback( + mJSContext, nullptr, nullptr); + mFinalizationGroupsToCleanUp.reset(); + JS_SetContextPrivate(mJSContext, nullptr); mRuntime->SetContext(nullptr); @@ -144,6 +149,10 @@ nsresult CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime, JS::GCVector( js::SystemAllocPolicy())); + mFinalizationGroupsToCleanUp.init(mJSContext); + JS::SetHostCleanupFinalizationGroupCallback( + mJSContext, CleanupFinalizationGroupCallback, this); + // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*). JS_SetContextPrivate(mJSContext, static_cast(this)); @@ -722,4 +731,54 @@ nsresult CycleCollectedJSContext::NotifyUnhandledRejections::Cancel() { } return NS_OK; } + +class CleanupFinalizationGroupsRunnable : public CancelableRunnable { + public: + explicit CleanupFinalizationGroupsRunnable(CycleCollectedJSContext* aContext) + : CancelableRunnable("CleanupFinalizationGroupsRunnable"), mContext(aContext) {} + NS_DECL_NSIRUNNABLE + private: + CycleCollectedJSContext* mContext; +}; + +NS_IMETHODIMP +CleanupFinalizationGroupsRunnable::Run() { + if (mContext->mFinalizationGroupsToCleanUp.empty()) { + return NS_OK; + } + + JS::RootingContext* cx = mContext->RootingCx(); + + JS::Rooted groups(cx); + std::swap(groups.get(), mContext->mFinalizationGroupsToCleanUp.get()); + + JS::Rooted group(cx); + for (const auto& g : groups) { + group = g; + + AutoEntryScript aes(group, "cleanupFinalizationGroup"); + mozilla::Unused << JS::CleanupQueuedFinalizationGroup(aes.cx(), group); + } + + return NS_OK; +} + +/* static */ +void CycleCollectedJSContext::CleanupFinalizationGroupCallback(JSObject* aGroup, + void* aData) { + CycleCollectedJSContext* ccjs = static_cast(aData); + ccjs->QueueFinalizationGroupForCleanup(aGroup); +} + +void CycleCollectedJSContext::QueueFinalizationGroupForCleanup( + JSObject* aGroup) { + bool firstGroup = mFinalizationGroupsToCleanUp.empty(); + MOZ_ALWAYS_TRUE(mFinalizationGroupsToCleanUp.append(aGroup)); + if (firstGroup) { + RefPtr cleanup = + new CleanupFinalizationGroupsRunnable(this); + NS_DispatchToCurrentThread(cleanup.forget()); + } +} + } // namespace mozilla diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h index 736564a54557..fc3f3d6d2a1c 100644 --- a/xpcom/base/CycleCollectedJSContext.h +++ b/xpcom/base/CycleCollectedJSContext.h @@ -17,6 +17,7 @@ #include "mozilla/dom/AtomList.h" #include "mozilla/dom/Promise.h" #include "jsapi.h" +#include "js/GCVector.h" #include "js/Promise.h" #include "nsCOMPtr.h" @@ -263,6 +264,9 @@ class CycleCollectedJSContext class SavedMicroTaskQueue; js::UniquePtr saveJobQueue(JSContext*) override; + static void CleanupFinalizationGroupCallback(JSObject* aGroup, void* aData); + void QueueFinalizationGroupForCleanup(JSObject* aGroup); + private: CycleCollectedJSRuntime* mRuntime; @@ -336,6 +340,15 @@ class CycleCollectedJSContext CycleCollectedJSContext* mCx; PromiseArray mUnhandledRejections; }; + + // Support for JS FinalizationGroup objects. + // + // These allow a JS callback to be registered that is called when an object + // dies. The browser part of the implementation keeps a vector of + // FinalizationGroups with pending callbacks here. + friend class CleanupFinalizationGroupsRunnable; + using ObjectVector = JS::GCVector; + JS::PersistentRooted mFinalizationGroupsToCleanUp; }; class MOZ_STACK_CLASS nsAutoMicroTask {