Bug 1126089 - Allow messages to be sent after frame script unload event (r=smaug)
☠☠ backed out by 76aab45b3300 ☠ ☠
authorBill McCloskey <bill.mccloskey@gmail.com>
Thu, 26 Feb 2015 21:35:26 -0800
changeset 233565 f63a2cf3fa11b5ee08b84e5d24dcfab51f56c90c
parent 233564 7d9a91ee3d484f651b31e3a2a4724e33836890aa
child 233566 3fb5364095bfeaedb7c3689aa6c3b9b80e7045a4
push id28417
push userryanvm@gmail.com
push dateFri, 13 Mar 2015 19:52:44 +0000
treeherdermozilla-central@977add19414a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1126089
milestone39.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 1126089 - Allow messages to be sent after frame script unload event (r=smaug)
b2g/components/Frames.jsm
browser/components/uitour/UITour.jsm
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsFrameLoader.cpp
dom/base/nsFrameLoader.h
dom/base/nsFrameMessageManager.cpp
dom/base/nsFrameMessageManager.h
dom/base/nsIDocument.h
dom/base/nsIMessageManager.idl
dom/base/nsInProcessTabChildGlobal.cpp
dom/base/nsInProcessTabChildGlobal.h
dom/base/test/browser.ini
dom/base/test/browser_messagemanager_unload.js
dom/base/test/file_messagemanager_unload.html
dom/ipc/TabChild.cpp
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
testing/mochitest/browser-test.js
toolkit/devtools/server/main.js
--- a/b2g/components/Frames.jsm
+++ b/b2g/components/Frames.jsm
@@ -20,32 +20,32 @@ const Observer = {
   _frames: new Map(),
 
   // Also save current number of iframes opened by app
   _apps: new Map(),
 
   start: function () {
     Services.obs.addObserver(this, 'remote-browser-shown', false);
     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
-    Services.obs.addObserver(this, 'message-manager-disconnect', false);
+    Services.obs.addObserver(this, 'message-manager-close', false);
 
     SystemAppProxy.getFrames().forEach(frame => {
       let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
       this._frames.set(mm, frame);
       let mozapp = frame.getAttribute('mozapp');
       if (mozapp) {
         this._apps.set(mozapp, (this._apps.get(mozapp) || 0) + 1);
       }
     });
   },
 
   stop: function () {
     Services.obs.removeObserver(this, 'remote-browser-shown');
     Services.obs.removeObserver(this, 'inprocess-browser-shown');
-    Services.obs.removeObserver(this, 'message-manager-disconnect');
+    Services.obs.removeObserver(this, 'message-manager-close');
     this._frames.clear();
     this._apps.clear();
   },
 
   observe: function (subject, topic, data) {
     switch(topic) {
 
       // Listen for frame creation in OOP (device) as well as in parent process (b2g desktop)
@@ -56,17 +56,17 @@ const Observer = {
         // get a ref to the app <iframe>
         frameLoader.QueryInterface(Ci.nsIFrameLoader);
         let frame = frameLoader.ownerElement;
         let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
         this.onMessageManagerCreated(mm, frame);
         break;
 
       // Every time an iframe is destroyed, its message manager also is
-      case 'message-manager-disconnect':
+      case 'message-manager-close':
         this.onMessageManagerDestroyed(subject);
         break;
     }
   },
 
   onMessageManagerCreated: function (mm, frame) {
     this._frames.set(mm, frame);
 
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -679,17 +679,17 @@ this.UITour = {
       }
     }
 
     if (!this.tourBrowsersByWindow.has(window)) {
       this.tourBrowsersByWindow.set(window, new Set());
     }
     this.tourBrowsersByWindow.get(window).add(browser);
 
-    Services.obs.addObserver(this, "message-manager-disconnect", false);
+    Services.obs.addObserver(this, "message-manager-close", false);
 
     window.addEventListener("SSWindowClosing", this);
 
     return true;
   },
 
   handleEvent: function(aEvent) {
     log.debug("handleEvent: type =", aEvent.type, "event =", aEvent);
@@ -731,17 +731,17 @@ this.UITour = {
     }
   },
 
   observe: function(aSubject, aTopic, aData) {
     log.debug("observe: aTopic =", aTopic);
     switch (aTopic) {
       // The browser message manager is disconnected when the <browser> is
       // destroyed and we want to teardown at that point.
-      case "message-manager-disconnect": {
+      case "message-manager-close": {
         let winEnum = Services.wm.getEnumerator("navigator:browser");
         while (winEnum.hasMoreElements()) {
           let window = winEnum.getNext();
           if (window.closed)
             continue;
 
           let tourBrowsers = this.tourBrowsersByWindow.get(window);
           if (!tourBrowsers)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -7329,24 +7329,24 @@ nsDocument::InitializeFrameLoader(nsFram
       NS_NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders);
     NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
     nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
   }
   return NS_OK;
 }
 
 nsresult
-nsDocument::FinalizeFrameLoader(nsFrameLoader* aLoader)
+nsDocument::FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer)
 {
   mInitializableFrameLoaders.RemoveElement(aLoader);
   if (mInDestructor) {
     return NS_ERROR_FAILURE;
   }
 
-  mFinalizableFrameLoaders.AppendElement(aLoader);
+  mFrameLoaderFinalizers.AppendElement(aFinalizer);
   if (!mFrameLoaderRunner) {
     mFrameLoaderRunner =
       NS_NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders);
     NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
     nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
   }
   return NS_OK;
 }
@@ -7361,17 +7361,17 @@ nsDocument::MaybeInitializeFinalizeFrame
     return;
   }
 
   // We're not in an update, but it is not safe to run scripts, so
   // postpone frameloader initialization and finalization.
   if (!nsContentUtils::IsSafeToRunScript()) {
     if (!mInDestructor && !mFrameLoaderRunner &&
         (mInitializableFrameLoaders.Length() ||
-         mFinalizableFrameLoaders.Length())) {
+         mFrameLoaderFinalizers.Length())) {
       mFrameLoaderRunner =
         NS_NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders);
       nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
     }
     return;
   }
   mFrameLoaderRunner = nullptr;
 
@@ -7380,52 +7380,38 @@ nsDocument::MaybeInitializeFinalizeFrame
   // array. But be careful to keep the loader alive when starting the load!
   while (mInitializableFrameLoaders.Length()) {
     nsRefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0];
     mInitializableFrameLoaders.RemoveElementAt(0);
     NS_ASSERTION(loader, "null frameloader in the array?");
     loader->ReallyStartLoading();
   }
 
-  uint32_t length = mFinalizableFrameLoaders.Length();
+  uint32_t length = mFrameLoaderFinalizers.Length();
   if (length > 0) {
-    nsTArray<nsRefPtr<nsFrameLoader> > loaders;
-    mFinalizableFrameLoaders.SwapElements(loaders);
+    nsTArray<nsCOMPtr<nsIRunnable> > finalizers;
+    mFrameLoaderFinalizers.SwapElements(finalizers);
     for (uint32_t i = 0; i < length; ++i) {
-      loaders[i]->Finalize();
+      finalizers[i]->Run();
     }
   }
 }
 
 void
 nsDocument::TryCancelFrameLoaderInitialization(nsIDocShell* aShell)
 {
   uint32_t length = mInitializableFrameLoaders.Length();
   for (uint32_t i = 0; i < length; ++i) {
     if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) {
       mInitializableFrameLoaders.RemoveElementAt(i);
       return;
     }
   }
 }
 
