michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "FinalizationWitnessService.h" michael@0: michael@0: #include "nsString.h" michael@0: #include "jsapi.h" michael@0: #include "js/CallNonGenericMethod.h" michael@0: #include "mozJSComponentLoader.h" michael@0: #include "nsZipArchive.h" michael@0: michael@0: #include "mozilla/Scoped.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/NullPtr.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: michael@0: // Implementation of nsIFinalizationWitnessService michael@0: michael@0: namespace mozilla { michael@0: michael@0: namespace { michael@0: michael@0: /** michael@0: * An event meant to be dispatched to the main thread upon finalization michael@0: * of a FinalizationWitness, unless method |forget()| has been called. michael@0: * michael@0: * Held as private data by each instance of FinalizationWitness. michael@0: * Important note: we maintain the invariant that these private data michael@0: * slots are already addrefed. michael@0: */ michael@0: class FinalizationEvent MOZ_FINAL: public nsRunnable michael@0: { michael@0: public: michael@0: FinalizationEvent(const char* aTopic, michael@0: const jschar* aValue) michael@0: : mTopic(aTopic) michael@0: , mValue(aValue) michael@0: { } michael@0: michael@0: NS_METHOD Run() { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (!observerService) { michael@0: // This is either too early or, more likely, too late for notifications. michael@0: // Bail out. michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: (void)observerService-> michael@0: NotifyObservers(nullptr, mTopic.get(), mValue.get()); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: /** michael@0: * The topic on which to broadcast the notification of finalization. michael@0: * michael@0: * Deallocated on the main thread. michael@0: */ michael@0: const nsCString mTopic; michael@0: michael@0: /** michael@0: * The result of converting the exception to a string. michael@0: * michael@0: * Deallocated on the main thread. michael@0: */ michael@0: const nsString mValue; michael@0: }; michael@0: michael@0: enum { michael@0: WITNESS_SLOT_EVENT, michael@0: WITNESS_INSTANCES_SLOTS michael@0: }; michael@0: michael@0: /** michael@0: * Extract the FinalizationEvent from an instance of FinalizationWitness michael@0: * and clear the slot containing the FinalizationEvent. michael@0: */ michael@0: already_AddRefed michael@0: ExtractFinalizationEvent(JSObject *objSelf) michael@0: { michael@0: JS::Value slotEvent = JS_GetReservedSlot(objSelf, WITNESS_SLOT_EVENT); michael@0: if (slotEvent.isUndefined()) { michael@0: // Forget() has been called michael@0: return nullptr; michael@0: } michael@0: michael@0: JS_SetReservedSlot(objSelf, WITNESS_SLOT_EVENT, JS::UndefinedValue()); michael@0: michael@0: return dont_AddRef(static_cast(slotEvent.toPrivate())); michael@0: } michael@0: michael@0: /** michael@0: * Finalizer for instances of FinalizationWitness. michael@0: * michael@0: * Unless method Forget() has been called, the finalizer displays an error michael@0: * message. michael@0: */ michael@0: void Finalize(JSFreeOp *fop, JSObject *objSelf) michael@0: { michael@0: nsRefPtr event = ExtractFinalizationEvent(objSelf); michael@0: if (event == nullptr) { michael@0: // Forget() has been called michael@0: return; michael@0: } michael@0: michael@0: // Notify observers. Since we are executed during garbage-collection, michael@0: // we need to dispatch the notification to the main thread. michael@0: (void)NS_DispatchToMainThread(event); michael@0: // We may fail at dispatching to the main thread if we arrive too late michael@0: // during shutdown. In that case, there is not much we can do. michael@0: } michael@0: michael@0: static const JSClass sWitnessClass = { michael@0: "FinalizationWitness", michael@0: JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS), michael@0: JS_PropertyStub /* addProperty */, michael@0: JS_DeletePropertyStub /* delProperty */, michael@0: JS_PropertyStub /* getProperty */, michael@0: JS_StrictPropertyStub /* setProperty */, michael@0: JS_EnumerateStub /* enumerate */, michael@0: JS_ResolveStub /* resolve */, michael@0: JS_ConvertStub /* convert */, michael@0: Finalize /* finalize */ michael@0: }; michael@0: michael@0: bool IsWitness(JS::Handle v) michael@0: { michael@0: return v.isObject() && JS_GetClass(&v.toObject()) == &sWitnessClass; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * JS method |forget()| michael@0: * michael@0: * === JS documentation michael@0: * michael@0: * Neutralize the witness. Once this method is called, the witness will michael@0: * never report any error. michael@0: */ michael@0: bool ForgetImpl(JSContext* cx, JS::CallArgs args) michael@0: { michael@0: if (args.length() != 0) { michael@0: JS_ReportError(cx, "forget() takes no arguments"); michael@0: return false; michael@0: } michael@0: JS::Rooted valSelf(cx, args.thisv()); michael@0: JS::Rooted objSelf(cx, &valSelf.toObject()); michael@0: michael@0: nsRefPtr event = ExtractFinalizationEvent(objSelf); michael@0: if (event == nullptr) { michael@0: JS_ReportError(cx, "forget() called twice"); michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: bool Forget(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = CallArgsFromVp(argc, vp); michael@0: return JS::CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: static const JSFunctionSpec sWitnessClassFunctions[] = { michael@0: JS_FN("forget", Forget, 0, JSPROP_READONLY | JSPROP_PERMANENT), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(FinalizationWitnessService, nsIFinalizationWitnessService) michael@0: michael@0: /** michael@0: * Create a new Finalization Witness. michael@0: * michael@0: * A finalization witness is an object whose sole role is to notify michael@0: * observers when it is gc-ed. Once the witness is created, call its michael@0: * method |forget()| to prevent the observers from being notified. michael@0: * michael@0: * @param aTopic The notification topic. michael@0: * @param aValue The notification value. Converted to a string. michael@0: * michael@0: * @constructor michael@0: */ michael@0: NS_IMETHODIMP michael@0: FinalizationWitnessService::Make(const char* aTopic, michael@0: const char16_t* aValue, michael@0: JSContext* aCx, michael@0: JS::MutableHandle aRetval) michael@0: { michael@0: JS::Rooted objResult(aCx, JS_NewObject(aCx, &sWitnessClass, JS::NullPtr(), michael@0: JS::NullPtr())); michael@0: if (!objResult) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr event = new FinalizationEvent(aTopic, aValue); michael@0: michael@0: // Transfer ownership of the addrefed |event| to |objResult|. michael@0: JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT, michael@0: JS::PrivateValue(event.forget().take())); michael@0: michael@0: aRetval.setObject(*objResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla