diff options
Diffstat (limited to 'toolkit/components/finalizationwitness')
4 files changed, 320 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 diff --git a/toolkit/components/finalizationwitness/FinalizationWitnessService.h b/toolkit/components/finalizationwitness/FinalizationWitnessService.h new file mode 100644 index 0000000000..0588d29764 --- /dev/null +++ b/toolkit/components/finalizationwitness/FinalizationWitnessService.h @@ -0,0 +1,32 @@ +/* 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/. */ + +#ifndef mozilla_finalizationwitnessservice_h__ +#define mozilla_finalizationwitnessservice_h__ + +#include "nsIFinalizationWitnessService.h" +#include "nsIObserver.h" + +namespace mozilla { + +/** + * XPConnect initializer, for use in the main thread. + */ +class FinalizationWitnessService final : public nsIFinalizationWitnessService, + public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIFINALIZATIONWITNESSSERVICE + NS_DECL_NSIOBSERVER + + nsresult Init(); + + private: + ~FinalizationWitnessService() = default; + void operator=(const FinalizationWitnessService* other) = delete; +}; + +} // namespace mozilla + +#endif // mozilla_finalizationwitnessservice_h__ diff --git a/toolkit/components/finalizationwitness/moz.build b/toolkit/components/finalizationwitness/moz.build new file mode 100644 index 0000000000..a70228fba2 --- /dev/null +++ b/toolkit/components/finalizationwitness/moz.build @@ -0,0 +1,28 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Async Tooling") + +SOURCES += [ + "FinalizationWitnessService.cpp", +] + +XPIDL_SOURCES += [ + "nsIFinalizationWitnessService.idl", +] + +XPIDL_MODULE = "toolkit_finalizationwitness" + +EXPORTS.mozilla += [ + "FinalizationWitnessService.h", +] + +LOCAL_INCLUDES += [ + "/js/xpconnect/loader", +] + +FINAL_LIBRARY = "xul" diff --git a/toolkit/components/finalizationwitness/nsIFinalizationWitnessService.idl b/toolkit/components/finalizationwitness/nsIFinalizationWitnessService.idl new file mode 100644 index 0000000000..71b6400c46 --- /dev/null +++ b/toolkit/components/finalizationwitness/nsIFinalizationWitnessService.idl @@ -0,0 +1,35 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* 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 "nsISupports.idl" + + +[scriptable, uuid(15686f9d-483e-4361-98cd-37f1e8f1e61d)] +interface nsIFinalizationWitnessService: nsISupports +{ + /** + * Create a new Finalization Witness. + * + * A finalization witness is an object whose sole role is to + * broadcast when it is garbage-collected. Once the witness is + * created, call method its method |forget()| to prevent the + * broadcast. + * + * @param aTopic The topic that the witness will broadcast using + * Services.obs. + * @param aString The string that the witness will broadcast. + * @return An object with a single method |forget()|. + */ + [implicit_jscontext] + jsval make(in string aTopic, in wstring aString); +}; + +%{ C++ + +#define FINALIZATIONWITNESSSERVICE_CID {0x15686f9d,0x483e,0x4361,{0x98,0xcd,0x37,0xf1,0xe8,0xf1,0xe6,0x1d}} +#define FINALIZATIONWITNESSSERVICE_CONTRACTID "@mozilla.org/toolkit/finalizationwitness;1" + +%} |