-bool
-nsDocument::FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell)
-{
-  if (aShell) {
-    uint32_t length = mFinalizableFrameLoaders.Length();
-    for (uint32_t i = 0; i < length; ++i) {
-      if (mFinalizableFrameLoaders[i]->GetExistingDocShell() == aShell) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
 nsIDocument*
 nsDocument::RequestExternalResource(nsIURI* aURI,
                                     nsINode* aRequestingNode,
                                     ExternalResourceLoad** aPendingLoad)
 {
   NS_PRECONDITION(aURI, "Must have a URI");
   NS_PRECONDITION(aRequestingNode, "Must have a node");
   if (mDisplayDocument) {
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1028,19 +1028,18 @@ public:
                                                    float aBottomSize, float aLeftSize,
                                                    bool aIgnoreRootScrollFrame,
                                                    bool aFlushLayout,
                                                    nsIDOMNodeList** aReturn) MOZ_OVERRIDE;
 
   virtual void FlushSkinBindings() MOZ_OVERRIDE;
 
   virtual nsresult InitializeFrameLoader(nsFrameLoader* aLoader) MOZ_OVERRIDE;
-  virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader) MOZ_OVERRIDE;
+  virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer) MOZ_OVERRIDE;
   virtual void TryCancelFrameLoaderInitialization(nsIDocShell* aShell) MOZ_OVERRIDE;
-  virtual bool FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell) MOZ_OVERRIDE;
   virtual nsIDocument*
     RequestExternalResource(nsIURI* aURI,
                             nsINode* aRequestingNode,
                             ExternalResourceLoad** aPendingLoad) MOZ_OVERRIDE;
   virtual void
     EnumerateExternalResources(nsSubDocEnumFunc aCallback, void* aData) MOZ_OVERRIDE;
 
   nsTArray<nsCString> mHostObjectURIs;
@@ -1763,17 +1762,17 @@ private:
   // AddStyleRelevantLink.
   bool mStyledLinksCleared;
 #endif
 
   // Member to store out last-selected stylesheet set.
   nsString mLastStyleSheetSet;
 
   nsTArray<nsRefPtr<nsFrameLoader> > mInitializableFrameLoaders;
-  nsTArray<nsRefPtr<nsFrameLoader> > mFinalizableFrameLoaders;
+  nsTArray<nsCOMPtr<nsIRunnable> > mFrameLoaderFinalizers;
   nsRefPtr<nsRunnableMethod<nsDocument> > mFrameLoaderRunner;
 
   nsRevocableEventPtr<nsRunnableMethod<nsDocument, void, false> >
     mPendingTitleChangeEvent;
 
   nsExternalResourceMap mExternalResourceMap;
 
   // All images in process of being preloaded.  This is a hashtable so
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -102,35 +102,16 @@
 using namespace mozilla;
 using namespace mozilla::hal;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 typedef FrameMetrics::ViewID ViewID;
 
-class nsAsyncDocShellDestroyer : public nsRunnable
-{
-public:
-  explicit nsAsyncDocShellDestroyer(nsIDocShell* aDocShell)
-    : mDocShell(aDocShell)
-  {
-  }
-
-  NS_IMETHOD Run()
-  {
-    nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell));
-    if (base_win) {
-      base_win->Destroy();
-    }
-    return NS_OK;
-  }
-  nsRefPtr<nsIDocShell> mDocShell;
-};
-
 // Bug 136580: Limit to the number of nested content frames that can have the
 //             same URL. This is to stop content that is recursively loading
 //             itself.  Note that "#foo" on the end of URL doesn't affect
 //             whether it's considered identical, but "?foo" or ";foo" are
 //             considered and compared.
 // Bug 228829: Limit this to 1, like IE does.
 #define MAX_SAME_URL_CONTENT_FRAMES 1
 
@@ -176,17 +157,16 @@ nsFrameLoader::nsFrameLoader(Element* aO
   , mChildID(0)
   , mEventMode(EVENT_MODE_NORMAL_DISPATCH)
 {
   ResetPermissionManagerStatus();
 }
 
 nsFrameLoader::~nsFrameLoader()
 {
-  mNeedsAsyncDestroy = true;
   if (mMessageManager) {
     mMessageManager->Disconnect();
   }
   MOZ_RELEASE_ASSERT(mDestroyCalled);
 }
 
 nsFrameLoader*
 nsFrameLoader::Create(Element* aOwner, bool aNetworkCreated)
@@ -518,26 +498,16 @@ nsFrameLoader::GetDocShell(nsIDocShell *
   }
 
   *aDocShell = mDocShell;
   NS_IF_ADDREF(*aDocShell);
 
   return rv;
 }
 
-void
-nsFrameLoader::Finalize()
-{
-  nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell));
-  if (base_win) {
-    base_win->Destroy();
-  }
-  mDocShell = nullptr;
-}
-
 static void
 FirePageHideEvent(nsIDocShellTreeItem* aItem,
                   EventTarget* aChromeEventHandler)
 {
   nsCOMPtr<nsIDocument> doc = aItem->GetDocument();
   NS_ASSERTION(doc, "What happened here?");
   doc->OnPageHide(true, aChromeEventHandler);
 
@@ -1353,51 +1323,80 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
 
   FirePageShowEvent(ourDocshell, ourEventTarget, true);
   FirePageShowEvent(otherDocshell, otherEventTarget, true);
 
   mInSwap = aOther->mInSwap = false;
   return NS_OK;
 }
 
-void
-nsFrameLoader::DestroyChild()
-{
-  if (mRemoteBrowser) {
-    mRemoteBrowser->SetOwnerElement(nullptr);
-    mRemoteBrowser->Destroy();
-    mRemoteBrowser = nullptr;
-  }
-}
-
 NS_IMETHODIMP
 nsFrameLoader::Destroy()
 {
+  StartDestroy();
+  return NS_OK;
+}
+
+class nsFrameLoaderDestroyRunnable : public nsRunnable
+{
+  enum DestroyPhase
+  {
+    // See the implementation of Run for an explanation of these phases.
+    eDestroyDocShell,
+    eWaitForUnloadMessage,
+    eDestroyComplete
+  };
+
+  nsRefPtr<nsFrameLoader> mFrameLoader;
+  DestroyPhase mPhase;
+
+public:
+  explicit nsFrameLoaderDestroyRunnable(nsFrameLoader* aFrameLoader)
+   : mFrameLoader(aFrameLoader), mPhase(eDestroyDocShell) {}
+
+  NS_IMETHODIMP Run() MOZ_OVERRIDE;
+};
+
+void
+nsFrameLoader::StartDestroy()
+{
+  // nsFrameLoader::StartDestroy is called just before the frameloader is
+  // detached from the <browser> element. Destruction continues in phases via
+  // the nsFrameLoaderDestroyRunnable.
+
   if (mDestroyCalled) {
-    return NS_OK;
+    return;
   }
   mDestroyCalled = true;
 
+  // After this point, we return an error when trying to send a message using
+  // the message manager on the frame.
   if (mMessageManager) {
-    mMessageManager->Disconnect();
+    mMessageManager->Close();
   }
-  if (mChildMessageManager) {
-    static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->Disconnect();
+
+  // Retain references to the <browser> element and the frameloader in case we
+  // receive any messages from the message manager on the frame. These
+  // references are dropped in DestroyComplete.
+  if (mChildMessageManager || mRemoteBrowser) {
+    mOwnerContentStrong = mOwnerContent;
+    if (mRemoteBrowser) {
+      mRemoteBrowser->CacheFrameLoader(this);
+    }
   }
 
   nsCOMPtr<nsIDocument> doc;
   bool dynamicSubframeRemoval = false;
   if (mOwnerContent) {
     doc = mOwnerContent->OwnerDoc();
     dynamicSubframeRemoval = !mIsTopLevelContent && !doc->InUnlinkOrDeletion();
     doc->SetSubDocumentFor(mOwnerContent, nullptr);
 
     SetOwnerContent(nullptr);
   }
-  DestroyChild();
 
   // Seems like this is a dynamic frame removal.
   if (dynamicSubframeRemoval) {
     if (mDocShell) {
       mDocShell->RemoveFromSessionHistory();
     }
   }
 
@@ -1407,42 +1406,141 @@ nsFrameLoader::Destroy()
       nsCOMPtr<nsIDocShellTreeItem> parentItem;
       mDocShell->GetParent(getter_AddRefs(parentItem));
       nsCOMPtr<nsIDocShellTreeOwner> owner = do_GetInterface(parentItem);
       if (owner) {
         owner->ContentShellRemoved(mDocShell);
       }
     }
   }
-  
+
   // Let our window know that we are gone
   if (mDocShell) {
     nsCOMPtr<nsPIDOMWindow> win_private(mDocShell->GetWindow());
     if (win_private) {
       win_private->SetFrameElementInternal(nullptr);
     }
   }
 
