Bug 1169129 - Make GMPService's GMP crash handlers easier to register. r=gerald, a=sledru
authorChris Pearce <cpearce@mozilla.com>
Fri, 05 Jun 2015 21:55:51 +1200
changeset 275154 adec40afb56318bf30f0a1b5b35f7ce166772fcf
parent 275153 78ac03fd7e4083c2ad6b6668a70a8f4467c50464
child 275155 5d77c8d4b3b5d78cd8e81f0b38fd704be3a1d6ee
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgerald, sledru
bugs1169129
milestone40.0a2
Bug 1169129 - Make GMPService's GMP crash handlers easier to register. r=gerald, a=sledru
dom/media/eme/MediaKeys.cpp
dom/media/gmp/GMPService.cpp
dom/media/gmp/GMPService.h
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -1,24 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 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/. */
 
 #include "mozilla/dom/MediaKeys.h"
 #include "GMPService.h"
-#include "mozilla/EventDispatcher.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/MediaKeysBinding.h"
 #include "mozilla/dom/MediaKeyMessageEvent.h"
 #include "mozilla/dom/MediaKeyError.h"
 #include "mozilla/dom/MediaKeySession.h"
 #include "mozilla/dom/DOMException.h"
-#include "mozilla/dom/PluginCrashedEvent.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/CDMProxy.h"
 #include "mozilla/EMEUtils.h"
 #include "nsContentUtils.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "mozilla/Preferences.h"
 #include "nsContentTypeParser.h"
 #ifdef MOZ_FMP4
@@ -378,93 +376,16 @@ MediaKeys::Init(ErrorResult& aRv)
   mProxy->Init(mCreatePromiseId,
                origin,
                topLevelOrigin,
                inPrivateBrowsing);
 
   return promise.forget();
 }
 
-class CrashHandler : public gmp::GeckoMediaPluginService::PluginCrashCallback
-{
-public:
-  CrashHandler(const uint32_t aPluginId,
-               nsPIDOMWindow* aParentWindow,
-               nsIDocument* aDocument)
-    : gmp::GeckoMediaPluginService::PluginCrashCallback(aPluginId)
-    , mPluginId(aPluginId)
-    , mParentWindowWeakPtr(do_GetWeakReference(aParentWindow))
-    , mDocumentWeakPtr(do_GetWeakReference(aDocument))
-  {
-  }
-
-  virtual void Run(const nsACString& aPluginName) override
-  {
-    PluginCrashedEventInit init;
-    init.mPluginID = mPluginId;
-    init.mBubbles = true;
-    init.mCancelable = true;
-    init.mGmpPlugin = true;
-    CopyUTF8toUTF16(aPluginName, init.mPluginName);
-    init.mSubmittedCrashReport = false;
-
-    // The following PluginCrashedEvent fields stay empty:
-    // init.mBrowserDumpID
-    // init.mPluginFilename
-    // TODO: Can/should we fill them?
-
-    nsCOMPtr<nsPIDOMWindow> parentWindow;
-    nsCOMPtr<nsIDocument> document;
-    if (!GetParentWindowAndDocumentIfValid(parentWindow, document)) {
-      return;
-    }
-
-    nsRefPtr<PluginCrashedEvent> event =
-      PluginCrashedEvent::Constructor(document, NS_LITERAL_STRING("PluginCrashed"), init);
-    event->SetTrusted(true);
-    event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
-
-    EventDispatcher::DispatchDOMEvent(parentWindow, nullptr, event, nullptr, nullptr);
-  }
-
-  virtual bool IsStillValid() override
-  {
-    nsCOMPtr<nsPIDOMWindow> parentWindow;
-    nsCOMPtr<nsIDocument> document;
-    return GetParentWindowAndDocumentIfValid(parentWindow, document);
-  }
-
-private:
-  virtual ~CrashHandler()
-  { }
-
-  bool
-  GetParentWindowAndDocumentIfValid(nsCOMPtr<nsPIDOMWindow>& parentWindow,
-                                    nsCOMPtr<nsIDocument>& document)
-  {
-    parentWindow = do_QueryReferent(mParentWindowWeakPtr);
-    if (!parentWindow) {
-      return false;
-    }
-    document = do_QueryReferent(mDocumentWeakPtr);
-    if (!document) {
-      return false;
-    }
-    nsCOMPtr<nsIDocument> parentWindowDocument = parentWindow->GetExtantDoc();
-    if (!parentWindowDocument || document.get() != parentWindowDocument.get()) {
-      return false;
-    }
-    return true;
-  }
-
-  uint32_t mPluginId;
-  nsWeakPtr mParentWindowWeakPtr;
-  nsWeakPtr mDocumentWeakPtr;
-};
-
 void
 MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const uint32_t aPluginId)
 {
   nsRefPtr<DetailedPromise> promise(RetrievePromise(aId));
   if (!promise) {
     return;
   }
   mNodeId = aNodeId;
@@ -484,21 +405,17 @@ MediaKeys::OnCDMCreated(PromiseId aId, c
     nsRefPtr<gmp::GeckoMediaPluginService> service =
       gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
     if (NS_WARN_IF(!service)) {
       return;
     }
     if (NS_WARN_IF(!mParent)) {
       return;
     }
-    nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
-    if (NS_WARN_IF(!doc)) {
-      return;
-    }
-    service->AddPluginCrashCallback(new CrashHandler(aPluginId, mParent, doc));
+    service->AddPluginCrashedEventTarget(aPluginId, mParent);
     EME_LOG("MediaKeys[%p]::OnCDMCreated() registered crash handler for pluginId '%i'",
             this, aPluginId);
   }
 }
 
 already_AddRefed<MediaKeySession>
 MediaKeys::CreateSession(JSContext* aCx,
                          SessionType aSessionType,
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -30,16 +30,19 @@
 #endif
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsHashKeys.h"
 #include "nsIFile.h"
 #include "nsISimpleEnumerator.h"
 
+#include "mozilla/dom/PluginCrashedEvent.h"
+#include "mozilla/EventDispatcher.h"
+
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 #ifdef PR_LOGGING
 PRLogModuleInfo*
@@ -158,65 +161,148 @@ GeckoMediaPluginService::~GeckoMediaPlug
 {
 }
 
 void
 GeckoMediaPluginService::RemoveObsoletePluginCrashCallbacks()
 {
   MOZ_ASSERT(NS_IsMainThread());
   for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
-    nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
+    nsRefPtr<GMPCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
     if (!callback->IsStillValid()) {
       LOGD(("%s::%s - Removing obsolete callback for pluginId %i",
-            __CLASS__, __FUNCTION__, callback->PluginId()));
+            __CLASS__, __FUNCTION__, callback->GetPluginId()));
       mPluginCrashCallbacks.RemoveElementAt(i - 1);
     }
   }
 }
 
