Bug 820704 - AudioChannelService must handle children quitting unexpectedly. r=jlebar, a=blocking-basecamp
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 28 Dec 2012 12:57:35 -0500
changeset 117898 38f7f302385e0248e4bb11ca58d07cfa154ff45e
parent 117897 d15afe54a3dedfbeac428c6e1c6026e03bf1e391
child 117899 7def9849a8a1da686fc33fa8e90f0d27d64f46f7
push id105
push userryanvm@gmail.com
push dateSat, 29 Dec 2012 15:53:07 +0000
reviewersjlebar, blocking-basecamp
bugs820704
milestone18.0
Bug 820704 - AudioChannelService must handle children quitting unexpectedly. r=jlebar, a=blocking-basecamp
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/audiochannel/AudioChannelServiceChild.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -63,74 +63,73 @@ AudioChannelService::Shutdown()
   }
 }
 
 NS_IMPL_ISUPPORTS0(AudioChannelService)
 
 AudioChannelService::AudioChannelService()
 : mCurrentHigherChannel(AUDIO_CHANNEL_NORMAL)
 {
-  mChannelCounters = new int32_t[AUDIO_CHANNEL_PUBLICNOTIFICATION+1];
-
-  for (int i = AUDIO_CHANNEL_NORMAL;
-       i <= AUDIO_CHANNEL_PUBLICNOTIFICATION;
-       ++i) {
-    mChannelCounters[i] = 0;
-  }
-
   // Creation of the hash table.
   mAgents.Init();
+
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->AddObserver(this, "ipc:content-shutdown", false);
+    }
+  }
 }
 
 AudioChannelService::~AudioChannelService()
 {
-  delete [] mChannelCounters;
 }
 
 void
 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
-                                          AudioChannelType aType)
+                                               AudioChannelType aType)
 {
   mAgents.Put(aAgent, aType);
-  RegisterType(aType);
+  RegisterType(aType, CONTENT_PARENT_UNKNOWN_CHILD_ID);
 }
 
 void
-AudioChannelService::RegisterType(AudioChannelType aType)
+AudioChannelService::RegisterType(AudioChannelType aType, uint64_t aChildID)
 {
-  mChannelCounters[aType]++;
+  mChannelCounters[aType].AppendElement(aChildID);
 
   // In order to avoid race conditions, it's safer to notify any existing
   // agent any time a new one is registered.
   Notify();
 }
 
 void
 AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
 {
   AudioChannelType type;
   if (!mAgents.Get(aAgent, &type)) {
     return;
   }
 
   mAgents.Remove(aAgent);
-  UnregisterType(type);
+  UnregisterType(type, CONTENT_PARENT_UNKNOWN_CHILD_ID);
 }
 
 void
-AudioChannelService::UnregisterType(AudioChannelType aType)
+AudioChannelService::UnregisterType(AudioChannelType aType, uint64_t aChildID)
 {
-  mChannelCounters[aType]--;
-  MOZ_ASSERT(mChannelCounters[aType] >= 0);
+  // The array may contain multiple occurrence of this appId but
+  // this should remove only the first one.
+  mChannelCounters[aType].RemoveElement(aChildID);
 
   bool isNoChannelUsed = true;
   for (int32_t type = AUDIO_CHANNEL_NORMAL;
          type <= AUDIO_CHANNEL_PUBLICNOTIFICATION;
          ++type) {
-    if (mChannelCounters[type]) {
+    if (!mChannelCounters[type].IsEmpty()) {
       isNoChannelUsed = false;
       break;
     }
   }
 
   if (isNoChannelUsed) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(nullptr, "audio-channel-changed", NS_LITERAL_STRING("default").get());
@@ -148,17 +147,17 @@ AudioChannelService::GetMuted(AudioChann
   // We are not visible, maybe we have to mute:
   if (aElementHidden) {
     switch (aType) {
       case AUDIO_CHANNEL_NORMAL:
         return true;
 
       case AUDIO_CHANNEL_CONTENT:
         // TODO: this should work per apps
-        if (mChannelCounters[AUDIO_CHANNEL_CONTENT] > 1)
+        if (mChannelCounters[AUDIO_CHANNEL_CONTENT].Length() > 1)
           return true;
         break;
 
       case AUDIO_CHANNEL_NOTIFICATION:
       case AUDIO_CHANNEL_ALARM:
       case AUDIO_CHANNEL_TELEPHONY:
       case AUDIO_CHANNEL_RINGER:
       case AUDIO_CHANNEL_PUBLICNOTIFICATION:
@@ -172,21 +171,21 @@ AudioChannelService::GetMuted(AudioChann
   }
 
   bool muted = false;
 
   // Priorities:
   switch (aType) {
     case AUDIO_CHANNEL_NORMAL:
     case AUDIO_CHANNEL_CONTENT:
-      muted = !!mChannelCounters[AUDIO_CHANNEL_NOTIFICATION] ||
-              !!mChannelCounters[AUDIO_CHANNEL_ALARM] ||
-              !!mChannelCounters[AUDIO_CHANNEL_TELEPHONY] ||
-              !!mChannelCounters[AUDIO_CHANNEL_RINGER] ||
-              !!mChannelCounters[AUDIO_CHANNEL_PUBLICNOTIFICATION];
+      muted = !mChannelCounters[AUDIO_CHANNEL_NOTIFICATION].IsEmpty() ||
+              !mChannelCounters[AUDIO_CHANNEL_ALARM].IsEmpty() ||
+              !mChannelCounters[AUDIO_CHANNEL_TELEPHONY].IsEmpty() ||
+              !mChannelCounters[AUDIO_CHANNEL_RINGER].IsEmpty() ||
+              !mChannelCounters[AUDIO_CHANNEL_PUBLICNOTIFICATION].IsEmpty();
       break;
 
     case AUDIO_CHANNEL_NOTIFICATION:
     case AUDIO_CHANNEL_ALARM:
     case AUDIO_CHANNEL_TELEPHONY:
     case AUDIO_CHANNEL_RINGER:
       muted = ChannelsActiveWithHigherPriorityThan(aType);
       break;
@@ -202,17 +201,17 @@ AudioChannelService::GetMuted(AudioChann
   // Notification if needed.
   if (!muted) {
 
     // Calculating the most important unmuted channel:
     AudioChannelType higher = AUDIO_CHANNEL_NORMAL;
     for (int32_t type = AUDIO_CHANNEL_NORMAL;
          type <= AUDIO_CHANNEL_PUBLICNOTIFICATION;
          ++type) {
-      if (mChannelCounters[type]) {
+      if (!mChannelCounters[type].IsEmpty()) {
         higher = (AudioChannelType)type;
       }
     }
 
     if (higher != mCurrentHigherChannel) {
       mCurrentHigherChannel = higher;
 
       nsString channelName;
@@ -221,17 +220,16 @@ AudioChannelService::GetMuted(AudioChann
       nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
       obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get());
     }
   }
 
   return muted;
 }
 
-
 static PLDHashOperator
 NotifyEnumerator(AudioChannelAgent* aAgent,
                  AudioChannelType aType, void* aData)
 {
   if (aAgent) {
     aAgent->NotifyAudioChannelStateChanged();
   }
   return PL_DHASH_NEXT;
@@ -257,17 +255,17 @@ bool
 AudioChannelService::ChannelsActiveWithHigherPriorityThan(AudioChannelType aType)
 {
   for (int i = AUDIO_CHANNEL_PUBLICNOTIFICATION;
        i != AUDIO_CHANNEL_CONTENT; --i) {
     if (i == aType) {
       return false;
     }
 
-    if (mChannelCounters[i]) {
+    if (!mChannelCounters[i].IsEmpty()) {
       return true;
     }
   }
 
   return false;
 }
 
 const char*
@@ -293,8 +291,42 @@ AudioChannelService::ChannelName(AudioCh
       return ChannelNameTable[i].value;
     }
   }
 
   NS_NOTREACHED("Execution should not reach here!");
   return nullptr;
 }
 
+NS_IMETHODIMP
+AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* data)
+{
+  MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown"));
+
+  nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
+  if (!props) {
+    NS_WARNING("ipc:content-shutdown message without property bag as subject");
+    return NS_OK;
+  }
+
+  uint64_t childID = 0;
+  nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
+                                           &childID);
+  if (NS_SUCCEEDED(rv)) {
+    for (int32_t type = AUDIO_CHANNEL_NORMAL;
+         type <= AUDIO_CHANNEL_PUBLICNOTIFICATION;
+         ++type) {
+      int32_t index;
+      while ((index = mChannelCounters[type].IndexOf(childID)) != -1) {
+        mChannelCounters[type].RemoveElementAt(index);
+      }
+    }
+
+    // We don't have to remove the agents from the mAgents hashtable because if
+    // that table contains only agents running on the same process.
+
+    Notify();
+  } else {
+    NS_WARNING("ipc:content-shutdown message without childID property");
+  }
+
+  return NS_OK;
+}
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -3,29 +3,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_audiochannelservice_h__
 #define mozilla_dom_audiochannelservice_h__
 
 #include "nsAutoPtr.h"
-#include "nsISupports.h"
+#include "nsIObserver.h"
 
 #include "AudioChannelCommon.h"
 #include "AudioChannelAgent.h"
 #include "nsDataHashtable.h"
 
 namespace mozilla {
 namespace dom {
 
-class AudioChannelService : public nsISupports
+class AudioChannelService : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
 
   /**
    * Returns the AudioChannelServce singleton. Only to be called from main thread.
    * @return NS_OK on proper assignment, NS_ERROR_FAILURE otherwise.
    */
   static AudioChannelService*
   GetAudioChannelService();
 
@@ -51,29 +52,29 @@ public:
    * Return true if this type should be muted.
    */
   virtual bool GetMuted(AudioChannelType aType, bool aElementHidden);
 
 protected:
   void Notify();
 
   /* Register/Unregister IPC types: */
-  void RegisterType(AudioChannelType aType);
-  void UnregisterType(AudioChannelType aType);
+  void RegisterType(AudioChannelType aType, uint64_t aChildID);
+  void UnregisterType(AudioChannelType aType, uint64_t aChildID);
 
   AudioChannelService();
   virtual ~AudioChannelService();
 
   bool ChannelsActiveWithHigherPriorityThan(AudioChannelType aType);
 
   const char* ChannelName(AudioChannelType aType);
 
   nsDataHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelType > mAgents;
 
-  int32_t* mChannelCounters;
+  nsTArray<uint64_t> mChannelCounters[AUDIO_CHANNEL_PUBLICNOTIFICATION+1];
 
   AudioChannelType mCurrentHigherChannel;
 
   // This is needed for IPC comunication between
   // AudioChannelServiceChild and this class.
   friend class ContentParent;
   friend class ContentChild;
 };
--- a/dom/audiochannel/AudioChannelServiceChild.cpp
+++ b/dom/audiochannel/AudioChannelServiceChild.cpp
@@ -69,17 +69,17 @@ AudioChannelServiceChild::GetMuted(Audio
     cc->SendAudioChannelGetMuted(aType, aMozHidden, &muted);
   }
 
   return muted;
 }
 
 void
 AudioChannelServiceChild::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
-                                               AudioChannelType aType)
+                                                    AudioChannelType aType)
 {
   AudioChannelService::RegisterAudioChannelAgent(aAgent, aType);
 
   ContentChild *cc = ContentChild::GetSingleton();
   if (cc) {
     cc->SendAudioChannelRegisterType(aType);
   }
 }
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -774,17 +774,17 @@ ContentParent::GetTestShellSingleton()
     return static_cast<TestShellParent*>(ManagedPTestShellParent()[0]);
 }
 
 ContentParent::ContentParent(const nsAString& aAppManifestURL,
                              bool aIsForBrowser,
                              ChildOSPrivileges aOSPrivileges)
     : mSubprocess(nullptr)
     , mOSPrivileges(aOSPrivileges)
-    , mChildID(-1)
+    , mChildID(CONTENT_PARENT_UNKNOWN_CHILD_ID)
     , mGeolocationWatchID(-1)
     , mRunToCompletionDepth(0)
     , mShouldCallUnblockChild(false)
     , mAppManifestURL(aAppManifestURL)
     , mIsAlive(true)
     , mIsDestroyed(false)
     , mSendPermissionUpdates(false)
     , mIsForBrowser(aIsForBrowser)
@@ -1102,28 +1102,28 @@ ContentParent::RecvAudioChannelGetMuted(
 }
 
 bool
 ContentParent::RecvAudioChannelRegisterType(const AudioChannelType& aType)
 {
     nsRefPtr<AudioChannelService> service =
         AudioChannelService::GetAudioChannelService();
     if (service) {
-        service->RegisterType(aType);
+        service->RegisterType(aType, mChildID);
     }
     return true;
 }
 
 bool
 ContentParent::RecvAudioChannelUnregisterType(const AudioChannelType& aType)
 {
     nsRefPtr<AudioChannelService> service =
         AudioChannelService::GetAudioChannelService();
     if (service) {
-        service->UnregisterType(aType);
+        service->UnregisterType(aType, mChildID);
     }
     return true;
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS3(ContentParent,
                               nsIObserver,
                               nsIThreadObserver,
                               nsIDOMGeoPositionCallback)
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -24,16 +24,18 @@
 #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")
 
+#define CONTENT_PARENT_UNKNOWN_CHILD_ID -1
+
 class mozIApplication;
 class nsConsoleService;
 class nsIDOMBlob;
 
 namespace mozilla {
 
 namespace ipc {
 class OptionalURIParams;
@@ -127,16 +129,18 @@ public:
 
     /**
      * Kill our subprocess and make sure it dies.  Should only be used
      * in emergency situations since it bypasses the normal shutdown
      * process.
      */
     void KillHard();
 
+    uint64_t ChildID() { return mChildID; }
+
 protected:
     void OnChannelConnected(int32_t pid);
     virtual void ActorDestroy(ActorDestroyReason why);
 
 private:
     typedef base::ChildPrivileges ChildOSPrivileges;
 
     static nsDataHashtable<nsStringHashKey, ContentParent*> *gAppContentParents;