-  if ((mNeedsAsyncDestroy || !doc ||
-       NS_FAILED(doc->FinalizeFrameLoader(this))) && mDocShell) {
-    nsCOMPtr<nsIRunnable> event = new nsAsyncDocShellDestroyer(mDocShell);
-    NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
-    NS_DispatchToCurrentThread(event);
-
-    // Let go of our docshell now that the async destroyer holds on to
-    // the docshell.
-
-    mDocShell = nullptr;
+  nsCOMPtr<nsIRunnable> destroyRunnable = new nsFrameLoaderDestroyRunnable(this);
+  if (mNeedsAsyncDestroy || !doc ||
+      NS_FAILED(doc->FinalizeFrameLoader(this, destroyRunnable))) {
+    NS_DispatchToCurrentThread(destroyRunnable);
   }
-
-  // NOTE: 'this' may very well be gone by now.
+}
+
+nsresult
+nsFrameLoaderDestroyRunnable::Run()
+{
+  switch (mPhase) {
+  case eDestroyDocShell:
+    mFrameLoader->DestroyDocShell();
+
+    // In the out-of-process case, TabParent will eventually call
+    // DestroyComplete once it receives a __delete__ message from the child. In
+    // the in-process case, we dispatch a series of runnables to ensure that
+    // DestroyComplete gets called at the right time. The frame loader is kept
+    // alive by mFrameLoader during this time.
+    if (mFrameLoader->mChildMessageManager) {
+      // When the docshell is destroyed, NotifyWindowIDDestroyed is called to
+      // asynchronously notify {outer,inner}-window-destroyed via a runnable. We
+      // don't want DestroyComplete to run until after those runnables have
+      // run. Since we're enqueueing ourselves after the window-destroyed
+      // runnables are enqueued, we're guaranteed to run after.
+      mPhase = eWaitForUnloadMessage;
+      NS_DispatchToCurrentThread(this);
+    }
+    break;
+
+   case eWaitForUnloadMessage:
+     // The *-window-destroyed observers have finished running at this
+     // point. However, it's possible that a *-window-destroyed observer might
+     // have sent a message using the message manager. These messages might not
+     // have been processed yet. So we enqueue ourselves again to ensure that
+     // DestroyComplete runs after all messages sent by *-window-destroyed
+     // observers have been processed.
+     mPhase = eDestroyComplete;
+     NS_DispatchToCurrentThread(this);
+     break;
+
+   case eDestroyComplete:
+     // Now that all messages sent by unload listeners and window destroyed
+     // observers have been processed, we disconnect the message manager and
+     // finish destruction.
+     mFrameLoader->DestroyComplete();
+     break;
+  }
 
   return NS_OK;
 }
 
+void
+nsFrameLoader::DestroyDocShell()
+{
+  // This code runs after the frameloader has been detached from the <browser>
+  // element. We postpone this work because we may not be allowed to run
+  // script at that time.
+
+  // Ask the TabChild to fire the frame script "unload" event, destroy its
+  // docshell, and finally destroy the PBrowser actor. This eventually leads to
+  // nsFrameLoader::DestroyComplete being called.
+  if (mRemoteBrowser) {
+    mRemoteBrowser->Destroy();
+  }
+
+  // Fire the "unload" event if we're in-process.
+  if (mChildMessageManager) {
+    static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->FireUnloadEvent();
+  }
+
+  // Destroy the docshell.
+  nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell));
+  if (base_win) {
+    base_win->Destroy();
+  }
+  mDocShell = nullptr;
+
+  if (mChildMessageManager) {
+    // Stop handling events in the in-process frame script.
+    static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->DisconnectEventListeners();
+  }
+}
+
+void
+nsFrameLoader::DestroyComplete()
+{
+  // We get here, as part of StartDestroy, after the docshell has been destroyed
+  // and all message manager messages sent during docshell destruction have been
+  // dispatched.  We also get here if the child process crashes. In the latter
+  // case, StartDestroy might not have been called.
+
+  // Drop the strong references created in StartDestroy.
+  if (mChildMessageManager || mRemoteBrowser) {
+    mOwnerContentStrong = nullptr;
+    if (mRemoteBrowser) {
+      mRemoteBrowser->CacheFrameLoader(nullptr);
+    }
+  }
+
+  // Call TabParent::Destroy if we haven't already (in case of a crash).
+  if (mRemoteBrowser) {
+    mRemoteBrowser->SetOwnerElement(nullptr);
+    mRemoteBrowser->Destroy();
+    mRemoteBrowser = nullptr;
+  }
+
+  if (mMessageManager) {
+    mMessageManager->Disconnect();
+  }
+
+  if (mChildMessageManager) {
+    static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->Disconnect();
+  }
+}
+
 NS_IMETHODIMP
 nsFrameLoader::GetDepthTooGreat(bool* aDepthTooGreat)
 {
   *aDepthTooGreat = mDepthTooGreat;
   return NS_OK;
 }
 
 void
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -73,17 +73,19 @@ public:
                                bool aNetworkCreated);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFrameLoader, nsIFrameLoader)
   NS_DECL_NSIFRAMELOADER
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
   nsresult CheckForRecursiveLoad(nsIURI* aURI);
   nsresult ReallyStartLoading();
-  void Finalize();
+  void StartDestroy();
+  void DestroyDocShell();
+  void DestroyComplete();
   nsIDocShell* GetExistingDocShell() { return mDocShell; }
   mozilla::dom::EventTarget* GetTabChildGlobalAsEventTarget();
   nsresult CreateStaticClone(nsIFrameLoader* aDest);
 
   /**
    * MessageManagerCallback methods that we override.
    */
   virtual bool DoLoadMessageManagerScript(const nsAString& aURL,
@@ -314,16 +316,21 @@ private:
   void ResetPermissionManagerStatus();
 
   void InitializeBrowserAPI();
 
   nsCOMPtr<nsIDocShell> mDocShell;
   nsCOMPtr<nsIURI> mURIToLoad;
   mozilla::dom::Element* mOwnerContent; // WEAK
 
+  // After the frameloader has been removed from the DOM but before all of the
+  // messages from the frame have been received, we keep a strong reference to
+  // our <browser> element.
+  nsRefPtr<mozilla::dom::Element> mOwnerContentStrong;
+
   // Note: this variable must be modified only by ResetPermissionManagerStatus()
   uint32_t mAppIdSentToPermissionManager;
 
 public:
   // public because a callback needs these.
   nsRefPtr<nsFrameMessageManager> mMessageManager;
   nsCOMPtr<nsIInProcessContentFrameMessageManager> mChildMessageManager;
 private:
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -296,17 +296,18 @@ SameProcessCpowHolder::ToObject(JSContex
   aObjp.set(mObj);
   return JS_WrapObject(aCx, aObjp);
 }
 
 // nsIMessageListenerManager
 
 NS_IMETHODIMP
 nsFrameMessageManager::AddMessageListener(const nsAString& aMessage,
-                                          nsIMessageListener* aListener)
+                                          nsIMessageListener* aListener,
+                                          bool aListenWhenClosed)
 {
   nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
     mListeners.Get(aMessage);
   if (!listeners) {
     listeners = new nsAutoTObserverArray<nsMessageListenerInfo, 1>();
     mListeners.Put(aMessage, listeners);
   } else {
     uint32_t len = listeners->Length();
@@ -315,16 +316,17 @@ nsFrameMessageManager::AddMessageListene
         return NS_OK;
       }
     }
   }
 
   nsMessageListenerInfo* entry = listeners->AppendElement();
   NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
   entry->mStrongListener = aListener;
+  entry->mListenWhenClosed = aListenWhenClosed;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage,
                                              nsIMessageListener* aListener)
 {
   nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
@@ -402,16 +404,17 @@ nsFrameMessageManager::AddWeakMessageLis
       if (listeners->ElementAt(i).mWeakListener == weak) {
         return NS_OK;
       }
     }
   }
 
   nsMessageListenerInfo* entry = listeners->AppendElement();
   entry->mWeakListener = weak;
