/* 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 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 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(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 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 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 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 valSelf(cx, args.thisv()); JS::Rooted objSelf(cx, &valSelf.toObject()); RefPtr 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(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 aRetval) { JS::Rooted 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 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 obs = mozilla::services::GetObserverService(); if (obs) { obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); } return NS_OK; } nsresult FinalizationWitnessService::Init() { nsCOMPtr obs = mozilla::services::GetObserverService(); if (!obs) { return NS_ERROR_FAILURE; } return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); } } // namespace mozilla