Bug 777508 - Notify parent if child processes are terminated through message manager. r=cjones
authorPhilipp von Weitershausen <philipp@weitershausen.de>
Thu, 27 Sep 2012 22:43:24 -0700
changeset 108521 965397b043c04a1fd46385102c3ebd3a7c7d0ff9
parent 108520 182c763efa680d9a9c9e8fccac5cd1b6a2def752
child 108522 52be204da1cfa0c3188d744ba21a5563affc8e34
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewerscjones
bugs777508
milestone18.0a1
Bug 777508 - Notify parent if child processes are terminated through message manager. r=cjones
content/base/test/Makefile.in
content/base/test/test_child_process_shutdown_message.html
content/base/test/test_messagemanager_assertpermission.html
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/TabParent.cpp
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -572,16 +572,17 @@ MOCHITEST_FILES_B = \
 		$(NULL)
 
 # OOP tests don't work on Windows (bug 763081) or native-fennec
 # (see Bug 774939)
 ifneq ($(OS_ARCH),WINNT)
 ifndef MOZ_JAVA_COMPOSITOR
 MOCHITEST_FILES_B += \
 		test_messagemanager_assertpermission.html \
+		test_child_process_shutdown_message.html \
 		$(NULL)
 endif
 endif
 
 MOCHITEST_CHROME_FILES =	\
 		test_bug357450.js \
 		$(NULL)
 
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_child_process_shutdown_message.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test that processes that are shutdown send a 'process-shutdown'
+         message to their process message manager.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runTests();">
+<p id="display">
+</p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = SpecialPowers.wrap(Components);
+
+const APP_URL = "http://example.org";
+const APP_MANIFEST = "http://example.org/manifest.webapp";
+const CHILD_PROCESS_SHUTDOWN_MESSAGE = "child-process-shutdown";
+
+let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+             .getService(Ci.nsIMessageBroadcaster);
+
+/**
+ * Load the example.org app in an <iframe mozbrowser mozapp>
+ */
+function loadApp(callback) {
+  let iframe = document.createElement("iframe");
+  iframe.setAttribute("mozapp", APP_MANIFEST);
+  iframe.mozbrowser = true;
+  iframe.src = APP_URL;
+  document.getElementById("content").appendChild(iframe);
+
+  iframe.addEventListener("mozbrowserloadend", function onloadend() {
+    iframe.removeEventListener("mozbrowserloadend", onloadend);
+    callback(iframe);
+  });
+}
+
+/**
+ * Prepare the child process for an intentional crash. This is to keep
+ * the leak automation tools happy.
+ *
+ * This also allows us to acquire the process message manaager that
+ * corresponds to the process by sending a message to a frame script
+ * in the content process and having it reply to us via the child
+ * process message manager.
+ */
+function prepareProcess(frameMM, callback) {
+  let frameScript = 'data:,\
+    privateNoteIntentionalCrash();\
+    var cpmm = Components.classes["@mozilla.org/childprocessmessagemanager;1"]\
+                         .getService(Components.interfaces.nsISyncMessageSender);\
+    addMessageListener("TestChild:Ohai", function receiveMessage(msg) {\
+      cpmm.sendAsyncMessage("TestChild:Ohai");\
+    });';
+  frameMM.loadFrameScript(frameScript, false);
+  frameMM.sendAsyncMessage("TestChild:Ohai");
+  ppmm.addMessageListener("TestChild:Ohai", function receiveMessage(msg) {
+    ppmm.removeMessageListener("TestChild:Ohai", receiveMessage);
+    msg = SpecialPowers.wrap(msg);
+    callback(msg.target);
+  });
+}
+
+/**
+ * Expects an OOP frame's process to shut down and report three
+ * events/messages: an error event on the browser element, and a
+ * 'child-process-shutdown' message on both the frame and process
+ * message managers.
+ */
+function expectFrameProcessShutdown(iframe, frameMM, processMM, callback) {
+  let msgCount = 0;
+  function countMessage() {
+    msgCount += 1;
+    if (msgCount == 3) {
+      ok(true, "Observed all three expected events.");
+      callback();
+    }
+  };
+
+  iframe.addEventListener("mozbrowsererror", function onerror(event) {
+    iframe.removeEventListener("mozbrowsererror", onerror);
+    is(event.detail.type, "fatal", "Observed fatal error event.");
+    countMessage();
+  });
+
+  processMM.addMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, function receiveMessage() {
+    processMM.removeMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, receiveMessage);
+    ok(true, "Received 'child-process-shutdown' message from process message manager.");
+    countMessage();
+  });
+
+  frameMM.addMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, function receiveMessage() {
+    frameMM.removeMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, receiveMessage);
+    ok(true, "Received 'child-process-shutdown' message from frame message manager.");
+    countMessage();
+  });
+}
+
+function runTests(callback) {
+  SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
+  SpecialPowers.setBoolPref("dom.ipc.browser_frames.oop_by_default", true);
+  SpecialPowers.addPermission("browser", true, window.document);
+
+  function tearDown() {
+    SpecialPowers.clearUserPref("dom.mozBrowserFramesEnabled");
+    SpecialPowers.clearUserPref("dom.ipc.browser_frames.oop_by_default");
+    SimpleTest.finish();
+  }
+
+  loadApp(function (iframe) {
+    // We want to make sure we get notified on both the frame and
+    // process message managers.
+    let frameMM = SpecialPowers.getBrowserFrameMessageManager(iframe);
+    prepareProcess(frameMM, function (processMM) {
+      // Let's kill the content process by asking for a permission
+      // that it doesn't have.
+      ok(!processMM.assertPermission("frobnaz"),
+         "Content child should not have this permission");
+      expectFrameProcessShutdown(iframe, frameMM, processMM, function () {
+        iframe.parentNode.removeChild(iframe);
+        tearDown();
+      });
+    });
+  });
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/base/test/test_messagemanager_assertpermission.html
+++ b/content/base/test/test_messagemanager_assertpermission.html
@@ -14,16 +14,17 @@
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = SpecialPowers.wrap(Components);
 
 const APP_URL = "http://example.org";
 const APP_MANIFEST = "http://example.org/manifest.webapp";
+const CHILD_PROCESS_SHUTDOWN_MESSAGE = "child-process-shutdown";
 
 let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
              .getService(Ci.nsIMessageBroadcaster);
 let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
              .getService(Ci.nsISyncMessageSender);
 let gAppsService = Cc["@mozilla.org/AppsService;1"]
                      .getService(Ci.nsIAppsService);
 
@@ -76,16 +77,51 @@ function prepareProcess(frameMM, callbac
   frameMM.sendAsyncMessage("TestChild:Ohai");
   ppmm.addMessageListener("TestChild:Ohai", function receiveMessage(msg) {
     ppmm.removeMessageListener("TestChild:Ohai", receiveMessage);
     msg = SpecialPowers.wrap(msg);
     callback(msg.target);
   });
 }
 
+/**
+ * Expects an OOP frame's process to shut down and report three
+ * events/messages: an error event on the browser element, and a
+ * 'child-process-shutdown' message on both the frame and process
+ * message managers.
+ */
+function expectFrameProcessShutdown(iframe, frameMM, processMM, callback) {
+  let msgCount = 0;
+  function countMessage() {
+    msgCount += 1;
+    if (msgCount == 3) {
+      ok(true, "Observed all three expected events.");
+      callback();
+    }
+  };
+
+  iframe.addEventListener("mozbrowsererror", function onerror(event) {
+    iframe.removeEventListener("mozbrowsererror", onerror);
+    is(event.detail.type, "fatal", "Observed fatal error event.");
+    countMessage();
+  });
+
+  processMM.addMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, function receiveMessage() {
+    processMM.removeMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, receiveMessage);
+    ok(true, "Received 'child-process-shutdown' message from process message manager.");
+    countMessage();
+  });
+
+  frameMM.addMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, function receiveMessage() {
+    frameMM.removeMessageListener(CHILD_PROCESS_SHUTDOWN_MESSAGE, receiveMessage);
+    ok(true, "Received 'child-process-shutdown' message from frame message manager.");
+    countMessage();
+  });
+}
+
 function testSameProcess() {
   // Assert permissions on the in-process child process message manager.
   // It always has all permissions, including ones that were never
   // assigned to anybody.
 
   cpmm.sendAsyncMessage("TestPermission:InProcess");
   ppmm.addMessageListener("TestPermission:InProcess", function receiveMessage(msg) {
     ppmm.removeMessageListener("TestPermission:InProcess", receiveMessage);
@@ -101,23 +137,17 @@ function testFrameMessageManager() {
 
   loadApp(function (iframe) {
     let frameMM = SpecialPowers.getBrowserFrameMessageManager(iframe);
     prepareProcess(frameMM, function (processMM) {
       ok(frameMM.assertPermission("foobar"),
          "Frame mm has assigned permission.");
       ok(!frameMM.assertPermission("frobnaz"),
          "Frame mm doesn't have non-existing permission.");
-
-      // The last permission check will result in the content process
-      // being killed.
-      iframe.addEventListener("mozbrowsererror", function onerror(event) {
-        iframe.removeEventListener("mozbrowsererror", onerror);
-        is(event.detail.type, "fatal", "Observed fatal error event.");
-
+      expectFrameProcessShutdown(iframe, frameMM, processMM, function () {
         iframe.parentNode.removeChild(iframe);
         runNextTest();
       });
     });
   });
 }
 
 function testChildProcessMessageManager() {
@@ -125,23 +155,17 @@ function testChildProcessMessageManager(
 
   loadApp(function (iframe) {
     let frameMM = SpecialPowers.getBrowserFrameMessageManager(iframe);
     prepareProcess(frameMM, function (processMM) {
       ok(processMM.assertPermission("foobar"),
          "Process mm has assigned permission.");
       ok(!processMM.assertPermission("frobnaz"),
          "Process mm doesn't have non-existing permission.");
-
-      // The last permission check will result in the content process
-      // being killed.
-      iframe.addEventListener("mozbrowsererror", function onerror(event) {
-        iframe.removeEventListener("mozbrowsererror", onerror);
-        is(event.detail.type, "fatal", "Observed fatal error event.");
-
+      expectFrameProcessShutdown(iframe, frameMM, processMM, function () {
         iframe.parentNode.removeChild(iframe);
         runNextTest();
       });
     });
   });
 }
 
 function tearDown() {
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -568,16 +568,22 @@ struct DelayedDeleteContentParentTask : 
     nsRefPtr<ContentParent> mObj;
 };
 
 }
 
 void
 ContentParent::ActorDestroy(ActorDestroyReason why)
 {
+    nsRefPtr<nsFrameMessageManager> ppm = mMessageManager;
+    if (ppm) {
+      ppm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm.get()),
+                          CHILD_PROCESS_SHUTDOWN_MESSAGE, false,
+                          nullptr, nullptr, nullptr);
+    }
     nsCOMPtr<nsIThreadObserver>
         kungFuDeathGrip(static_cast<nsIThreadObserver*>(this));
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "xpcom-shutdown");
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "memory-pressure");
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "child-memory-reporter-request");
         obs->RemoveObserver(static_cast<nsIObserver*>(this), NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC);
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -21,16 +21,18 @@
 #include "nsNetUtil.h"
 #include "nsIPermissionManager.h"
 #include "nsIDOMGeoPositionCallback.h"
 #include "nsIMemoryReporter.h"
 #include "nsCOMArray.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 
+#define CHILD_PROCESS_SHUTDOWN_MESSAGE NS_LITERAL_STRING("child-process-shutdown")
+
 class mozIApplication;
 class nsIDOMBlob;
 
 namespace mozilla {
 
 namespace ipc {
 class OptionalURIParams;
 class URIParams;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -127,16 +127,17 @@ TabParent::ActorDestroy(ActorDestroyReas
   if (sEventCapturer == this) {
     sEventCapturer = nullptr;
   }
   if (mIMETabParent == this) {
     mIMETabParent = nullptr;
   }
   nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
   if (frameLoader) {
+    ReceiveMessage(CHILD_PROCESS_SHUTDOWN_MESSAGE, false, nullptr, nullptr);
     frameLoader->DestroyChild();
 
     if (why == AbnormalShutdown) {
       nsCOMPtr<nsIObserverService> os = services::GetObserverService();
       if (os) {
         os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, frameLoader),
                             "oop-frameloader-crashed", nullptr);
       }