Bug 966762 - Add chrome JS function to unlink ghost windows. r=smaug
authorAndrew McCreight <continuation@gmail.com>
Wed, 19 Feb 2014 13:27:15 -0800
changeset 169958 1853f837d121d60460c6051317494805aa468922
parent 169957 fc5ea4b0b1147ddf5e011dc920fee261be8aa39f
child 169959 f3b21728d32f76cef47f5420b9c63781ed76acff
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerssmaug
bugs966762
milestone30.0a1
Bug 966762 - Add chrome JS function to unlink ghost windows. r=smaug
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsWindowMemoryReporter.cpp
dom/base/nsWindowMemoryReporter.h
js/xpconnect/idl/xpccomponents.idl
js/xpconnect/src/XPCComponents.cpp
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1804,16 +1804,24 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mToolbar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocationbar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPersonalbar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusbar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
+#ifdef DEBUG
+void
+nsGlobalWindow::RiskyUnlink()
+{
+  NS_CYCLE_COLLECTION_INNERNAME.Unlink(this);
+}
+#endif
+
 struct TraceData
 {
   const TraceCallbacks& callbacks;
   void* closure;
 };
 
 static PLDHashOperator
 TraceXBLHandlers(nsXBLPrototypeHandler* aKey, JS::Heap<JSObject*>& aData, void* aClosure)
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -599,16 +599,22 @@ public:
   static void RunPendingTimeoutsRecursive(nsGlobalWindow *aTopWindow,
                                           nsGlobalWindow *aWindow);
 
   friend class WindowStateHolder;
 
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsGlobalWindow,
                                                                    nsIDOMEventTarget)
 
+#ifdef DEBUG
+  // Call Unlink on this window. This may cause bad things to happen, so use
+  // with caution.
+  void RiskyUnlink();
+#endif
+
   virtual NS_HIDDEN_(JSObject*)
     GetCachedXBLPrototypeHandler(nsXBLPrototypeHandler* aKey);
 
   virtual NS_HIDDEN_(void)
     CacheXBLPrototypeHandler(nsXBLPrototypeHandler* aKey,
                              JS::Handle<JSObject*> aHandler);
 
   virtual bool TakeFocus(bool aFocus, uint32_t aFocusMethod);
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -831,8 +831,50 @@ NS_IMPL_ISUPPORTS1(nsWindowMemoryReporte
 /* static */ int64_t
 nsWindowMemoryReporter::GhostWindowsReporter::DistinguishedAmount()
 {
   nsTHashtable<nsUint64HashKey> ghostWindows;
   sWindowReporter->CheckForGhostWindows(&ghostWindows);
   return ghostWindows.Count();
 }
 
+#ifdef DEBUG
+static PLDHashOperator
+UnlinkGhostWindowsEnumerator(nsUint64HashKey* aIDHashKey, void *)
+{
+  nsGlobalWindow::WindowByIdTable* windowsById =
+    nsGlobalWindow::GetWindowsTable();
+  if (!windowsById) {
+    return PL_DHASH_NEXT;
+  }
+
+  nsRefPtr<nsGlobalWindow> window = windowsById->Get(aIDHashKey->GetKey());
+  if (window) {
+    window->RiskyUnlink();
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+/* static */ void
+nsWindowMemoryReporter::UnlinkGhostWindows()
+{
+  if (!sWindowReporter) {
+    return;
+  }
+
+  nsGlobalWindow::WindowByIdTable* windowsById =
+    nsGlobalWindow::GetWindowsTable();
+  if (!windowsById) {
+    return;
+  }
+
+  // Hold on to every window in memory so that window objects can't be
+  // destroyed while we're calling the UnlinkGhostWindows callback.
+  WindowArray windows;
+  windowsById->Enumerate(GetWindows, &windows);
+
+  // Get the IDs of all the "ghost" windows, and unlink them all.
+  nsTHashtable<nsUint64HashKey> ghostWindows;
+  sWindowReporter->CheckForGhostWindows(&ghostWindows);
+  ghostWindows.EnumerateEntries(UnlinkGhostWindowsEnumerator, nullptr);
+}
+#endif
--- a/dom/base/nsWindowMemoryReporter.h
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -141,16 +141,24 @@ class nsWindowMemoryReporter MOZ_FINAL :
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTER
   NS_DECL_NSIOBSERVER
 
   static void Init();
 
+#ifdef DEBUG
+  /**
+   * Unlink all known ghost windows, to enable investigating what caused them
+   * to become ghost windows in the first place.
+   */
+  static void UnlinkGhostWindows();
+#endif
+
 private:
   /**
    * nsGhostWindowReporter generates the "ghost-windows" report, which counts
    * the number of ghost windows present.
    */
   class GhostWindowsReporter MOZ_FINAL : public nsIMemoryReporter
   {
   public:
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -116,17 +116,17 @@ interface nsIXPCComponents_utils_Sandbox
 interface ScheduledGCCallback : nsISupports
 {
     void callback();
 };
 
 /**
 * interface of Components.utils
 */
-[scriptable, uuid(f4b12240-02aa-47e5-99d5-43cd50fbd4b7)]
+[scriptable, uuid(0b81b4f2-cad7-4602-a5cb-b99c9d514196)]
 interface nsIXPCComponents_Utils : nsISupports
 {
 
     /* reportError is designed to be called from JavaScript only.
      *
      * It will report a JS Error object to the JS console, and return. It
      * is meant for use in exception handler blocks which want to "eat"
      * an exception, but still want to report it to the console.
@@ -274,16 +274,22 @@ interface nsIXPCComponents_Utils : nsISu
     void schedulePreciseGC(in ScheduledGCCallback callback);
 
     /*
      * Schedule a shrinking garbage collection cycle for a point in the future
      * when no JS is running. Call the provided function once this has occured.
      */
     void schedulePreciseShrinkingGC(in ScheduledGCCallback callback);
 
+    /*
+     * In a debug build, unlink any ghost windows. This is only for debugging
+     * leaks, and can cause bad things to happen if called.
+     */
+    void unlinkGhostWindows();
+
     /**
      * Return the keys in a weak map.  This operation is
      * non-deterministic because it is affected by the scheduling of the
      * garbage collector and the cycle collector.
      *
      * This should only be used to write tests of the interaction of
      * the GC and CC with weak maps.
      *
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2852,16 +2852,28 @@ nsXPCComponents_Utils::SchedulePreciseGC
 /* void schedulePreciseShrinkingGC(in ScheduledGCCallback callback); */
 NS_IMETHODIMP
 nsXPCComponents_Utils::SchedulePreciseShrinkingGC(ScheduledGCCallback* aCallback)
 {
     nsRefPtr<PreciseGCRunnable> event = new PreciseGCRunnable(aCallback, true);
     return NS_DispatchToMainThread(event);
 }
 
+/* void unlinkGhostWindows(); */
+NS_IMETHODIMP
+nsXPCComponents_Utils::UnlinkGhostWindows()
+{
+#ifdef DEBUG
+    nsWindowMemoryReporter::UnlinkGhostWindows();
+    return NS_OK;
+#else
+    return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
 /* [implicit_jscontext] jsval nondeterministicGetWeakMapKeys(in jsval aMap); */
 NS_IMETHODIMP
 nsXPCComponents_Utils::NondeterministicGetWeakMapKeys(HandleValue aMap,
                                                       JSContext *aCx,
                                                       MutableHandleValue aKeys)
 {
     if (!aMap.isObject()) {
         aKeys.setUndefined();