Bug 581335 - Hook up crash reporting for content process by attaching a property bag to the global observer-service notification for them. r=mfinkle
authorBenjamin Smedberg <benjamin@smedbergs.us>
Wed, 24 Nov 2010 08:58:21 -0500
changeset 58164 9a7aaa92e4b916755461fab726953fbc4ef268e1
parent 58163 78d9167f4372cc1dc56126778cf63ba1d453c33e
child 58165 0ec2e68a8677c0c64c75cb9ffd1fbb6d0936df3c
push id17177
push userbsmedberg@mozilla.com
push dateWed, 24 Nov 2010 14:10:57 +0000
treeherdermozilla-central@0ec2e68a8677 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs581335
milestone2.0b8pre
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 581335 - Hook up crash reporting for content process by attaching a property bag to the global observer-service notification for them. r=mfinkle
content/base/public/nsIFrameMessageManager.idl
content/base/src/nsFrameMessageManager.cpp
content/base/src/nsInProcessTabChildGlobal.cpp
content/base/src/nsInProcessTabChildGlobal.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/Makefile.in
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/tests/Makefile.in
dom/ipc/tests/process_error.xul
dom/ipc/tests/process_error_contentscript.js
dom/ipc/tests/test_process_error.xul
--- a/content/base/public/nsIFrameMessageManager.idl
+++ b/content/base/public/nsIFrameMessageManager.idl
@@ -93,16 +93,22 @@ interface nsIContentFrameMessageManager 
    * The top level docshell or null.
    */
   readonly attribute nsIDocShell docShell;
 
   /**
    * Print a string to stdout.
    */
   void dump(in DOMString aStr);
