Source code

Revision control

Copy as Markdown

Other Tools

/* 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