Bug 920804 - Improve nsFrameMessageManager p=fabrice,smaug, r=smaug,fabrice
☠☠ backed out by fb0173b73836 ☠ ☠
authorFabrice Desré <fabrice@mozilla.com>
Thu, 24 Oct 2013 09:52:36 -0700
changeset 165956 5e8e1e39adf33d382f10620aa68a90cb82534dbf
parent 165941 94e90235eee0d8265cbebe807958058ddb4144f1
child 165957 8e6b095b47193b72bdcd8f24387341716270e527
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, fabrice
bugs920804
milestone27.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 920804 - Improve nsFrameMessageManager p=fabrice,smaug, r=smaug,fabrice
content/base/src/nsFrameMessageManager.cpp
content/base/src/nsFrameMessageManager.h
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -46,24 +46,36 @@
 #  undef SendMessage
 # endif
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 
+static PLDHashOperator
+CycleCollectorTraverseListeners(const nsAString& aKey,
+                                nsTArray<nsMessageListenerInfo>* aListeners,
+                                void* aCb)
+{
+  nsCycleCollectionTraversalCallback* cb =
+    static_cast<nsCycleCollectionTraversalCallback*> (aCb);
+  uint32_t count = aListeners->Length();
+  for (uint32_t i = 0; i < count; ++i) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "listeners[i] mStrongListener");
+    cb->NoteXPCOMChild((*aListeners)[i].mStrongListener.get());
+  }
+  return PL_DHASH_NEXT;
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager)
-  uint32_t count = tmp->mListeners.Length();
-  for (uint32_t i = 0; i < count; i++) {
-    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mListeners[i] mStrongListener");
-    cb.NoteXPCOMChild(tmp->mListeners[i].mStrongListener.get());
-  }
+  tmp->mListeners.EnumerateRead(CycleCollectorTraverseListeners,
+                                static_cast<void*>(&cb));
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameMessageManager)
   tmp->mListeners.Clear();
   for (int32_t i = tmp->mChildManagers.Count(); i > 0; --i) {
     static_cast<nsFrameMessageManager*>(tmp->mChildManagers[i - 1])->
       Disconnect(false);
@@ -241,100 +253,136 @@ SameProcessCpowHolder::ToObject(JSContex
 }
 
 // nsIMessageListenerManager
 
 NS_IMETHODIMP
 nsFrameMessageManager::AddMessageListener(const nsAString& aMessage,
                                           nsIMessageListener* aListener)
 {
-  nsCOMPtr<nsIAtom> message = do_GetAtom(aMessage);
-  uint32_t len = mListeners.Length();
-  for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mMessage == message &&
-      mListeners[i].mStrongListener == aListener) {
-      return NS_OK;
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (!listeners) {
+    listeners = new nsTArray<nsMessageListenerInfo>();
+    mListeners.Put(aMessage, listeners);
+  } else {
+    uint32_t len = listeners->Length();
+    for (uint32_t i = 0; i < len; ++i) {
+      if ((*listeners)[i].mStrongListener == aListener) {
+        return NS_OK;
+      }
     }
   }
-  nsMessageListenerInfo* entry = mListeners.AppendElement();
+
+  nsMessageListenerInfo* entry = listeners->AppendElement();
   NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
-  entry->mMessage = message;
   entry->mStrongListener = aListener;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage,
                                              nsIMessageListener* aListener)
 {
-  nsCOMPtr<nsIAtom> message = do_GetAtom(aMessage);
-  uint32_t len = mListeners.Length();
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (!listeners) {
+    return NS_OK;
+  }
+
+  uint32_t len = listeners->Length();
   for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mMessage == message &&
-      mListeners[i].mStrongListener == aListener) {
-      mListeners.RemoveElementAt(i);
+    if ((*listeners)[i].mStrongListener == aListener) {
+      listeners->RemoveElementAt(i);
       return NS_OK;
     }
   }
   return NS_OK;
 }
 