+
+  /**
+   * If leak detection is enabled, print a note to the leak log that this
+   * process will intentionally crash.
+   */
+  void privateNoteIntentionalCrash();
 };
 
 [uuid(9c48d557-92fe-4edb-95fc-bfe97e77bdc3)]
 interface nsIInProcessContentFrameMessageManager : nsIContentFrameMessageManager
 {
   [notxpcom] nsIContent getOwnerContent();
 };
 
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -303,16 +303,22 @@ NS_IMETHODIMP
 nsFrameMessageManager::Dump(const nsAString& aStr)
 {
   fputs(NS_ConvertUTF16toUTF8(aStr).get(), stdout);
   fflush(stdout);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsFrameMessageManager::PrivateNoteIntentionalCrash()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
 nsFrameMessageManager::GetContent(nsIDOMWindow** aContent)
 {
   *aContent = nsnull;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameMessageManager::GetDocShell(nsIDocShell** aDocShell)
--- a/content/base/src/nsInProcessTabChildGlobal.cpp
+++ b/content/base/src/nsInProcessTabChildGlobal.cpp
@@ -175,16 +175,22 @@ nsInProcessTabChildGlobal::GetContent(ns
 
 NS_IMETHODIMP
 nsInProcessTabChildGlobal::GetDocShell(nsIDocShell** aDocShell)
 {
   NS_IF_ADDREF(*aDocShell = mDocShell);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsInProcessTabChildGlobal::PrivateNoteIntentionalCrash()
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 void
 nsInProcessTabChildGlobal::Disconnect()
 {
   // 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)
   );
--- a/content/base/src/nsInProcessTabChildGlobal.h
+++ b/content/base/src/nsInProcessTabChildGlobal.h
@@ -72,16 +72,17 @@ public:
                            : NS_ERROR_NULL_POINTER;
   }
   NS_IMETHOD GetContent(nsIDOMWindow** aContent);
   NS_IMETHOD GetDocShell(nsIDocShell** aDocShell);
   NS_IMETHOD Dump(const nsAString& aStr)
   {
     return mMessageManager ? mMessageManager->Dump(aStr) : NS_OK;
   }
+  NS_IMETHOD PrivateNoteIntentionalCrash();
   NS_DECL_NSIINPROCESSCONTENTFRAMEMESSAGEMANAGER
 
   virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
   NS_IMETHOD AddEventListener(const nsAString& aType,
                               nsIDOMEventListener* aListener,
                               PRBool aUseCapture)
   {
     // By default add listeners only for trusted events!
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -38,16 +38,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "ContentParent.h"
 
 #include "TabParent.h"
 #include "History.h"
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/net/NeckoParent.h"
+#include "nsHashPropertyBag.h"
 #include "nsIFilePicker.h"
 #include "nsIWindowWatcher.h"
 #include "nsIDOMWindow.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefBranch2.h"
 #include "nsIPrefService.h"
 #include "nsIPrefLocalizedString.h"
 #include "nsIObserverService.h"
@@ -67,16 +68,20 @@
 #include "nsIScriptError.h"
 #include "nsConsoleMessage.h"
 #include "AudioParent.h"
 
 #ifdef MOZ_PERMISSIONS
 #include "nsPermissionManager.h"
 #endif
 
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
 #include "mozilla/dom/ExternalHelperAppParent.h"
 #include "mozilla/dom/StorageParent.h"
 #include "nsAccelerometer.h"
 
 using namespace mozilla::ipc;
 using namespace mozilla::net;
 using namespace mozilla::places;
 using mozilla::MonitorAutoEnter;
@@ -119,16 +124,38 @@ ContentParent::GetSingleton(PRBool aForc
                 threadInt->SetObserver(parent);
             }
         }
     }
     return gSingleton;
 }
 
 void
+ContentParent::OnChannelConnected(int32 pid)
+{
+    ProcessHandle handle;
+    if (!base::OpenPrivilegedProcessHandle(pid, &handle)) {
+        NS_WARNING("Can't open handle to child process.");
+    }
+    else {
+        SetOtherProcess(handle);
+    }
+}
+
+namespace {
+void
+DelayedDeleteSubprocess(GeckoChildProcessHost* aSubprocess)
+{
+    XRE_GetIOMessageLoop()
+        ->PostTask(FROM_HERE,
+                   new DeleteTask<GeckoChildProcessHost>(aSubprocess));
+}
+}
+
+void
 ContentParent::ActorDestroy(ActorDestroyReason why)
 {
     nsCOMPtr<nsIThreadObserver>
         kungFuDeathGrip(static_cast<nsIThreadObserver*>(this));
     nsCOMPtr<nsIObserverService>
         obs(do_GetService("@mozilla.org/observer-service;1"));
     if (obs)
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "xpcom-shutdown");
@@ -137,21 +164,42 @@ ContentParent::ActorDestroy(ActorDestroy
     if (threadInt)
         threadInt->SetObserver(mOldObserver);
     if (mRunToCompletionDepth)
         mRunToCompletionDepth = 0;
 
     mIsAlive = false;
 
     if (obs) {
-        nsString context = NS_LITERAL_STRING("");
-        if (AbnormalShutdown == why)
-            context.AssignLiteral("abnormal");
-        obs->NotifyObservers(nsnull, "ipc:content-shutdown", context.get());
+        nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+        props->Init();
+
+        if (AbnormalShutdown == why) {
+            props->SetPropertyAsBool(NS_LITERAL_STRING("abnormal"), PR_TRUE);
+
+#ifdef MOZ_CRASHREPORTER
+            nsAutoString dumpID;
+
+            nsCOMPtr<nsILocalFile> crashDump;
+            TakeMinidump(getter_AddRefs(crashDump)) &&
+                CrashReporter::GetIDFromMinidump(crashDump, dumpID);
+
+            if (!dumpID.IsEmpty())
+                props->SetPropertyAsAString(NS_LITERAL_STRING("dumpID"),
+                                            dumpID);
+#endif
+
+            obs->NotifyObservers((nsIPropertyBag2*) props, "ipc:content-shutdown", nsnull);
+        }
     }
+
+    MessageLoop::current()->
+        PostTask(FROM_HERE,
+                 NewRunnableFunction(DelayedDeleteSubprocess, mSubprocess));
+    mSubprocess = NULL;
 }
 
 TabParent*
 ContentParent::CreateTab(PRUint32 aChromeFlags)
 {
   return static_cast<TabParent*>(SendPBrowserConstructor(aChromeFlags));
 }
 
@@ -182,16 +230,19 @@ ContentParent::ContentParent()
     nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
     nsChromeRegistryChrome* chromeRegistry =
         static_cast<nsChromeRegistryChrome*>(registrySvc.get());
     chromeRegistry->SendRegisteredChrome(this);
 }
 
 ContentParent::~ContentParent()
 {
+    if (OtherProcess())
+        base::CloseProcessHandle(OtherProcess());
+
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     //If the previous content process has died, a new one could have
     //been started since.
     if (gSingleton == this)
         gSingleton = nsnull;
 }
 
 bool
