Bug 903433 - Add FinalizationWitnessService. r=Waldo, r=glandium, sr=Mossop
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Sat, 12 Oct 2013 14:17:43 -0400
changeset 164398 15dd7257c1f6d4335f662564326301100461f1bc
parent 164397 30d64411e9a32c702dc49973f48fb33743c8d52f
child 164399 6cbcb6e96dce6d5bd4746ba259bda51b0c8ce830
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo, glandium, Mossop
bugs903433
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 903433 - Add FinalizationWitnessService. r=Waldo, r=glandium, sr=Mossop
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
mobile/android/installer/package-manifest.in
toolkit/components/build/Makefile.in
toolkit/components/build/nsToolkitCompsModule.cpp
toolkit/components/finalizationwitness/FinalizationWitnessService.cpp
toolkit/components/finalizationwitness/FinalizationWitnessService.h
toolkit/components/finalizationwitness/moz.build
toolkit/components/finalizationwitness/nsIFinalizationWitnessService.idl
toolkit/components/moz.build
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -294,16 +294,17 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
+@BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 #ifdef MOZ_USE_NATIVE_UCONV
 @BINPATH@/components/ucnative.xpt
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -300,16 +300,17 @@
 @BINPATH@/components/services-crypto-component.xpt
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/browser/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
+@BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
 @BINPATH@/components/unicharutil.xpt
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -238,16 +238,17 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
+@BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
 @BINPATH@/components/unicharutil.xpt
--- a/toolkit/components/build/Makefile.in
+++ b/toolkit/components/build/Makefile.in
@@ -23,16 +23,17 @@ SHARED_LIBRARY_LIBS = \
   ../find/$(LIB_PREFIX)mozfind_s.$(LIB_SUFFIX) \
   ../typeaheadfind/$(LIB_PREFIX)fastfind_s.$(LIB_SUFFIX) \
   ../startup/$(LIB_PREFIX)appstartup_s.$(LIB_SUFFIX) \
   ../statusfilter/$(LIB_PREFIX)mozbrwsr_s.$(LIB_SUFFIX) \
   ../downloads/$(LIB_PREFIX)download_s.$(LIB_SUFFIX) \
   ../jsdownloads/src/$(LIB_PREFIX)jsdownloads_s.$(LIB_SUFFIX) \
   ../protobuf/$(LIB_PREFIX)protobuf_s.$(LIB_SUFFIX) \
   ../intl/$(LIB_PREFIX)intl_s.$(LIB_SUFFIX) \
+  ../finalizationwitness/$(LIB_PREFIX)finalizationwitness_s.$(LIB_SUFFIX) \
   $(NULL)
 
 ifndef MOZ_DISABLE_PARENTAL_CONTROLS
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 SHARED_LIBRARY_LIBS += ../parentalcontrols/$(LIB_PREFIX)parentalcontrols_s.$(LIB_SUFFIX)
 LOCAL_INCLUDES += \
   -I$(srcdir)/../parentalcontrols \
   $(NULL)
--- a/toolkit/components/build/nsToolkitCompsModule.cpp
+++ b/toolkit/components/build/nsToolkitCompsModule.cpp
@@ -29,16 +29,17 @@
 #include "ApplicationReputation.h"
 #include "nsUrlClassifierDBService.h"
 #include "nsUrlClassifierStreamUpdater.h"
 #include "nsUrlClassifierUtils.h"
 #include "nsUrlClassifierPrefixSet.h"
 #endif
 
 #include "nsBrowserStatusFilter.h"
+#include "mozilla/FinalizationWitnessService.h"
 
 /////////////////////////////////////////////////////////////////////////////
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppStartup, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUserInfo)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFindService)
 
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
@@ -80,16 +81,17 @@ nsUrlClassifierDBServiceConstructor(nsIS
     return rv;
 }
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter)
 #if defined(USE_MOZ_UPDATER)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
 #endif
+NS_GENERIC_FACTORY_CONSTRUCTOR(FinalizationWitnessService)
 
 NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
 NS_DEFINE_NAMED_CID(NS_USERINFO_CID);
 NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID);
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
 NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_DOWNLOADMANAGER_CID);
@@ -104,16 +106,17 @@ NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERDBSE
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERSTREAMUPDATER_CID);
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTILS_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID);
 NS_DEFINE_NAMED_CID(NS_CHARSETMENU_CID);
 #if defined(USE_MOZ_UPDATER)
 NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
 #endif
+NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
 
 static const mozilla::Module::CIDEntry kToolkitCIDs[] = {
   { &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor },
   { &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor },
   { &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor },
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
   { &kNS_PARENTALCONTROLSSERVICE_CID, false, nullptr, nsParentalControlsServiceWinConstructor },
 #endif
@@ -129,16 +132,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_URLCLASSIFIERSTREAMUPDATER_CID, false, nullptr, nsUrlClassifierStreamUpdaterConstructor },
   { &kNS_URLCLASSIFIERUTILS_CID, false, nullptr, nsUrlClassifierUtilsConstructor },
 #endif
   { &kNS_BROWSERSTATUSFILTER_CID, false, nullptr, nsBrowserStatusFilterConstructor },
   { &kNS_CHARSETMENU_CID, false, nullptr, NS_NewCharsetMenu },
 #if defined(USE_MOZ_UPDATER)
   { &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
 #endif
+  { &kFINALIZATIONWITNESSSERVICE_CID, false, NULL, FinalizationWitnessServiceConstructor },
   { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kToolkitContracts[] = {
   { NS_APPSTARTUP_CONTRACTID, &kNS_TOOLKIT_APPSTARTUP_CID },
   { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID },
   { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID },
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
@@ -157,16 +161,17 @@ static const mozilla::Module::ContractID
   { NS_URLCLASSIFIERSTREAMUPDATER_CONTRACTID, &kNS_URLCLASSIFIERSTREAMUPDATER_CID },
   { NS_URLCLASSIFIERUTILS_CONTRACTID, &kNS_URLCLASSIFIERUTILS_CID },
 #endif
   { NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID },
   { NS_RDF_DATASOURCE_CONTRACTID_PREFIX NS_CHARSETMENU_PID, &kNS_CHARSETMENU_CID },
 #if defined(USE_MOZ_UPDATER)
   { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
 #endif
+  { FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
   { nullptr }
 };
 
 static const mozilla::Module kToolkitModule = {
   mozilla::Module::kVersion,
   kToolkitCIDs,
   kToolkitContracts
 };
new file mode 100644
--- /dev/null
+++ b/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp
@@ -0,0 +1,213 @@
+/* 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<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(JSFreeOp *fop, JSObject *objSelf)
+{
+  nsRefPtr<FinalizationEvent> 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<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, JS::CallArgs args)
+{
+  if (args.length() != 0) {
+    JS_ReportError(cx, "forget() takes no arguments");
+    return false;
+  }
+  JS::Rooted<JS::Value> valSelf(cx, args.thisv());
+  JS::Rooted<JSObject*> objSelf(cx, &valSelf.toObject());
+
+  nsRefPtr<FinalizationEvent> 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<IsWitness, ForgetImpl>(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 PRUnichar* aValue,
+                                 JSContext* aCx,
+                                 JS::Value *aRetval) {
+  MOZ_ASSERT(aRetval);
+
+  JS::Rooted<JSObject*> 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<FinalizationEvent> 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
new file mode 100644
--- /dev/null
+++ b/toolkit/components/finalizationwitness/FinalizationWitnessService.h
@@ -0,0 +1,26 @@
+/* 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"
+
+namespace mozilla {
+
+/**
+ * XPConnect initializer, for use in the main thread.
+ */
+class FinalizationWitnessService MOZ_FINAL : public nsIFinalizationWitnessService
+{
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIFINALIZATIONWITNESSSERVICE
+ private:
+  void operator=(const FinalizationWitnessService* other) MOZ_DELETE;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_finalizationwitnessservice_h__
new file mode 100644
--- /dev/null
+++ b/toolkit/components/finalizationwitness/moz.build
@@ -0,0 +1,28 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+MODULE = 'finalizationwitness'
+
+CPP_SOURCES += [
+  'FinalizationWitnessService.cpp',
+]
+
+XPIDL_SOURCES += [
+  'nsIFinalizationWitnessService.idl',
+]
+
+XPIDL_MODULE = 'toolkit_finalizationwitness'
+
+EXPORTS.mozilla += [
+    'FinalizationWitnessService.h',
+]
+
+LOCAL_INCLUDES += [
+    '/js/xpconnect/loader',
+]
+
+LIBRARY_NAME = 'finalizationwitness_s'
+LIBXUL_LIBRARY = True
new file mode 100644
--- /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"
+
+%}
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -15,16 +15,17 @@ PARALLEL_DIRS += [
     'commandlines',
     'console',
     'contentprefs',
     'cookie',
     'diskspacewatcher',
     'downloads',
     'exthelper',
     'filepicker',
+    'finalizationwitness',
     'find',
     'intl',
     'jsdownloads',
     'mediasniffer',
     'microformats',
     'osfile',
     'parentalcontrols',
     'passwordmgr',