+  entry->mListenWhenClosed = false;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessage,
                                                  nsIMessageListener* aListener)
 {
   nsWeakPtr weak = do_GetWeakReference(aListener);
@@ -979,16 +982,30 @@ nsresult
 nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
                                       const nsAString& aMessage,
                                       bool aIsSync,
                                       const StructuredCloneData* aCloneData,
                                       mozilla::jsipc::CpowHolder* aCpows,
                                       nsIPrincipal* aPrincipal,
                                       InfallibleTArray<nsString>* aJSONRetVal)
 {
+  return ReceiveMessage(aTarget, mClosed, aMessage, aIsSync,
+                        aCloneData, aCpows, aPrincipal, aJSONRetVal);
+}
+
+nsresult
+nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
+                                      bool aTargetClosed,
+                                      const nsAString& aMessage,
+                                      bool aIsSync,
+                                      const StructuredCloneData* aCloneData,
+                                      mozilla::jsipc::CpowHolder* aCpows,
+                                      nsIPrincipal* aPrincipal,
+                                      InfallibleTArray<nsString>* aJSONRetVal)
+{
   nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
     mListeners.Get(aMessage);
   if (listeners) {
 
     MMListenerRemover lr(this);
 
     nsAutoTObserverArray<nsMessageListenerInfo, 1>::EndLimitedIterator
       iter(*listeners);
@@ -999,16 +1016,20 @@ nsFrameMessageManager::ReceiveMessage(ns
       if (listener.mWeakListener) {
         weakListener = do_QueryReferent(listener.mWeakListener);
         if (!weakListener) {
           listeners->RemoveElement(listener);
           continue;
         }
       }
 
+      if (!listener.mListenWhenClosed && aTargetClosed) {
+        continue;
+      }
+
       nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS;
       if (weakListener) {
         wrappedJS = do_QueryInterface(weakListener);
       } else {
         wrappedJS = do_QueryInterface(listener.mStrongListener);
       }
 
       if (!wrappedJS) {
@@ -1149,17 +1170,17 @@ nsFrameMessageManager::ReceiveMessage(ns
             continue;
           }
           aJSONRetVal->AppendElement(json);
         }
       }
     }
   }
   nsRefPtr<nsFrameMessageManager> kungfuDeathGrip = mParentManager;
-  return mParentManager ? mParentManager->ReceiveMessage(aTarget, aMessage,
+  return mParentManager ? mParentManager->ReceiveMessage(aTarget, aTargetClosed, aMessage,
                                                          aIsSync, aCloneData,
                                                          aCpows, aPrincipal,
                                                          aJSONRetVal) : NS_OK;
 }
 
 void
 nsFrameMessageManager::AddChildManager(nsFrameMessageManager* aManager)
 {
@@ -1230,32 +1251,48 @@ nsFrameMessageManager::RemoveFromParent(
     mParentManager->RemoveChildManager(this);
   }
   mParentManager = nullptr;
   mCallback = nullptr;
   mOwnedCallback = nullptr;
 }
 
 void
+nsFrameMessageManager::Close()
+{
+  if (!mClosed) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this),
+                            "message-manager-close", nullptr);
+    }
+  }
+  mClosed = true;
+  mCallback = nullptr;
+  mOwnedCallback = nullptr;
+}
+
+void
 nsFrameMessageManager::Disconnect(bool aRemoveFromParent)
 {
+  // Notify message-manager-close if we haven't already.
+  Close();
+
   if (!mDisconnected) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
        obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this),
                             "message-manager-disconnect", nullptr);
     }
   }
   if (mParentManager && aRemoveFromParent) {
     mParentManager->RemoveChildManager(this);
   }
   mDisconnected = true;
   mParentManager = nullptr;
-  mCallback = nullptr;
-  mOwnedCallback = nullptr;
   if (!mHandlingMessage) {
     mListeners.Clear();
   }
 }
 
 namespace {
 
 struct MessageManagerReferentCount
--- a/dom/base/nsFrameMessageManager.h
+++ b/dom/base/nsFrameMessageManager.h
@@ -128,16 +128,17 @@ struct nsMessageListenerInfo
   bool operator==(const nsMessageListenerInfo& aOther) const
   {
     return &aOther == this;
   }
 
   // Exactly one of mStrongListener and mWeakListener must be non-null.
   nsCOMPtr<nsIMessageListener> mStrongListener;
   nsWeakPtr mWeakListener;
+  bool mListenWhenClosed;
 };
 
 
 class MOZ_STACK_CLASS SameProcessCpowHolder : public mozilla::jsipc::CpowHolder
 {
 public:
   SameProcessCpowHolder(JSRuntime *aRuntime, JS::Handle<JSObject*> aObj)
     : mObj(aRuntime, aObj)
@@ -164,16 +165,17 @@ public:
                         nsFrameMessageManager* aParentManager,
                         /* mozilla::dom::ipc::MessageManagerFlags */ uint32_t aFlags)
   : mChrome(!!(aFlags & mozilla::dom::ipc::MM_CHROME)),
     mGlobal(!!(aFlags & mozilla::dom::ipc::MM_GLOBAL)),
     mIsProcessManager(!!(aFlags & mozilla::dom::ipc::MM_PROCESSMANAGER)),
     mIsBroadcaster(!!(aFlags & mozilla::dom::ipc::MM_BROADCASTER)),
     mOwnsCallback(!!(aFlags & mozilla::dom::ipc::MM_OWNSCALLBACK)),
     mHandlingMessage(false),
+    mClosed(false),
     mDisconnected(false),
     mCallback(aCallback),
     mParentManager(aParentManager)
   {
     NS_ASSERTION(mChrome || !aParentManager, "Should not set parent manager!");
     NS_ASSERTION(!mIsBroadcaster || !mCallback,
                  "Broadcasters cannot have callbacks!");
     // This is a bit hackish. When parent manager is global, we want
@@ -233,16 +235,17 @@ public:
                           InfallibleTArray<nsString>* aJSONRetVal);
 
   void AddChildManager(nsFrameMessageManager* aManager);
   void RemoveChildManager(nsFrameMessageManager* aManager)
   {
     mChildManagers.RemoveObject(aManager);
   }
   void Disconnect(bool aRemoveFromParent = true);
+  void Close();
 
   void InitWithCallback(mozilla::dom::ipc::MessageManagerCallback* aCallback);
   void SetCallback(mozilla::dom::ipc::MessageManagerCallback* aCallback);
   mozilla::dom::ipc::MessageManagerCallback* GetCallback()
   {
     return mCallback;
   }
 
@@ -285,16 +288,21 @@ private:
                        JS::Handle<JS::Value> aJSON,
                        JS::Handle<JS::Value> aObjects,
                        nsIPrincipal* aPrincipal,
                        JSContext* aCx,
                        uint8_t aArgc,
                        JS::MutableHandle<JS::Value> aRetval,
                        bool aIsSync);
 
+  nsresult ReceiveMessage(nsISupports* aTarget, bool aTargetClosed, const nsAString& aMessage,
+                          bool aIsSync, const StructuredCloneData* aCloneData,
+                          mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal,
+                          InfallibleTArray<nsString>* aJSONRetVal);
+
   NS_IMETHOD LoadScript(const nsAString& aURL,
                         bool aAllowDelayedLoad,
                         bool aRunInGlobalScope);
   NS_IMETHOD RemoveDelayedScript(const nsAString& aURL);
   NS_IMETHOD GetDelayedScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList);
 
 protected:
   friend class MMListenerRemover;
@@ -304,16 +312,17 @@ protected:
                    nsAutoTObserverArray<nsMessageListenerInfo, 1>> mListeners;
   nsCOMArray<nsIContentFrameMessageManager> mChildManagers;
   bool mChrome;     // true if we're in the chrome process
   bool mGlobal;     // true if we're the global frame message manager
   bool mIsProcessManager; // true if the message manager belongs to the process realm
   bool mIsBroadcaster; // true if the message manager is a broadcaster
   bool mOwnsCallback;
   bool mHandlingMessage;
+  bool mClosed;    // true if we can no longer send messages
   bool mDisconnected;
   mozilla::dom::ipc::MessageManagerCallback* mCallback;
   nsAutoPtr<mozilla::dom::ipc::MessageManagerCallback> mOwnedCallback;
   nsFrameMessageManager* mParentManager;
   nsTArray<nsString> mPendingScripts;
   nsTArray<bool> mPendingScriptsGlobalStates;
 
   void LoadPendingScripts(nsFrameMessageManager* aManager,
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -56,16 +56,17 @@ class nsIDOMNodeList;
 class nsIHTMLCollection;
 class nsILayoutHistoryState;
 class nsILoadContext;
 class nsIObjectLoadingContent;
 class nsIObserver;
 class nsIPresShell;
 class nsIPrincipal;
 class nsIRequest;
+class nsIRunnable;
 class nsIStreamListener;
 class nsIStructuredCloneContainer;
 class nsIStyleRule;
 class nsIStyleSheet;
 class nsIURI;
 class nsIVariant;
 class nsLocation;
 class nsViewManager;
@@ -1647,21 +1648,19 @@ public:
   already_AddRefed<nsIDocumentEncoder> GetCachedEncoder();
 
   void SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder);
 
   // In case of failure, the document really can't initialize the frame loader.
   virtual nsresult InitializeFrameLoader(nsFrameLoader* aLoader) = 0;
   // In case of failure, the caller must handle the error, for example by
   // finalizing frame loader asynchronously.