@@ -263,24 +314,16 @@ ContentParent::RecvReadPermissions(Infal
     return true;
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS3(ContentParent,
                               nsIObserver,
                               nsIThreadObserver,
                               nsIDOMGeoPositionCallback)
 
-namespace {
-void
-DeleteSubprocess(GeckoChildProcessHost* aSubprocess)
-{
-    delete aSubprocess;
-}
-}
-
 NS_IMETHODIMP
 ContentParent::Observe(nsISupports* aSubject,
                        const char* aTopic,
                        const PRUnichar* aData)
 {
     if (!strcmp(aTopic, "xpcom-shutdown") && mSubprocess) {
         // remove the global remote preferences observers
         nsCOMPtr<nsIPrefBranch2> prefs 
@@ -289,20 +332,17 @@ ContentParent::Observe(nsISupports* aSub
             if (gSingleton) {
                 prefs->RemoveObserver("", this);
             }
         }
 
         RecvRemoveGeolocationListener();
             
         Close();
-        XRE_GetIOMessageLoop()->PostTask(
-            FROM_HERE,
-            NewRunnableFunction(DeleteSubprocess, mSubprocess));
-        mSubprocess = nsnull;
+        NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess");
     }
 
     if (!mIsAlive || !mSubprocess)
         return NS_OK;
 
     // listening for remotePrefs...
     if (!strcmp(aTopic, "nsPref:changed")) {
         // We know prefs are ASCII here.
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -94,16 +94,17 @@ public:
     bool DestroyTestShell(TestShellParent* aTestShell);
 
     void ReportChildAlreadyBlocked();
     bool RequestRunToCompletion();
 
     bool IsAlive();
 
 protected:
+    void OnChannelConnected(int32 pid);
     virtual void ActorDestroy(ActorDestroyReason why);
 
 private:
     static ContentParent* gSingleton;
 
     // Hide the raw constructor methods since we don't want client code
     // using them.
     using PContentParent::SendPBrowserConstructor;
--- a/dom/ipc/Makefile.in
+++ b/dom/ipc/Makefile.in
@@ -42,16 +42,18 @@ VPATH = @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = dom
 LIBRARY_NAME = domipc_s
 LIBXUL_LIBRARY = 1
 FORCE_STATIC_LIB = 1
 EXPORT_LIBRARY = 1
 
+DIRS = tests
+
 EXPORTS = TabMessageUtils.h PCOMContentPermissionRequestChild.h
 
 EXPORTS_NAMESPACES = mozilla/dom
 
 EXPORTS_mozilla/dom = \
   AudioChild.h \
   AudioParent.h \
   ContentChild.h \
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -32,16 +32,17 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "TabChild.h"
+#include "mozilla/IntentionalCrash.h"
 #include "mozilla/dom/PContentChild.h"
 #include "mozilla/dom/PContentDialogChild.h"
 #include "mozilla/layers/PLayersChild.h"
 #include "mozilla/layout/RenderFrameChild.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 
 #include "BasicLayers.h"
 #include "nsIWebBrowser.h"
@@ -993,16 +994,23 @@ TabChildGlobal::GetContent(nsIDOMWindow*
   if (!mTabChild)
     return NS_ERROR_NULL_POINTER;
   nsCOMPtr<nsIDOMWindow> window = do_GetInterface(mTabChild->WebNavigation());
   window.swap(*aContent);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+TabChildGlobal::PrivateNoteIntentionalCrash()
+{
+    mozilla::NoteIntentionalCrash("tab");
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 TabChildGlobal::GetDocShell(nsIDocShell** aDocShell)
 {
   *aDocShell = nsnull;
   if (!mTabChild)
     return NS_ERROR_NULL_POINTER;
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(mTabChild->WebNavigation());
   docShell.swap(*aDocShell);
   return NS_OK;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -103,16 +103,17 @@ public:
                            : NS_ERROR_NULL_POINTER;
   }
   NS_IMETHOD GetContent(nsIDOMWindow** aContent);
   NS_IMETHOD GetDocShell(nsIDocShell** aDocShell);
   NS_IMETHOD Dump(const nsAString& aStr)
   {
     return mMessageManager ? mMessageManager->Dump(aStr) : NS_OK;
   }
+  NS_IMETHOD PrivateNoteIntentionalCrash();
 
   NS_IMETHOD AddEventListener(const nsAString& aType,
                               nsIDOMEventListener* aListener,
                               PRBool aUseCapture)
   {
     // By default add listeners only for trusted events!
     return nsDOMEventTargetHelper::AddEventListener(aType, aListener,
                                                     aUseCapture, PR_FALSE, 1);
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/Makefile.in
@@ -0,0 +1,53 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Firefox testing code.
+#
+# The Initial Developer of the Original Code is
+# the Mozilla Founation <http://www.mozilla.org/>.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = dom/ipc/tests
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+MOCHICHROME_FILES = \
+  test_process_error.xul \
+  process_error.xul \
+  process_error_contentscript.js \
+  $(NULL)
+
+libs:: $(MOCHICHROME_FILES)
+	$(INSTALL) $^ $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/process_error.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+	orient="vertical">
+
+  <browser id="thebrowser" type="content" remote="true" />
+  <script type="application/javascript"><![CDATA[
+    Components.utils.import("resource://gre/modules/Services.jsm");
+
+    const ok = window.opener.wrappedJSObject.ok;
+    const is = window.opener.wrappedJSObject.is;
+    const done = window.opener.wrappedJSObject.done;
+    const SimpleTest = window.opener.wrappedJSObject.SimpleTest;
+
+    function crashObserver(subject, topic, data) {
+      is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+      ok(subject instanceof Components.interfaces.nsIPropertyBag2,
+         'Subject implements nsIPropertyBag2.');
+      
+      if ('nsICrashReporter' in Components.interfaces) {
+        ok(subject.getPropertyAsAString('dumpID'), "dumpID is present");
+      }
+
+      Services.obs.removeObserver(crashObserver, 'ipc:content-shutdown');
+      done();
+    }
+    Services.obs.addObserver(crashObserver, 'ipc:content-shutdown', false);
+
+    document.getElementById('thebrowser').
+      QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.
+      messageManager.loadFrameScript('chrome://mochitests/content/chrome/dom/ipc/tests/process_error_contentscript.js', true);
+  ]]></script>
+
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/process_error_contentscript.js
@@ -0,0 +1,7 @@
+Components.utils.import("resource://gre/modules/ctypes.jsm");
+
+privateNoteIntentionalCrash();
+
+var zero = new ctypes.intptr_t(8);
+var badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+var crash = badptr.contents;
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/test_process_error.xul
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+	  src="chrome://mochikit/content/MochiKit/packed.js" />
+  <script type="application/javascript"
+	  src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+  <script>
+  SimpleTest.waitForExplicitFinish();
+
+  var w = window.open('process_error.xul', '_blank', 'chrome,resizable=yes,width=400,height=600');
+
+  function done()
+  {
+    w.close();
+    SimpleTest.finish();
+  }
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" />
+</window>