+#ifdef DEBUG
+typedef struct
+{
+  nsCOMPtr<nsISupports> mCanonical;
+  nsWeakPtr mWeak;
+} CanonicalCheckerParams;
+
+static PLDHashOperator
+CanonicalChecker(const nsAString& aKey,
+                 nsTArray<nsMessageListenerInfo>* aListeners,
+                 void* aParams)
+{
+  CanonicalCheckerParams* params =
+    static_cast<CanonicalCheckerParams*> (aParams);
+
+  uint32_t count = aListeners->Length();
+  for (uint32_t i = 0; i < count; i++) {
+    if (!(*aListeners)[i].mWeakListener) {
+      continue;
+    }
+    nsCOMPtr<nsISupports> otherCanonical =
+      do_QueryReferent((*aListeners)[i].mWeakListener);
+    MOZ_ASSERT((params->mCanonical == otherCanonical) ==
+               (params->mWeak == (*aListeners)[i].mWeakListener));
+  }
+  return PL_DHASH_NEXT;
+}
+#endif
+
 NS_IMETHODIMP
 nsFrameMessageManager::AddWeakMessageListener(const nsAString& aMessage,
                                               nsIMessageListener* aListener)
 {
   nsWeakPtr weak = do_GetWeakReference(aListener);
   NS_ENSURE_TRUE(weak, NS_ERROR_NO_INTERFACE);
 
 #ifdef DEBUG
   // It's technically possible that one object X could give two different
   // nsIWeakReference*'s when you do_GetWeakReference(X).  We really don't want
   // this to happen; it will break e.g. RemoveWeakMessageListener.  So let's
   // check that we're not getting ourselves into that situation.
   nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
-  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
-    if (!mListeners[i].mWeakListener) {
-      continue;
-    }
-
-    nsCOMPtr<nsISupports> otherCanonical =
-      do_QueryReferent(mListeners[i].mWeakListener);
-    MOZ_ASSERT((canonical == otherCanonical) ==
-               (weak == mListeners[i].mWeakListener));
-  }
+  CanonicalCheckerParams params;
+  params.mCanonical = canonical;
+  params.mWeak = weak;
+  mListeners.EnumerateRead(CanonicalChecker, (void*)&params);
 #endif
 
-  nsCOMPtr<nsIAtom> message = do_GetAtom(aMessage);
-  uint32_t len = mListeners.Length();
-  for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mMessage == message &&
-        mListeners[i].mWeakListener == weak) {
-      return NS_OK;
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (!listeners) {
+    listeners = new nsTArray<nsMessageListenerInfo>();
+    mListeners.Put(aMessage, listeners);
+  } else {
+    uint32_t len = listeners->Length();
+    for (uint32_t i = 0; i < len; ++i) {
+      if ((*listeners)[i].mWeakListener == weak) {
+        return NS_OK;
+      }
     }
   }
 
-  nsMessageListenerInfo* entry = mListeners.AppendElement();
-  entry->mMessage = message;
+  nsMessageListenerInfo* entry = listeners->AppendElement();
   entry->mWeakListener = weak;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessage,
                                                  nsIMessageListener* aListener)
 {
   nsWeakPtr weak = do_GetWeakReference(aListener);
   NS_ENSURE_TRUE(weak, NS_OK);
 
-  nsCOMPtr<nsIAtom> message = do_GetAtom(aMessage);
-  uint32_t len = mListeners.Length();
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (!listeners) {
+    return NS_OK;
+  }
+
+  uint32_t len = listeners->Length();
   for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mMessage == message &&
-        mListeners[i].mWeakListener == weak) {
-      mListeners.RemoveElementAt(i);
+    if ((*listeners)[i].mWeakListener == weak) {
+      listeners->RemoveElementAt(i);
       return NS_OK;
     }
   }
 
   return NS_OK;
 }
 
 // nsIFrameScriptLoader