-void
-GeckoMediaPluginService::AddPluginCrashCallback(
-  nsRefPtr<PluginCrashCallback> aPluginCrashCallback)
+GeckoMediaPluginService::GMPCrashCallback::GMPCrashCallback(const uint32_t aPluginId,
+                                                            nsPIDOMWindow* aParentWindow,
+                                                            nsIDocument* aDocument)
+  : mPluginId(aPluginId)
+  , mParentWindowWeakPtr(do_GetWeakReference(aParentWindow))
+  , mDocumentWeakPtr(do_GetWeakReference(aDocument))
 {
-  RemoveObsoletePluginCrashCallbacks();
-  mPluginCrashCallbacks.AppendElement(aPluginCrashCallback);
+  MOZ_ASSERT(NS_IsMainThread());
 }
 
 void
-GeckoMediaPluginService::RemovePluginCrashCallbacks(const uint32_t aPluginId)
+GeckoMediaPluginService::GMPCrashCallback::Run(const nsACString& aPluginName)
+{
+  dom::PluginCrashedEventInit init;
+  init.mPluginID = mPluginId;
+  init.mBubbles = true;
+  init.mCancelable = true;
+  init.mGmpPlugin = true;
+  CopyUTF8toUTF16(aPluginName, init.mPluginName);
+  init.mSubmittedCrashReport = false;
+
+  // The following PluginCrashedEvent fields stay empty:
+  // init.mBrowserDumpID
+  // init.mPluginFilename
+  // TODO: Can/should we fill them?
+
+  nsCOMPtr<nsPIDOMWindow> parentWindow;
+  nsCOMPtr<nsIDocument> document;
+  if (!GetParentWindowAndDocumentIfValid(parentWindow, document)) {
+    return;
+  }
+
+  nsRefPtr<dom::PluginCrashedEvent> event =
+    dom::PluginCrashedEvent::Constructor(document, NS_LITERAL_STRING("PluginCrashed"), init);
+  event->SetTrusted(true);
+  event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
+
+  EventDispatcher::DispatchDOMEvent(parentWindow, nullptr, event, nullptr, nullptr);
+}
+
+bool
+GeckoMediaPluginService::GMPCrashCallback::IsStillValid()
 {
+  nsCOMPtr<nsPIDOMWindow> parentWindow;
+  nsCOMPtr<nsIDocument> document;
+  return GetParentWindowAndDocumentIfValid(parentWindow, document);
+}
+
+bool
+GeckoMediaPluginService::GMPCrashCallback::GetParentWindowAndDocumentIfValid(
+  nsCOMPtr<nsPIDOMWindow>& parentWindow,
+  nsCOMPtr<nsIDocument>& document)
+{
+  parentWindow = do_QueryReferent(mParentWindowWeakPtr);
+  if (!parentWindow) {
+    return false;
+  }
+  document = do_QueryReferent(mDocumentWeakPtr);
+  if (!document) {
+    return false;
+  }
+  nsCOMPtr<nsIDocument> parentWindowDocument = parentWindow->GetExtantDoc();
+  if (!parentWindowDocument || document.get() != parentWindowDocument.get()) {
+    return false;
+  }
+  return true;
+}
+
+void
+GeckoMediaPluginService::AddPluginCrashedEventTarget(const uint32_t aPluginId,
+                                                     nsPIDOMWindow* aParentWindow)
+{
+  LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
+
+  if (NS_WARN_IF(!aParentWindow)) {
+    return;
+  }
+  nsCOMPtr<nsIDocument> doc = aParentWindow->GetExtantDoc();
+  if (NS_WARN_IF(!doc)) {
+    return;
+  }
+  nsRefPtr<GMPCrashCallback> callback(new GMPCrashCallback(aPluginId, aParentWindow, doc));
   RemoveObsoletePluginCrashCallbacks();
-  for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
-    nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
-    if (callback->PluginId() == aPluginId) {
-      mPluginCrashCallbacks.RemoveElementAt(i - 1);
+
+  // If the plugin with that ID has already crashed without being handled,
+  // just run the handler now.
+  for (size_t i = mPluginCrashes.Length(); i != 0; --i) {
+    size_t index = i - 1;
+    const PluginCrash& crash = mPluginCrashes[index];
+    if (crash.mPluginId == aPluginId) {
+      LOGD(("%s::%s(%i) - added crash handler for crashed plugin, running handler #%u",
+        __CLASS__, __FUNCTION__, aPluginId, index));
+      callback->Run(crash.mPluginName);
+      mPluginCrashes.RemoveElementAt(index);
     }
   }
+
+  // Remember crash, so if a handler is added for it later, we report the
+  // crash to that window too.
+  mPluginCrashCallbacks.AppendElement(callback);
 }
 
 void
 GeckoMediaPluginService::RunPluginCrashCallbacks(const uint32_t aPluginId,
                                                  const nsACString& aPluginName)
 {
   MOZ_ASSERT(NS_IsMainThread());
   LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
+  RemoveObsoletePluginCrashCallbacks();
+
   for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
-    nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
-    const uint32_t callbackPluginId = callback->PluginId();
-    if (!callback->IsStillValid()) {
-      LOGD(("%s::%s(%i) - Removing obsolete callback for pluginId %i",
-            __CLASS__, __FUNCTION__, aPluginId, callback->PluginId()));
-      mPluginCrashCallbacks.RemoveElementAt(i - 1);
-    } else if (callbackPluginId == aPluginId) {
+    nsRefPtr<GMPCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
+    if (callback->GetPluginId() == aPluginId) {
       LOGD(("%s::%s(%i) - Running #%u",
           __CLASS__, __FUNCTION__, aPluginId, i - 1));
       callback->Run(aPluginName);
       mPluginCrashCallbacks.RemoveElementAt(i - 1);
     }
   }
+  mPluginCrashes.AppendElement(PluginCrash(aPluginId, aPluginName));
+  if (mPluginCrashes.Length() > MAX_PLUGIN_CRASHES) {
+    mPluginCrashes.RemoveElementAt(0);
+  }
 }
 
 nsresult
 GeckoMediaPluginService::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
--- a/dom/media/gmp/GMPService.h
+++ b/dom/media/gmp/GMPService.h
@@ -10,16 +10,19 @@
 #include "mozIGeckoMediaPluginService.h"
 #include "nsIObserver.h"
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Monitor.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIThread.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocument.h"
+#include "nsIWeakReference.h"
 
 template <class> struct already_AddRefed;
 
 namespace mozilla {
 
 extern PRLogModuleInfo* GetGMPLog();
 
 namespace gmp {
@@ -56,62 +59,86 @@ public:
     override;
   NS_IMETHOD GetGMPDecryptor(nsTArray<nsCString>* aTags,
                              const nsACString& aNodeId,
                              UniquePtr<GetGMPDecryptorCallback>&& aCallback)
     override;
 
   int32_t AsyncShutdownTimeoutMs();
 
-  class PluginCrashCallback
-  {
-  public:
-    NS_INLINE_DECL_REFCOUNTING(PluginCrashCallback)
-
-    PluginCrashCallback(const uint32_t aPluginId)
-      : mPluginId(aPluginId)
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-    }
-    const uint32_t PluginId() const { return mPluginId; }
-    virtual void Run(const nsACString& aPluginName) = 0;
-    virtual bool IsStillValid() = 0; // False if callback has become useless.
-  protected:
-    virtual ~PluginCrashCallback()
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-    }
-  private:
-    const uint32_t mPluginId;
-  };
-  void RemoveObsoletePluginCrashCallbacks(); // Called from add/remove/run.
-  void AddPluginCrashCallback(nsRefPtr<PluginCrashCallback> aPluginCrashCallback);
-  void RemovePluginCrashCallbacks(const uint32_t aPluginId);
   void RunPluginCrashCallbacks(const uint32_t aPluginId,
                                const nsACString& aPluginName);
 
+  // Sets the window to which 'PluginCrashed' chromeonly event is dispatched.
+  // Note: if the plugin has crashed before the target window has been set,
+  // the 'PluginCrashed' event is dispatched as soon as a target window is set.
+  void AddPluginCrashedEventTarget(const uint32_t aPluginId,
+                                   nsPIDOMWindow* aParentWindow);
+
 protected:
   GeckoMediaPluginService();
   virtual ~GeckoMediaPluginService();
 
+  void RemoveObsoletePluginCrashCallbacks(); // Called from add/run.
+
   virtual void InitializePlugins() = 0;
   virtual bool GetContentParentFrom(const nsACString& aNodeId,
                                     const nsCString& aAPI,
                                     const nsTArray<nsCString>& aTags,
                                     UniquePtr<GetGMPContentParentCallback>&& aCallback) = 0;
 
   nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL);
   void ShutdownGMPThread();
 
