summaryrefslogtreecommitdiffstats
path: root/toolkit/components/finalizationwitness
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/finalizationwitness')
-rw-r--r--toolkit/components/finalizationwitness/FinalizationWitnessService.cpp225
-rw-r--r--toolkit/components/finalizationwitness/FinalizationWitnessService.h32
-rw-r--r--toolkit/components/finalizationwitness/moz.build28
-rw-r--r--toolkit/components/finalizationwitness/nsIFinalizationWitnessService.idl35
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"
+
+%}