-  virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader) = 0;
+  virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer) = 0;
   // Removes the frame loader of aShell from the initialization list.
   virtual void TryCancelFrameLoaderInitialization(nsIDocShell* aShell) = 0;
-  //  Returns true if the frame loader of aShell is in the finalization list.
-  virtual bool FrameLoaderScheduledToBeFinalized(nsIDocShell* aShell) = 0;
 
   /**
    * Check whether this document is a root document that is not an
    * external resource.
    */
   bool IsRootDisplayDocument() const
   {
     return !mParentDocument && !mDisplayDocument;
--- a/dom/base/nsIMessageManager.idl
+++ b/dom/base/nsIMessageManager.idl
@@ -196,31 +196,38 @@ interface nsIMessageListener : nsISuppor
    * returned as JSON (will be changed to use structured clones).
    * When there are multiple listeners to sync messages, each
    * listener's return value is sent back as an array.  |undefined|
    * return values show up as undefined values in the array.
    */
   void receiveMessage();
 };
 
-[scriptable, builtinclass, uuid(aae827bd-acf1-45fe-a556-ea545d4c0804)]
+[scriptable, builtinclass, uuid(b949bfec-bb7d-47bc-b387-ac6a9b655072)]
 interface nsIMessageListenerManager : nsISupports
 {
   /**
    * Register |listener| to receive |messageName|.  All listener
    * callbacks for a particular message are invoked when that message
    * is received.
    *
    * The message manager holds a strong ref to |listener|.
    *
    * If the same listener registers twice for the same message, the
    * second registration is ignored.
+   *
+   * Pass true for listenWhenClosed if you want to receive messages
+   * during the short period after a frame has been removed from the
+   * DOM and before its frame script has finished unloading. This
+   * parameter only has an effect for frame message managers in
+   * the main process. Default is false.
    */
   void addMessageListener(in AString messageName,
-                          in nsIMessageListener listener);
+                          in nsIMessageListener listener,
+                          [optional] in boolean listenWhenClosed);
 
   /**
    * Undo an |addMessageListener| call -- that is, calling this causes us to no
    * longer invoke |listener| when |messageName| is received.
    *
    * removeMessageListener does not remove a message listener added via
    * addWeakMessageListener; use removeWeakMessageListener for that.
    */
@@ -247,17 +254,17 @@ interface nsIMessageListenerManager : ns
 };
 
 /**
  * Message "senders" have a single "other side" to which messages are
  * sent.  For example, a child-process message manager will send
  * messages that are only delivered to its one parent-process message
  * manager.
  */