@@ -787,143 +835,141 @@ nsresult
 nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
                                       const nsAString& aMessage,
                                       bool aIsSync,
                                       const StructuredCloneData* aCloneData,
                                       CpowHolder* aCpows,
                                       InfallibleTArray<nsString>* aJSONRetVal)
 {
   AutoSafeJSContext ctx;
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (listeners) {
 
-  if (mListeners.Length()) {
-    nsCOMPtr<nsIAtom> name = do_GetAtom(aMessage);
     MMListenerRemover lr(this);
 
-    for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    for (uint32_t i = 0; i < listeners->Length(); ++i) {
       // Remove mListeners[i] if it's an expired weak listener.
       nsCOMPtr<nsISupports> weakListener;
-      if (mListeners[i].mWeakListener) {
-        weakListener = do_QueryReferent(mListeners[i].mWeakListener);
+      if ((*listeners)[i].mWeakListener) {
+        weakListener = do_QueryReferent((*listeners)[i].mWeakListener);
         if (!weakListener) {
-          mListeners.RemoveElementAt(i--);
+          listeners->RemoveElementAt(i--);
           continue;
         }
       }
 
-      if (mListeners[i].mMessage == name) {
-        nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS;
-        if (weakListener) {
-          wrappedJS = do_QueryInterface(weakListener);
-        } else {
-          wrappedJS = do_QueryInterface(mListeners[i].mStrongListener);
-        }
+      nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS;
+      if (weakListener) {
+        wrappedJS = do_QueryInterface(weakListener);
+      } else {
+        wrappedJS = do_QueryInterface((*listeners)[i].mStrongListener);
+      }
+
+      if (!wrappedJS) {
+        continue;
+      }
+      JS::Rooted<JSObject*> object(ctx, wrappedJS->GetJSObject());
+      if (!object) {
+        continue;
+      }
+      JSAutoCompartment ac(ctx, object);
+
+      // The parameter for the listener function.
+      JS::Rooted<JSObject*> param(ctx,
+        JS_NewObject(ctx, nullptr, nullptr, nullptr));
+      NS_ENSURE_TRUE(param, NS_ERROR_OUT_OF_MEMORY);
 
-        if (!wrappedJS) {
-          continue;
+      JS::Rooted<JS::Value> targetv(ctx);
+      JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
+      nsContentUtils::WrapNative(ctx, global, aTarget, &targetv,
+                                 nullptr, true);
+
+      JS::RootedObject cpows(ctx);
+      if (aCpows) {
+        if (!aCpows->ToObject(ctx, &cpows)) {
+          return NS_ERROR_UNEXPECTED;
         }
-        JS::Rooted<JSObject*> object(ctx, wrappedJS->GetJSObject());
-        if (!object) {
-          continue;
+      }
+
+      if (!cpows) {
+        cpows = JS_NewObject(ctx, nullptr, nullptr, nullptr);
+        if (!cpows) {
+          return NS_ERROR_UNEXPECTED;
         }
-        JSAutoCompartment ac(ctx, object);
+      }
+
+      JS::RootedValue cpowsv(ctx, JS::ObjectValue(*cpows));
 
-        // The parameter for the listener function.
-        JS::Rooted<JSObject*> param(ctx,
-          JS_NewObject(ctx, nullptr, nullptr, nullptr));
-        NS_ENSURE_TRUE(param, NS_ERROR_OUT_OF_MEMORY);
+      JS::Rooted<JS::Value> json(ctx, JS::NullValue());
+      if (aCloneData && aCloneData->mDataLength &&
+          !ReadStructuredClone(ctx, *aCloneData, json.address())) {
+        JS_ClearPendingException(ctx);
+        return NS_OK;
+      }
+      JS::Rooted<JSString*> jsMessage(ctx,
+        JS_NewUCStringCopyN(ctx,
+                            static_cast<const jschar*>(aMessage.BeginReading()),
+                            aMessage.Length()));
+      NS_ENSURE_TRUE(jsMessage, NS_ERROR_OUT_OF_MEMORY);
+      JS_DefineProperty(ctx, param, "target", targetv, nullptr, nullptr, JSPROP_ENUMERATE);
+      JS_DefineProperty(ctx, param, "name",
+                        STRING_TO_JSVAL(jsMessage), nullptr, nullptr, JSPROP_ENUMERATE);
+      JS_DefineProperty(ctx, param, "sync",
+                        BOOLEAN_TO_JSVAL(aIsSync), nullptr, nullptr, JSPROP_ENUMERATE);
+      JS_DefineProperty(ctx, param, "json", json, nullptr, nullptr, JSPROP_ENUMERATE); // deprecated
+      JS_DefineProperty(ctx, param, "data", json, nullptr, nullptr, JSPROP_ENUMERATE);
+      JS_DefineProperty(ctx, param, "objects", cpowsv, nullptr, nullptr, JSPROP_ENUMERATE);
 
-        JS::Rooted<JS::Value> targetv(ctx);
-        JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
-        nsContentUtils::WrapNative(ctx, global, aTarget, &targetv,
-                                   nullptr, true);
+      JS::Rooted<JS::Value> thisValue(ctx, JS::UndefinedValue());
+
+      JS::Rooted<JS::Value> funval(ctx);
+      if (JS_ObjectIsCallable(ctx, object)) {
+        // If the listener is a JS function:
+        funval.setObject(*object);
 
-        JS::RootedObject cpows(ctx);
-        if (aCpows) {
-          if (!aCpows->ToObject(ctx, &cpows)) {
-            return NS_ERROR_UNEXPECTED;
-          }
+        // A small hack to get 'this' value right on content side where
+        // messageManager is wrapped in TabChildGlobal.
+        nsCOMPtr<nsISupports> defaultThisValue;
+        if (mChrome) {
+          defaultThisValue = do_QueryObject(this);
+        } else {
+          defaultThisValue = aTarget;
         }
+        JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
+        nsContentUtils::WrapNative(ctx, global, defaultThisValue,
+                                   &thisValue, nullptr, true);
+      } else {
+        // If the listener is a JS object which has receiveMessage function:
+        if (!JS_GetProperty(ctx, object, "receiveMessage", &funval) ||
+            !funval.isObject())
+          return NS_ERROR_UNEXPECTED;
 
-        if (!cpows) {
-          cpows = JS_NewObject(ctx, nullptr, nullptr, nullptr);
-          if (!cpows) {
-            return NS_ERROR_UNEXPECTED;
-          }
+        // Check if the object is even callable.
+        NS_ENSURE_STATE(JS_ObjectIsCallable(ctx, &funval.toObject()));
+        thisValue.setObject(*object);
+      }
+
+      JS::Rooted<JS::Value> rval(ctx, JSVAL_VOID);
+      JS::Rooted<JS::Value> argv(ctx, JS::ObjectValue(*param));
+
+      {
+        JS::Rooted<JSObject*> thisObject(ctx, thisValue.toObjectOrNull());
+
+        JSAutoCompartment tac(ctx, thisObject);
+        if (!JS_WrapValue(ctx, argv.address())) {
+          return NS_ERROR_UNEXPECTED;
         }
 
-        JS::RootedValue cpowsv(ctx, JS::ObjectValue(*cpows));
-
-        JS::Rooted<JS::Value> json(ctx, JS::NullValue());
-        if (aCloneData && aCloneData->mDataLength &&
-            !ReadStructuredClone(ctx, *aCloneData, json.address())) {
-          JS_ClearPendingException(ctx);
-          return NS_OK;
-        }
-        JS::Rooted<JSString*> jsMessage(ctx,
-          JS_NewUCStringCopyN(ctx,
-                              static_cast<const jschar*>(aMessage.BeginReading()),
-                              aMessage.Length()));
-        NS_ENSURE_TRUE(jsMessage, NS_ERROR_OUT_OF_MEMORY);
-        JS_DefineProperty(ctx, param, "target", targetv, nullptr, nullptr, JSPROP_ENUMERATE);
-        JS_DefineProperty(ctx, param, "name",
-                          STRING_TO_JSVAL(jsMessage), nullptr, nullptr, JSPROP_ENUMERATE);
-        JS_DefineProperty(ctx, param, "sync",
-                          BOOLEAN_TO_JSVAL(aIsSync), nullptr, nullptr, JSPROP_ENUMERATE);
-        JS_DefineProperty(ctx, param, "json", json, nullptr, nullptr, JSPROP_ENUMERATE); // deprecated
-        JS_DefineProperty(ctx, param, "data", json, nullptr, nullptr, JSPROP_ENUMERATE);
-        JS_DefineProperty(ctx, param, "objects", cpowsv, nullptr, nullptr, JSPROP_ENUMERATE);
-
-        JS::Rooted<JS::Value> thisValue(ctx, JS::UndefinedValue());
-
-        JS::Rooted<JS::Value> funval(ctx);
-        if (JS_ObjectIsCallable(ctx, object)) {
-          // If the listener is a JS function:
-          funval.setObject(*object);
-
-          // A small hack to get 'this' value right on content side where
-          // messageManager is wrapped in TabChildGlobal.
-          nsCOMPtr<nsISupports> defaultThisValue;
-          if (mChrome) {
-            defaultThisValue = do_QueryObject(this);
-          } else {
-            defaultThisValue = aTarget;
-          }
-          JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
-          nsContentUtils::WrapNative(ctx, global, defaultThisValue,
-                                     &thisValue, nullptr, true);
-        } else {
-          // If the listener is a JS object which has receiveMessage function:
-          if (!JS_GetProperty(ctx, object, "receiveMessage", &funval) ||
-              !funval.isObject())
-            return NS_ERROR_UNEXPECTED;
-
-          // Check if the object is even callable.
-          NS_ENSURE_STATE(JS_ObjectIsCallable(ctx, &funval.toObject()));
-          thisValue.setObject(*object);
-        }
-
-        JS::Rooted<JS::Value> rval(ctx, JSVAL_VOID);
-        JS::Rooted<JS::Value> argv(ctx, JS::ObjectValue(*param));
-
-        {
-          JS::Rooted<JSObject*> thisObject(ctx, thisValue.toObjectOrNull());
-
-          JSAutoCompartment tac(ctx, thisObject);
-          if (!JS_WrapValue(ctx, argv.address())) {
-            return NS_ERROR_UNEXPECTED;
-          }
-
-          JS_CallFunctionValue(ctx, thisObject,
-                               funval, 1, argv.address(), rval.address());
-          if (aJSONRetVal) {
-            nsString json;
-            if (JS_Stringify(ctx, &rval, JS::NullPtr(), JS::NullHandleValue,
-                             JSONCreator, &json)) {
-              aJSONRetVal->AppendElement(json);
-            }
+        JS_CallFunctionValue(ctx, thisObject,
+                             funval, 1, argv.address(), rval.address());
+        if (aJSONRetVal) {
+          nsString json;
+          if (JS_Stringify(ctx, &rval, JS::NullPtr(), JS::NullHandleValue,
+                           JSONCreator, &json)) {
+            aJSONRetVal->AppendElement(json);
           }
         }
       }
     }
   }
   nsRefPtr<nsFrameMessageManager> kungfuDeathGrip = mParentManager;
   return mParentManager ? mParentManager->ReceiveMessage(aTarget, aMessage,
                                                          aIsSync, aCloneData,
@@ -1006,77 +1052,97 @@ nsFrameMessageManager::Disconnect(bool a
   mOwnedCallback = nullptr;
   if (!mHandlingMessage) {
     mListeners.Clear();
   }
 }
 
 namespace {
 
-struct MessageManagerReferentCount {
-  MessageManagerReferentCount() : strong(0), weakAlive(0), weakDead(0) {}
-  size_t strong;
-  size_t weakAlive;
-  size_t weakDead;
-  nsCOMArray<nsIAtom> suspectMessages;
-  nsDataHashtable<nsPtrHashKey<nsIAtom>, uint32_t> messageCounter;
+struct MessageManagerReferentCount
+{
+  MessageManagerReferentCount() : mStrong(0), mWeakAlive(0), mWeakDead(0) {}
+  size_t mStrong;
+  size_t mWeakAlive;
+  size_t mWeakDead;
+  nsTArray<nsString> mSuspectMessages;
+  nsDataHashtable<nsStringHashKey, uint32_t> mMessageCounter;
 };
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace dom {
 
 class MessageManagerReporter MOZ_FINAL : public nsIMemoryReporter
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTER
+
+  static const size_t kSuspectReferentCount = 300;
 protected:
-  static const size_t kSuspectReferentCount = 300;
   void CountReferents(nsFrameMessageManager* aMessageManager,
                       MessageManagerReferentCount* aReferentCount);
 };
 
 NS_IMPL_ISUPPORTS1(MessageManagerReporter, nsIMemoryReporter)
 
-void
-MessageManagerReporter::CountReferents(nsFrameMessageManager* aMessageManager,
-                                       MessageManagerReferentCount* aReferentCount)
+static PLDHashOperator
+CollectMessageListenerData(const nsAString& aKey,
+                           nsTArray<nsMessageListenerInfo>* aListeners,
+                           void* aData)
 {
-  for (uint32_t i = 0; i < aMessageManager->mListeners.Length(); i++) {
-    const nsMessageListenerInfo& listenerInfo = aMessageManager->mListeners[i];
+  MessageManagerReferentCount* referentCount =
+    static_cast<MessageManagerReferentCount*>(aData);
+
+  uint32_t listenerCount = aListeners->Length();
+  if (!listenerCount) {
+    return PL_DHASH_NEXT;
+  }
 
+  nsString key(aKey);
+  uint32_t oldCount = 0;
+  referentCount->mMessageCounter.Get(key, &oldCount);
+  uint32_t currentCount = oldCount + listenerCount;
+  referentCount->mMessageCounter.Put(key, currentCount);
+
+  // Keep track of messages that have a suspiciously large
+  // number of referents (symptom of leak).
+  if (currentCount == MessageManagerReporter::kSuspectReferentCount) {
+    referentCount->mSuspectMessages.AppendElement(key);
+  }
+
+  for (uint32_t i = 0; i < listenerCount; ++i) {
+    const nsMessageListenerInfo& listenerInfo = (*aListeners)[i];
     if (listenerInfo.mWeakListener) {
       nsCOMPtr<nsISupports> referent =
         do_QueryReferent(listenerInfo.mWeakListener);
       if (referent) {
-        aReferentCount->weakAlive++;
+        referentCount->mWeakAlive++;
       } else {
-        aReferentCount->weakDead++;
+        referentCount->mWeakDead++;
       }
     } else {
-      aReferentCount->strong++;
-    }
-
-    uint32_t oldCount = 0;
-    aReferentCount->messageCounter.Get(listenerInfo.mMessage, &oldCount);
-    uint32_t currentCount = oldCount + 1;
-    aReferentCount->messageCounter.Put(listenerInfo.mMessage, currentCount);
-
-    // Keep track of messages that have a suspiciously large
-    // number of referents (symptom of leak).
-    if (currentCount == kSuspectReferentCount) {
-      aReferentCount->suspectMessages.AppendElement(listenerInfo.mMessage);
+      referentCount->mStrong++;
     }
   }
+  return PL_DHASH_NEXT;
+}
+
+void
+MessageManagerReporter::CountReferents(nsFrameMessageManager* aMessageManager,
+                                       MessageManagerReferentCount* aReferentCount)
+{
+  aMessageManager->mListeners.EnumerateRead(CollectMessageListenerData,
+                                            aReferentCount);
 
   // Add referent count in child managers because the listeners
   // participate in messages dispatched from parent message manager.
-  for (uint32_t i = 0; i < aMessageManager->mChildManagers.Length(); i++) {
+  for (uint32_t i = 0; i < aMessageManager->mChildManagers.Length(); ++i) {
     nsRefPtr<nsFrameMessageManager> mm =
       static_cast<nsFrameMessageManager*>(aMessageManager->mChildManagers[i]);
     CountReferents(mm, aReferentCount);
   }
 }
 
 NS_IMETHODIMP
 MessageManagerReporter::GetName(nsACString& aName)
@@ -1097,35 +1163,35 @@ ReportReferentCount(const char* aManager
       rv = aCb->Callback(EmptyCString(), _path,                               \
                          nsIMemoryReporter::KIND_OTHER,                       \
                          nsIMemoryReporter::UNITS_COUNT, _amount,             \
                          _desc, aClosure);                                    \
       NS_ENSURE_SUCCESS(rv, rv);                                              \
     } while (0)
 
   REPORT(nsPrintfCString("message-manager/referent/%s/strong", aManagerType),
-         aReferentCount.strong,
+         aReferentCount.mStrong,
          nsPrintfCString("The number of strong referents held by the message "
                          "manager in the %s manager.", aManagerType));
   REPORT(nsPrintfCString("message-manager/referent/%s/weak/alive", aManagerType),
-         aReferentCount.weakAlive,
+         aReferentCount.mWeakAlive,
          nsPrintfCString("The number of weak referents that are still alive "
                          "held by the message manager in the %s manager.",
                          aManagerType));
   REPORT(nsPrintfCString("message-manager/referent/%s/weak/dead", aManagerType),
-         aReferentCount.weakDead,
+         aReferentCount.mWeakDead,
          nsPrintfCString("The number of weak referents that are dead "
                          "held by the message manager in the %s manager.",
                          aManagerType));
 
-  for (uint32_t i = 0; i < aReferentCount.suspectMessages.Length(); i++) {
+  for (uint32_t i = 0; i < aReferentCount.mSuspectMessages.Length(); i++) {
     uint32_t totalReferentCount = 0;
-    aReferentCount.messageCounter.Get(aReferentCount.suspectMessages[i],
-                                      &totalReferentCount);
-    nsAtomCString suspect(aReferentCount.suspectMessages[i]);
+    aReferentCount.mMessageCounter.Get(aReferentCount.mSuspectMessages[i],
+                                       &totalReferentCount);
+    NS_ConvertUTF16toUTF8 suspect(aReferentCount.mSuspectMessages[i]);
     REPORT(nsPrintfCString("message-manager-suspect/%s/referent(message=%s)",
                            aManagerType, suspect.get()), totalReferentCount,
            nsPrintfCString("A message in the %s message manager with a "
                            "suspiciously large number of referents (symptom "
                            "of a leak).", aManagerType));
   }
 
 #undef REPORT
@@ -1706,22 +1772,32 @@ NS_NewChildProcessMessageManager(nsISync
   }
   nsFrameMessageManager* mm = new nsFrameMessageManager(cb,
                                                         nullptr,
                                                         MM_PROCESSMANAGER | MM_OWNSCALLBACK);
   nsFrameMessageManager::sChildProcessManager = mm;
   return CallQueryInterface(mm, aResult);
 }
 
+static PLDHashOperator
+CycleCollectorMarkListeners(const nsAString& aKey,
+                            nsTArray<nsMessageListenerInfo>* aListeners,
+                            void* aData)
+{
+  uint32_t count = aListeners->Length();
+  for (uint32_t i = 0; i < count; i++) {
+    if ((*aListeners)[i].mStrongListener) {
+      xpc_TryUnmarkWrappedGrayObject((*aListeners)[i].mStrongListener);
+    }
+  }
+  return PL_DHASH_NEXT;
+}
+
 bool
 nsFrameMessageManager::MarkForCC()
 {
-  uint32_t len = mListeners.Length();
-  for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mStrongListener) {
-      xpc_TryUnmarkWrappedGrayObject(mListeners[i].mStrongListener);
-    }
-  }
+  mListeners.EnumerateRead(CycleCollectorMarkListeners, nullptr);
+
   if (mRefCnt.IsPurple()) {
     mRefCnt.RemovePurple();
   }
   return true;
 }
--- a/content/base/src/nsFrameMessageManager.h
+++ b/content/base/src/nsFrameMessageManager.h
@@ -13,16 +13,17 @@
 #include "nsCOMArray.h"
 #include "nsTArray.h"
 #include "nsIAtom.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsTArray.h"
 #include "nsIPrincipal.h"
 #include "nsIXPConnect.h"
 #include "nsDataHashtable.h"
+#include "nsClassHashtable.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
 #include "nsThreadUtils.h"
 #include "nsWeakPtr.h"
 #include "mozilla/Attributes.h"
 #include "js/RootingAPI.h"
 
 namespace mozilla {
@@ -111,17 +112,16 @@ StructuredCloneData UnpackClonedMessageD
 
 class nsAXPCNativeCallContext;
 
 struct nsMessageListenerInfo
 {
   // Exactly one of mStrongListener and mWeakListener must be non-null.
   nsCOMPtr<nsIMessageListener> mStrongListener;
   nsWeakPtr mWeakListener;
-  nsCOMPtr<nsIAtom> mMessage;
 };
 
 class CpowHolder
 {
   public:
     virtual bool ToObject(JSContext* cx, JS::MutableHandleObject objp) = 0;
 };
 
@@ -263,17 +263,19 @@ private:
                        const JS::Value& aJSON,
                        const JS::Value& aObjects,
                        JSContext* aCx,
                        uint8_t aArgc,
                        JS::Value* aRetval,
                        bool aIsSync);
 protected:
   friend class MMListenerRemover;
-  nsTArray<nsMessageListenerInfo> mListeners;
+  // We keep the message listeners as arrays in a hastable indexed by the
+  // message name. That gives us fast lookups in ReceiveMessage().
+  nsClassHashtable<nsStringHashKey, nsTArray<nsMessageListenerInfo>> 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 mDisconnected;