-protected:
   Mutex mMutex; // Protects mGMPThread and mGMPThreadShutdown and some members
                 // in derived classes.
   nsCOMPtr<nsIThread> mGMPThread;
   bool mGMPThreadShutdown;
   bool mShuttingDownOnGMPThread;
 
-  nsTArray<nsRefPtr<PluginCrashCallback>> mPluginCrashCallbacks;
+  class GMPCrashCallback
+  {
+  public:
+    NS_INLINE_DECL_REFCOUNTING(GMPCrashCallback)
+
+    GMPCrashCallback(const uint32_t aPluginId,
+                     nsPIDOMWindow* aParentWindow,
+                     nsIDocument* aDocument);
+    void Run(const nsACString& aPluginName);
+    bool IsStillValid();
+    const uint32_t GetPluginId() const { return mPluginId; }
+  private:
+    virtual ~GMPCrashCallback() { MOZ_ASSERT(NS_IsMainThread()); }
+
+    bool GetParentWindowAndDocumentIfValid(nsCOMPtr<nsPIDOMWindow>& parentWindow,
+                                           nsCOMPtr<nsIDocument>& document);
+    const uint32_t mPluginId;
+    nsWeakPtr mParentWindowWeakPtr;
+    nsWeakPtr mDocumentWeakPtr;
+  };
+
+  struct PluginCrash
+  {
+    PluginCrash(uint32_t aPluginId,
+                const nsACString& aPluginName)
+      : mPluginId(aPluginId)
+      , mPluginName(aPluginName)
+    {
+    }
+    uint32_t mPluginId;
+    nsCString mPluginName;
+
+    bool operator==(const PluginCrash& aOther) const {
+      return mPluginId == aOther.mPluginId &&
+             mPluginName == aOther.mPluginName;
+    }
+  };
+
+  static const size_t MAX_PLUGIN_CRASHES = 100;
+  nsTArray<PluginCrash> mPluginCrashes;
+
+  nsTArray<nsRefPtr<GMPCrashCallback>> mPluginCrashCallbacks;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPService_h_