-[scriptable, builtinclass, uuid(d6b0d851-43e6-426d-9f13-054bc0198175)]
+[scriptable, builtinclass, uuid(bb5d79e4-e73c-45e7-9651-4d718f4b994c)]
 interface nsIMessageSender : nsIMessageListenerManager
 {
   /**
    * Send |messageName| and |obj| to the "other side" of this message
    * manager.  This invokes listeners who registered for
    * |messageName|.
    *
    * See nsIMessageListener::receiveMessage() for the format of the
@@ -279,17 +286,17 @@ interface nsIMessageSender : nsIMessageL
 
 /**
  * Message "broadcasters" don't have a single "other side" that they
  * send messages to, but rather a set of subordinate message managers.
  * For example, broadcasting a message through a window message
  * manager will broadcast the message to all frame message managers
  * within its window.
  */
-[scriptable, builtinclass, uuid(d36346b9-5d3b-497d-9c28-ffbc3e4f6d0d)]
+[scriptable, builtinclass, uuid(4d7d62ad-4725-4f39-86cf-8fb22bf9c1d8)]
 interface nsIMessageBroadcaster : nsIMessageListenerManager
 {
   /**
    * Like |sendAsyncMessage()|, but also broadcasts this message to
    * all "child" message managers of this message manager.  See long
    * comment above for details.
    *
    * WARNING: broadcasting messages can be very expensive and leak
@@ -306,17 +313,17 @@ interface nsIMessageBroadcaster : nsIMes
   readonly attribute unsigned long childCount;
 
   /**
    * Return a single subordinate message manager.
    */
   nsIMessageListenerManager getChildAt(in unsigned long aIndex);
 };
 
-[scriptable, builtinclass, uuid(7fda0941-9dcc-448b-bd39-16373c5b4003)]
+[scriptable, builtinclass, uuid(0e602c9e-1977-422a-a8e4-fe0d4a4f78d0)]
 interface nsISyncMessageSender : nsIMessageSender
 {
   /**
    * Like |sendAsyncMessage()|, except blocks the sender until all
    * listeners of the message have been invoked.  Returns an array
    * containing return values from each listener invoked.
    */
   [implicit_jscontext, optional_argc]
@@ -336,17 +343,17 @@ interface nsISyncMessageSender : nsIMess
    */
   [implicit_jscontext, optional_argc]
   jsval sendRpcMessage([optional] in AString messageName,
                        [optional] in jsval obj,
                        [optional] in jsval objects,
                        [optional] in nsIPrincipal principal);
 };
 
-[scriptable, builtinclass, uuid(e04a7ade-c61a-46ec-9f13-efeabedd9d3d)]
+[scriptable, builtinclass, uuid(13f3555f-769e-44ea-b607-5239230c3162)]
 interface nsIMessageManagerGlobal : nsISyncMessageSender
 {
   /**
    * Print a string to stdout.
    */
   void dump(in DOMString aStr);
 
   /**
@@ -371,23 +378,23 @@ interface nsIContentFrameMessageManager 
   readonly attribute nsIDOMWindow content;
 
   /**
    * The top level docshell or null.
    */
   readonly attribute nsIDocShell docShell;
 };
 
-[uuid(a2325927-9c0c-437d-9215-749c79235031)]
+[uuid(a9e07e89-7125-48e3-bf73-2cbae7fc5b1c)]
 interface nsIInProcessContentFrameMessageManager : nsIContentFrameMessageManager
 {
   [notxpcom] nsIContent getOwnerContent();
 };
 
-[scriptable, builtinclass, uuid(9ca95410-b253-11e4-ab27-0800200c9a66)]
+[scriptable, builtinclass, uuid(d0c799a2-d5ff-4a75-acbb-b8c8347944a6)]
 interface nsIContentProcessMessageManager : nsIMessageManagerGlobal
 {
 };
 
 [scriptable, builtinclass, uuid(6fb78110-45ae-11e3-8f96-0800200c9a66)]
 interface nsIFrameScriptLoader : nsISupports
 {
   /**
--- a/dom/base/nsInProcessTabChildGlobal.cpp
+++ b/dom/base/nsInProcessTabChildGlobal.cpp
@@ -96,16 +96,17 @@ nsInProcessTabChildGlobal::DoSendAsyncMe
   NS_DispatchToCurrentThread(ev);
   return true;
 }
 
 nsInProcessTabChildGlobal::nsInProcessTabChildGlobal(nsIDocShell* aShell,
                                                      nsIContent* aOwner,
                                                      nsFrameMessageManager* aChrome)
 : mDocShell(aShell), mInitialized(false), mLoadingScript(false),
+  mPreventEventsEscaping(false),
   mOwner(aOwner), mChromeMessageManager(aChrome)
 {
   SetIsNotDOMBinding();
   mozilla::HoldJSObjects(this);
 
   // If owner corresponds to an <iframe mozbrowser> or <iframe mozapp>, we'll
   // have to tweak our PreHandleEvent implementation.
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwner);
@@ -202,89 +203,100 @@ nsInProcessTabChildGlobal::GetContent(ns
 NS_IMETHODIMP
 nsInProcessTabChildGlobal::GetDocShell(nsIDocShell** aDocShell)
 {
   NS_IF_ADDREF(*aDocShell = mDocShell);
   return NS_OK;
 }
 
 void
-nsInProcessTabChildGlobal::Disconnect()
+nsInProcessTabChildGlobal::FireUnloadEvent()
 {
-  // Let the frame scripts know the child is being closed. We do any other
-  // cleanup after the event has been fired. See DelayedDisconnect
-  nsContentUtils::AddScriptRunner(
-     NS_NewRunnableMethod(this, &nsInProcessTabChildGlobal::DelayedDisconnect)
-  );
+  // We're called from nsDocument::MaybeInitializeFinalizeFrameLoaders, so it
+  // should be safe to run script.
+  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+  // Don't let the unload event propagate to chrome event handlers.
+  mPreventEventsEscaping = true;
+  DOMEventTargetHelper::DispatchTrustedEvent(NS_LITERAL_STRING("unload"));
+
+  // Allow events fired during docshell destruction (pagehide, unload) to
+  // propagate to the <browser> element since chrome code depends on this.
+  mPreventEventsEscaping = false;
 }
 
 void
-nsInProcessTabChildGlobal::DelayedDisconnect()
+nsInProcessTabChildGlobal::DisconnectEventListeners()
 {
-  // Don't let the event escape
-  mOwner = nullptr;
-
-  // Fire the "unload" event
-  DOMEventTargetHelper::DispatchTrustedEvent(NS_LITERAL_STRING("unload"));
-
-  // Continue with the Disconnect cleanup
   if (mDocShell) {
     nsCOMPtr<nsPIDOMWindow> win = mDocShell->GetWindow();
     if (win) {
       MOZ_ASSERT(win->IsOuterWindow());
       win->SetChromeEventHandler(win->GetChromeEventHandler());
     }
   }
+  if (mListenerManager) {
+    mListenerManager->Disconnect();
+  }
+
   mDocShell = nullptr;
+}
+
+void
+nsInProcessTabChildGlobal::Disconnect()
+{
   mChromeMessageManager = nullptr;
+  mOwner = nullptr;
   if (mMessageManager) {
     static_cast<nsFrameMessageManager*>(mMessageManager.get())->Disconnect();
     mMessageManager = nullptr;
   }
-  if (mListenerManager) {
-    mListenerManager->Disconnect();
-  }
 }
 
 NS_IMETHODIMP_(nsIContent *)
 nsInProcessTabChildGlobal::GetOwnerContent()
 {
   return mOwner;
 }
 
 nsresult
 nsInProcessTabChildGlobal::PreHandleEvent(EventChainPreVisitor& aVisitor)
 {
   aVisitor.mCanHandle = true;
 
-  if (mIsBrowserOrAppFrame &&
-      (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) {
-    if (mOwner) {
-      nsPIDOMWindow* innerWindow = mOwner->OwnerDoc()->GetInnerWindow();
-      if (innerWindow) {
-        aVisitor.mParentTarget = innerWindow->GetParentTarget();
-      }
-    }
-  } else {
-    aVisitor.mParentTarget = mOwner;
-  }
-
 #ifdef DEBUG
   if (mOwner) {
     nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(mOwner);
     nsRefPtr<nsFrameLoader> fl = owner->GetFrameLoader();
     if (fl) {
       NS_ASSERTION(this == fl->GetTabChildGlobalAsEventTarget(),
                    "Wrong event target!");
       NS_ASSERTION(fl->mMessageManager == mChromeMessageManager,
                    "Wrong message manager!");
     }
   }
 #endif
 
+  if (mPreventEventsEscaping) {
+    aVisitor.mParentTarget = nullptr;
+    return NS_OK;
+  }
+
+  if (mIsBrowserOrAppFrame &&
+      (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) {
+    if (mOwner) {
+      nsPIDOMWindow* innerWindow = mOwner->OwnerDoc()->GetInnerWindow();
+      if (innerWindow) {
+        aVisitor.mParentTarget = innerWindow->GetParentTarget();
+      }
+    }
+  } else {
+    aVisitor.mParentTarget = mOwner;
+  }
+
   return NS_OK;
 }
 
 nsresult
 nsInProcessTabChildGlobal::InitTabChildGlobal()
 {
   // If you change this, please change GetCompartmentName() in XPCJSRuntime.cpp
   // accordingly.
--- a/dom/base/nsInProcessTabChildGlobal.h
+++ b/dom/base/nsInProcessTabChildGlobal.h
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*- */
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8; -*- */
 /* vim: set sw=4 ts=8 et tw=80 : */
 /* 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 nsInProcessTabChildGlobal_h
 #define nsInProcessTabChildGlobal_h
 
@@ -113,16 +113,18 @@ public:
                                                            aWantsUntrusted,
                                                            optional_argc);
   }
   using mozilla::DOMEventTargetHelper::AddEventListener;
 
   virtual JSContext* GetJSContextForEventHandlers() MOZ_OVERRIDE { return nsContentUtils::GetSafeJSContext(); }
   virtual nsIPrincipal* GetPrincipal() MOZ_OVERRIDE { return mPrincipal; }
   void LoadFrameScript(const nsAString& aURL, bool aRunInGlobalScope);
+  void FireUnloadEvent();
+  void DisconnectEventListeners();
   void Disconnect();
   void SendMessageToParent(const nsString& aMessage, bool aSync,
                            const nsString& aJSON,
                            nsTArray<nsString>* aJSONRetVal);
   nsFrameMessageManager* GetInnerManager()
   {
     return static_cast<nsFrameMessageManager*>(mMessageManager.get());
   }
@@ -132,18 +134,16 @@ public:
   {
     return mChromeMessageManager;
   }
   void SetChromeMessageManager(nsFrameMessageManager* aParent)
   {
     mChromeMessageManager = aParent;
   }
 
-  void DelayedDisconnect();
-
   virtual JSObject* GetGlobalJSObject() MOZ_OVERRIDE {
     if (!mGlobal) {
       return nullptr;
     }
 
     return mGlobal->GetJSObject();
   }
   virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE
@@ -159,15 +159,16 @@ protected:
   nsCOMPtr<nsIDocShell> mDocShell;
   bool mInitialized;
   bool mLoadingScript;
 
   // Is this the message manager for an in-process <iframe mozbrowser> or
   // <iframe mozapp>?  This affects where events get sent, so it affects
   // PreHandleEvent.
   bool mIsBrowserOrAppFrame;
+  bool mPreventEventsEscaping;
 public:
   nsIContent* mOwner;
   nsFrameMessageManager* mChromeMessageManager;
   nsTArray<nsCOMPtr<nsIRunnable> > mASyncMessages;
 };
 
 #endif
--- a/dom/base/test/browser.ini
+++ b/dom/base/test/browser.ini
@@ -1,14 +1,17 @@
 [DEFAULT]
+support-files =
+  file_messagemanager_unload.html
 
 [browser_bug593387.js]
 skip-if = e10s # Bug ?????? - test directly touches content (contentWindow.iframe.addEventListener)
 [browser_bug902350.js]
 skip-if = e10s # Bug ?????? - test e10s utils don't support load events from iframe etc, which this test relies on.
 [browser_messagemanager_loadprocessscript.js]
 [browser_pagehide_on_tab_close.js]
 skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s.
+[browser_messagemanager_unload.js]
 [browser_state_notifications.js]
 # skip-if = e10s # Bug ?????? - content-document-* notifications come while document's URI is still about:blank, but test expects real URL.
 skip-if = true # Intermittent failures - bug 987493. Restore the skip-if above once fixed
 [browser_bug1058164.js]
 skip-if = e10s # We need bug 918634 to land before this can be tested with e10s.
new file mode 100644
--- /dev/null
+++ b/dom/base/test/browser_messagemanager_unload.js
@@ -0,0 +1,98 @@
+function frameScript()
+{
+  Components.utils.import("resource://gre/modules/Services.jsm");
+
+  function eventHandler(e) {
+    if (!docShell) {
+      sendAsyncMessage("Test:Fail", "docShell is null");
+    }
+
+    sendAsyncMessage("Test:Event", [e.type, e.target === content.document, e.eventPhase]);
+  }
+
+  let outerID = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+        getInterface(Components.interfaces.nsIDOMWindowUtils).outerWindowID;
+  function onOuterWindowDestroyed(subject, topic, data) {
+    if (docShell) {
+      sendAsyncMessage("Test:Fail", "docShell is non-null");
+    }
+
+    let id = subject.QueryInterface(Components.interfaces.nsISupportsPRUint64).data;
+    sendAsyncMessage("Test:Event", ["outer-window-destroyed", id == outerID]);
+    if (id == outerID) {
+      Services.obs.removeObserver(onOuterWindowDestroyed, "outer-window-destroyed");
+    }
+  }
+
+  let url = "https://example.com/browser/dom/base/test/file_messagemanager_unload.html";
+
+  content.location = url;
+  addEventListener("load", (e) => {
+    if (e.target.location != url) {
+      return;
+    }
+
+    addEventListener("unload", eventHandler, false);
+    addEventListener("unload", eventHandler, true);
+    addEventListener("pagehide", eventHandler, false);
+    addEventListener("pagehide", eventHandler, true);
+    Services.obs.addObserver(onOuterWindowDestroyed, "outer-window-destroyed", false);
+
+    sendAsyncMessage("Test:Ready");
+  }, true);
+}
+
+const EXPECTED = [
+  // Unload events on the TabChildGlobal. These come first so that the
+  // docshell is available.
+  ["unload", false, 2],
+  ["unload", false, 2],
+
+  // pagehide and unload events for the top-level page.
+  ["pagehide", true, 1],
+  ["pagehide", true, 3],
+  ["unload", true, 1],
+
+  // pagehide and unload events for the iframe.
+  ["pagehide", false, 1],
+  ["pagehide", false, 3],
+  ["unload", false, 1],
+
+  // outer-window-destroyed for both pages.
+  ["outer-window-destroyed", false],
+  ["outer-window-destroyed", true],
+];
+
+function test() {
+  waitForExplicitFinish();
+
+  var newTab = gBrowser.addTab("about:blank");
+  gBrowser.selectedTab = newTab;
+
+  let browser = newTab.linkedBrowser;
+
+  browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")()", false);
+
+  browser.messageManager.addMessageListener("Test:Fail", (msg) => {
+    ok(false, msg.data);
+  }, true);
+
+  let index = 0;
+  browser.messageManager.addMessageListener("Test:Event", (msg) => {
+    ok(msg.target === browser, "<browser> is correct");
+
+    info(JSON.stringify(msg.data));
+
+    is(JSON.stringify(msg.data), JSON.stringify(EXPECTED[index]), "results match");
+    index++;
+
+    if (index == EXPECTED.length) {
+      finish();
+    }
+  }, true);
+
+  browser.messageManager.addMessageListener("Test:Ready", () => {
+    info("Got ready message");
+    gBrowser.removeCurrentTab();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_messagemanager_unload.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <iframe id="frame" src="about:robots"></iframe>
+  </body>
+</html>
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2645,53 +2645,27 @@ TabChild::RecvAppOfflineStatus(const uin
   nsCOMPtr<nsIIOService> ioService = mozilla::services::GetIOService();
   if (gIOService && ioService) {
     gIOService->SetAppOfflineInternal(aId, aOffline ?
       nsIAppOfflineInfo::OFFLINE : nsIAppOfflineInfo::ONLINE);
   }
   return true;
 }
 
-class UnloadScriptEvent : public nsRunnable
-{
-public:
-  UnloadScriptEvent(TabChild* aTabChild, TabChildGlobal* aTabChildGlobal)
-    : mTabChild(aTabChild), mTabChildGlobal(aTabChildGlobal)
-  { }
-
-  NS_IMETHOD Run()
-  {
-    nsCOMPtr<nsIDOMEvent> event;
-    NS_NewDOMEvent(getter_AddRefs(event), mTabChildGlobal, nullptr, nullptr);
-    if (event) {
-      event->InitEvent(NS_LITERAL_STRING("unload"), false, false);
-      event->SetTrusted(true);
-
-      bool dummy;
-      mTabChildGlobal->DispatchEvent(event, &dummy);
-    }
-
-    return NS_OK;
-  }
-
-  nsRefPtr<TabChild> mTabChild;
-  TabChildGlobal* mTabChildGlobal;
-};
-
 bool
 TabChild::RecvDestroy()
 {
   MOZ_ASSERT(mDestroyed == false);
   mDestroyed = true;
 
   if (mTabChildGlobal) {
-    // Let the frame scripts know the child is being closed
-    nsContentUtils::AddScriptRunner(
-      new UnloadScriptEvent(this, mTabChildGlobal)
-    );
+    // Message handlers are called from the event loop, so it better be safe to
+    // run script.
+    MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+    mTabChildGlobal->DispatchTrustedEvent(NS_LITERAL_STRING("unload"));
   }
 
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
 
   observerService->RemoveObserver(this, BROWSER_ZOOM_TO_RECT);
   observerService->RemoveObserver(this, BEFORE_FIRST_PAINT);
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -314,16 +314,22 @@ TabParent::RemoveTabParentFromTable(uint
   sLayerToTabParentTable->Remove(aLayersId);
   if (sLayerToTabParentTable->Count() == 0) {
     delete sLayerToTabParentTable;
     sLayerToTabParentTable = nullptr;
   }
 }
 
 void
+TabParent::CacheFrameLoader(nsFrameLoader* aFrameLoader)
+{
+  mFrameLoader = aFrameLoader;
+}
+
+void
 TabParent::SetOwnerElement(Element* aElement)
 {
   mFrameElement = aElement;
   TryCacheDPIAndScale();
 }
 
 void
 TabParent::GetAppType(nsAString& aOut)
@@ -402,41 +408,38 @@ void
 TabParent::ActorDestroy(ActorDestroyReason why)
 {
   if (sEventCapturer == this) {
     sEventCapturer = nullptr;
   }
   if (mIMETabParent == this) {
     mIMETabParent = nullptr;
   }
-  nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true);
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-  nsRefPtr<nsFrameMessageManager> fmm;
   if (frameLoader) {
-    fmm = frameLoader->GetFrameMessageManager();
     nsCOMPtr<Element> frameElement(mFrameElement);
     ReceiveMessage(CHILD_PROCESS_SHUTDOWN_MESSAGE, false, nullptr, nullptr,
                    nullptr);
-    frameLoader->DestroyChild();
+    frameLoader->DestroyComplete();
 
     if (why == AbnormalShutdown && os) {
       os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, frameLoader),
                           "oop-frameloader-crashed", nullptr);
       nsContentUtils::DispatchTrustedEvent(frameElement->OwnerDoc(), frameElement,
                                            NS_LITERAL_STRING("oop-browser-crashed"),
                                            true, true);
     }
+
+    mFrameLoader = nullptr;
   }
 
   if (os) {
     os->NotifyObservers(NS_ISUPPORTS_CAST(nsITabParent*, this), "ipc:browser-destroyed", nullptr);
   }
-  if (fmm) {
-    fmm->Disconnect();
-  }
 }
 
 bool
 TabParent::RecvMoveFocus(const bool& aForward)
 {
   nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
   if (fm) {
     nsCOMPtr<nsIDOMElement> dummy;
@@ -2280,17 +2283,17 @@ TabParent::RecvGetWidgetNativeData(Windo
 bool
 TabParent::ReceiveMessage(const nsString& aMessage,
                           bool aSync,
                           const StructuredCloneData* aCloneData,
                           CpowHolder* aCpows,
                           nsIPrincipal* aPrincipal,
                           InfallibleTArray<nsString>* aJSONRetVal)
 {
-  nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true);
   if (frameLoader && frameLoader->GetFrameMessageManager()) {
     nsRefPtr<nsFrameMessageManager> manager =
       frameLoader->GetFrameMessageManager();
 
     manager->ReceiveMessage(mFrameElement,
                             aMessage,
                             aSync,
                             aCloneData,
@@ -2398,18 +2401,26 @@ TabParent::AllowContentIME()
   nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent();
   if (focusedContent && focusedContent->IsEditable())
     return false;
 
   return true;
 }
 
 already_AddRefed<nsFrameLoader>
-TabParent::GetFrameLoader() const
+TabParent::GetFrameLoader(bool aUseCachedFrameLoaderAfterDestroy) const
 {
+  if (mIsDestroyed && !aUseCachedFrameLoaderAfterDestroy) {
+    return nullptr;
+  }
+
+  if (mFrameLoader) {
+    nsRefPtr<nsFrameLoader> fl = mFrameLoader;
+    return fl.forget();
+  }
   nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(mFrameElement);
   return frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nullptr;
 }
 
 void
 TabParent::TryCacheDPIAndScale()
 {
   if (mDPI > 0) {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -75,16 +75,18 @@ public:
 
     TabParent(nsIContentParent* aManager,
               const TabId& aTabId,
               const TabContext& aContext,
               uint32_t aChromeFlags);
     Element* GetOwnerElement() const { return mFrameElement; }
     void SetOwnerElement(Element* aElement);
 
+    void CacheFrameLoader(nsFrameLoader* aFrameLoader);
+
     /**
      * Get the mozapptype attribute from this TabParent's owner DOM element.
      */
     void GetAppType(nsAString& aOut);
 
     /**
      * Returns true iff this TabParent's nsIFrameLoader is visible.
      *
@@ -447,17 +449,17 @@ protected:
     ScreenIntSize mDimensions;
     ScreenOrientation mOrientation;
     float mDPI;
     CSSToLayoutDeviceScale mDefaultScale;
     bool mShown;
     bool mUpdatedDimensions;
 
 private:
-    already_AddRefed<nsFrameLoader> GetFrameLoader() const;
+    already_AddRefed<nsFrameLoader> GetFrameLoader(bool aUseCachedFrameLoaderAfterDestroy = false) const;
     layout::RenderFrameParent* GetRenderFrame();
     nsRefPtr<nsIContentParent> mManager;
     void TryCacheDPIAndScale();
 
     CSSPoint AdjustTapToChildWidget(const CSSPoint& aPoint);
 
     // Update state prior to routing an APZ-aware event to the child process.
     // |aOutTargetGuid| will contain the identifier
@@ -489,16 +491,21 @@ private:
     uint32_t mChromeFlags;
 
     // When true, the TabParent is initialized without child side's request.
     // When false, the TabParent is initialized by window.open() from child side.
     bool mInitedByParent;
 
     nsCOMPtr<nsILoadContext> mLoadContext;
 
+    // We keep a strong reference to the frameloader after we've sent the
+    // Destroy message and before we've received __delete__. This allows us to
+    // dispatch message manager messages during this time.
+    nsRefPtr<nsFrameLoader> mFrameLoader;
+
     TabId mTabId;
 
     // Helper class for RecvCreateWindow.
     struct AutoUseNewTab;
 
     // When loading a new tab or window via window.open, the child process sends
     // a new PBrowser to use. We store that tab in sNextTabParent and then
     // proceed through the browser's normal paths to create a new
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -43,16 +43,52 @@ function b2gStart() {
   let homescreen = document.getElementById('systemapp');
   var webNav = homescreen.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                    .getInterface(Ci.nsIWebNavigation);
   var url = "chrome://mochikit/content/harness.xul?manifestFile=tests.json";
 
   webNav.loadURI(url, null, null, null, null);
 }
 
+let TabDestroyObserver = {
+  outstanding: new Set(),
+  promiseResolver: null,
+
+  init: function() {
+    Services.obs.addObserver(this, "message-manager-close", false);
+    Services.obs.addObserver(this, "message-manager-disconnect", false);
+  },
+
+  destroy: function() {
+    Services.obs.removeObserver(this, "message-manager-close");
+    Services.obs.removeObserver(this, "message-manager-disconnect");
+  },
+
+  observe: function(subject, topic, data) {
+    if (topic == "message-manager-close") {
+      this.outstanding.add(subject);
+    } else if (topic == "message-manager-disconnect") {
+      this.outstanding.delete(subject);
+      if (!this.outstanding.size && this.promiseResolver) {
+        this.promiseResolver();
+      }
+    }
+  },
+
+  wait: function() {
+    if (!this.outstanding.size) {
+      return Promise.resolve();
+    }
+
+    return new Promise((resolve) => {
+      this.promiseResolver = resolve;
+    });
+  },
+};
+
 function testInit() {
   gConfig = readConfig();
   if (gConfig.testRoot == "browser" ||
       gConfig.testRoot == "metro" ||
       gConfig.testRoot == "webapprtChrome") {
     // Make sure to launch the test harness for the first opened window only
     var prefs = Services.prefs;
     if (prefs.prefHasUserValue("testing.browserTestHarness.running"))
@@ -180,16 +216,18 @@ Tester.prototype = {
   get currentTest() {
     return this.tests[this.currentTestIndex];
   },
   get done() {
     return this.currentTestIndex == this.tests.length - 1;
   },
 
   start: function Tester_start() {
+    TabDestroyObserver.init();
+
     //if testOnLoad was not called, then gConfig is not defined
     if (!gConfig)
       gConfig = readConfig();
 
     if (gConfig.runUntilFailure)
       this.runUntilFailure = true;
 
     if (gConfig.repeat)
@@ -269,16 +307,18 @@ Tester.prototype = {
       }
     }
 
     // Make sure the window is raised before each test.
     this.SimpleTest.waitForFocus(aCallback);
   },
 
   finish: function Tester_finish(aSkipSummary) {
+    TabDestroyObserver.destroy();
+
     this.Promise.Debugging.flushUncaughtErrors();
 
     var passCount = this.tests.reduce(function(a, f) a + f.passCount, 0);
     var failCount = this.tests.reduce(function(a, f) a + f.failCount, 0);
     var todoCount = this.tests.reduce(function(a, f) a + f.todoCount, 0);
 
     if (this.repeat > 0) {
       --this.repeat;
@@ -578,16 +618,19 @@ Tester.prototype = {
         let {AsyncShutdown} =
           Cu.import("resource://gre/modules/AsyncShutdown.jsm", {});
 
         let barrier = new AsyncShutdown.Barrier(
           "ShutdownLeaks: Wait for cleanup to be finished before checking for leaks");
         Services.obs.notifyObservers({wrappedJSObject: barrier},
           "shutdown-leaks-before-check", null);
 
+        barrier.client.addBlocker("ShutdownLeaks: Wait for tabs to finish closing",
+                                  TabDestroyObserver.wait());
+
         barrier.wait().then(() => {
           // Simulate memory pressure so that we're forced to free more resources
           // and thus get rid of more false leaks like already terminated workers.
           Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize");
 
           let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
                        .getService(Ci.nsIMessageBroadcaster);
           ppmm.broadcastAsyncMessage("browser-test:collect-request");
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -708,43 +708,43 @@ var DebuggerServer = {
 
       deferred.resolve(actor);
     });
 
     aMm.sendAsyncMessage("DevTools:InitDebuggerServer", {
       prefix: prefix
     });
 
-    function onDisconnect() {
-      Services.obs.removeObserver(onMessageManagerDisconnect, "message-manager-disconnect");
-      events.off(aConnection, "closed", onDisconnect);
+    function onClose() {
+      Services.obs.removeObserver(onMessageManagerClose, "message-manager-close");
+      events.off(aConnection, "closed", onClose);
       if (childTransport) {
         // If we have a child transport, the actor has already
         // been created. We need to stop using this message manager.
         childTransport.close();
         childTransport = null;
         aConnection.cancelForwarding(prefix);
 
         // ... and notify the child process to clean the tab actors.
         try {
           aMm.sendAsyncMessage("debug:content-process-destroy");
         } catch(e) {}
       }
     }
 
-    let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
+    let onMessageManagerClose = DevToolsUtils.makeInfallible(function (subject, topic, data) {
       if (subject == aMm) {
-        onDisconnect();
+        onClose();
         aConnection.send({ from: actor.actor, type: "tabDetached" });
       }
     }).bind(this);
-    Services.obs.addObserver(onMessageManagerDisconnect,
-                             "message-manager-disconnect", false);
+    Services.obs.addObserver(onMessageManagerClose,
+                             "message-manager-close", false);
 
-    events.on(aConnection, "closed", onDisconnect);
+    events.on(aConnection, "closed", onClose);
 
     return deferred.promise;
   },
 
   /**
    * Check if the caller is running in a content child process.
    *
    * @return boolean
@@ -877,19 +877,19 @@ var DebuggerServer = {
       netMonitor = new NetworkMonitorManager(aFrame, actor.actor);
 
       events.emit(DebuggerServer, "new-child-process", { mm: mm });
 
       deferred.resolve(actor);
     }).bind(this);
     mm.addMessageListener("debug:actor", onActorCreated);
 
-    let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
+    let onMessageManagerClose = DevToolsUtils.makeInfallible(function (subject, topic, data) {
       if (subject == mm) {
-        Services.obs.removeObserver(onMessageManagerDisconnect, topic);
+        Services.obs.removeObserver(onMessageManagerClose, topic);
 
         // provides hook to actor modules that need to exchange messages
         // between e10s parent and child processes
         this.emit("disconnected-from-child:" + childID, { mm: mm, childID: childID });
 
         mm.removeMessageListener("debug:setup-in-parent", onSetupInParent);
 
         if (childTransport) {
@@ -920,18 +920,18 @@ var DebuggerServer = {
           netMonitor = null;
         }
 
         if (aOnDisconnect) {
           aOnDisconnect(mm);
         }
       }
     }).bind(this);
-    Services.obs.addObserver(onMessageManagerDisconnect,
-                             "message-manager-disconnect", false);
+    Services.obs.addObserver(onMessageManagerClose,
+                             "message-manager-close", false);
 
     events.once(aConnection, "closed", () => {
       if (childTransport) {
         // When the client disconnects, we have to unplug the dedicated
         // ChildDebuggerTransport...
         childTransport.close();
         childTransport = null;
         aConnection.cancelForwarding(prefix);