diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/finalizationwitness/FinalizationWitnessService.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/finalizationwitness/FinalizationWitnessService.cpp')
-rw-r--r-- | toolkit/components/finalizationwitness/FinalizationWitnessService.cpp | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp b/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp new file mode 100644 index 0000000000..12dc3fc590 --- /dev/null +++ b/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp @@ -0,0 +1,225 @@ +/* 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 "FinalizationWitnessService.h" + +#include "nsString.h" +#include "jsapi.h" +#include "js/CallNonGenericMethod.h" +#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot +#include "js/PropertyAndElement.h" // JS_DefineFunctions +#include "js/PropertySpec.h" +#include "nsIThread.h" + +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" + +// Implementation of nsIFinalizationWitnessService + +static bool gShuttingDown = false; + +namespace mozilla { + +namespace { + +/** + * An event meant to be dispatched to the main thread upon finalization + * of a FinalizationWitness, unless method |forget()| has been called. + * + * Held as private data by each instance of FinalizationWitness. + * Important note: we maintain the invariant that these private data + * slots are already addrefed. + */ +class FinalizationEvent final : public Runnable { + public: + FinalizationEvent(const char* aTopic, const char16_t* aValue) + : Runnable("FinalizationEvent"), mTopic(aTopic), mValue(aValue) {} + + NS_IMETHOD Run() override { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) { + // This is either too early or, more likely, too late for notifications. + // Bail out. + return NS_ERROR_NOT_AVAILABLE; + } + (void)observerService->NotifyObservers(nullptr, mTopic.get(), mValue.get()); + return NS_OK; + } + + private: + /** + * The topic on which to broadcast the notification of finalization. + * + * Deallocated on the main thread. + */ + const nsCString mTopic; + + /** + * The result of converting the exception to a string. + * + * Deallocated on the main thread. + */ + const nsString mValue; +}; + +enum { WITNESS_SLOT_EVENT, WITNESS_INSTANCES_SLOTS }; + +/** + * Extract the FinalizationEvent from an instance of FinalizationWitness + * and clear the slot containing the FinalizationEvent. + */ +already_AddRefed<FinalizationEvent> ExtractFinalizationEvent( + JSObject* objSelf) { + JS::Value slotEvent = JS::GetReservedSlot(objSelf, WITNESS_SLOT_EVENT); + if (slotEvent.isUndefined()) { + // Forget() has been called + return nullptr; + } + + JS_SetReservedSlot(objSelf, WITNESS_SLOT_EVENT, JS::UndefinedValue()); + + return dont_AddRef(static_cast<FinalizationEvent*>(slotEvent.toPrivate())); +} + +/** + * Finalizer for instances of FinalizationWitness. + * + * Unless method Forget() has been called, the finalizer displays an error + * message. + */ +void Finalize(JS::GCContext* gcx, JSObject* objSelf) { + RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf); + if (event == nullptr || gShuttingDown) { + // NB: event will be null if Forget() has been called + return; + } + + // Notify observers. Since we are executed during garbage-collection, + // we need to dispatch the notification to the main thread. + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + if (mainThread) { + mainThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL); + } + // We may fail at dispatching to the main thread if we arrive too late + // during shutdown. In that case, there is not much we can do. +} + +static const JSClassOps sWitnessClassOps = { + nullptr /* addProperty */, + nullptr /* delProperty */, + nullptr /* enumerate */, + nullptr /* newEnumerate */, + nullptr /* resolve */, + nullptr /* mayResolve */, + Finalize /* finalize */ +}; + +static const JSClass sWitnessClass = { + "FinalizationWitness", + JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS) | + JSCLASS_FOREGROUND_FINALIZE, + &sWitnessClassOps}; + +bool IsWitness(JS::Handle<JS::Value> v) { + return v.isObject() && JS::GetClass(&v.toObject()) == &sWitnessClass; +} + +/** + * JS method |forget()| + * + * === JS documentation + * + * Neutralize the witness. Once this method is called, the witness will + * never report any error. + */ +bool ForgetImpl(JSContext* cx, const JS::CallArgs& args) { + if (args.length() != 0) { + JS_ReportErrorASCII(cx, "forget() takes no arguments"); + return false; + } + JS::Rooted<JS::Value> valSelf(cx, args.thisv()); + JS::Rooted<JSObject*> objSelf(cx, &valSelf.toObject()); + + RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf); + if (event == nullptr) { + JS_ReportErrorASCII(cx, "forget() called twice"); + return false; + } + + args.rval().setUndefined(); + return true; +} + +bool Forget(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + return JS::CallNonGenericMethod<IsWitness, ForgetImpl>(cx, args); +} + +static const JSFunctionSpec sWitnessClassFunctions[] = { + JS_FN("forget", Forget, 0, JSPROP_READONLY | JSPROP_PERMANENT), JS_FS_END}; + +} // namespace + +NS_IMPL_ISUPPORTS(FinalizationWitnessService, nsIFinalizationWitnessService, + nsIObserver) + +/** + * Create a new Finalization Witness. + * + * A finalization witness is an object whose sole role is to notify + * observers when it is gc-ed. Once the witness is created, call its + * method |forget()| to prevent the observers from being notified. + * + * @param aTopic The notification topic. + * @param aValue The notification value. Converted to a string. + * + * @constructor + */ +NS_IMETHODIMP +FinalizationWitnessService::Make(const char* aTopic, const char16_t* aValue, + JSContext* aCx, + JS::MutableHandle<JS::Value> aRetval) { + JS::Rooted<JSObject*> objResult(aCx, JS_NewObject(aCx, &sWitnessClass)); + if (!objResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) { + return NS_ERROR_FAILURE; + } + + RefPtr<FinalizationEvent> event = new FinalizationEvent(aTopic, aValue); + + // Transfer ownership of the addrefed |event| to |objResult|. + JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT, + JS::PrivateValue(event.forget().take())); + + aRetval.setObject(*objResult); + return NS_OK; +} + +NS_IMETHODIMP +FinalizationWitnessService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aValue) { + MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); + gShuttingDown = true; + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + + return NS_OK; +} + +nsresult FinalizationWitnessService::Init() { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (!obs) { + return NS_ERROR_FAILURE; + } + + return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); +} + +} // namespace mozilla |