/* 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 "mozJSComponentLoader.h" #include "nsZipArchive.h" #include "mozilla/Scoped.h" #include "mozilla/Services.h" #include "mozilla/NullPtr.h" #include "nsIObserverService.h" #include "nsThreadUtils.h" // Implementation of nsIFinalizationWitnessService 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 MOZ_FINAL: public nsRunnable { public: FinalizationEvent(const char* aTopic, const jschar* aValue) : mTopic(aTopic) , mValue(aValue) { } NS_METHOD Run() { 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(JSFreeOp *fop, JSObject *objSelf) { nsRefPtr event = ExtractFinalizationEvent(objSelf); if (event == nullptr) { // Forget() has been called return; } // Notify observers. Since we are executed during garbage-collection, // we need to dispatch the notification to the main thread. (void)NS_DispatchToMainThread(event); // 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 JSClass sWitnessClass = { "FinalizationWitness", JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS), JS_PropertyStub /* addProperty */, JS_DeletePropertyStub /* delProperty */, JS_PropertyStub /* getProperty */, JS_StrictPropertyStub /* setProperty */, JS_EnumerateStub /* enumerate */, JS_ResolveStub /* resolve */, JS_ConvertStub /* convert */, Finalize /* finalize */ }; 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, JS::CallArgs args) { if (args.length() != 0) { JS_ReportError(cx, "forget() takes no arguments"); return false; } JS::Rooted valSelf(cx, args.thisv()); JS::Rooted objSelf(cx, &valSelf.toObject()); nsRefPtr event = ExtractFinalizationEvent(objSelf); if (event == nullptr) { JS_ReportError(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 }; } NS_IMPL_ISUPPORTS1(FinalizationWitnessService, nsIFinalizationWitnessService) /** * 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::Value *aRetval) { MOZ_ASSERT(aRetval); JS::Rooted objResult(aCx, JS_NewObject(aCx, &sWitnessClass, nullptr, nullptr)); if (!objResult) { return NS_ERROR_OUT_OF_MEMORY; } if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) { return NS_ERROR_FAILURE; } nsRefPtr event = new FinalizationEvent(aTopic, aValue); // Transfer ownership of the addrefed |event| to |objResult|. JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT, JS::PrivateValue(event.forget().get())); aRetval->setObject(*objResult); return NS_OK; } } // namespace mozilla