Bug 1111160 - Dispatch observer service notifications when content succeeds or fails to get CDM access. r=bz
authorChris Pearce <cpearce@mozilla.com>
Sat, 14 Feb 2015 08:52:42 +1300
changeset 256274 713722c64bd3cf8990e5fc8ecb81a14a3b9ffe3c
parent 256273 32fe8089d155fceebe20d73d9d128ad9640d3c35
child 256275 e590c2f29859eac36af00bc93ddd8b33018da7ad
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1111160
milestone38.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 1111160 - Dispatch observer service notifications when content succeeds or fails to get CDM access. r=bz
dom/base/Navigator.cpp
dom/media/eme/MediaKeyStatusMap.cpp
dom/media/eme/MediaKeySystemAccess.cpp
dom/media/eme/MediaKeySystemAccess.h
dom/media/eme/MediaKeys.cpp
dom/media/fmp4/MP4Reader.cpp
dom/media/gmp/GMPService.cpp
dom/webidl/MediaKeysRequestStatus.webidl
dom/webidl/moz.build
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -2604,27 +2604,38 @@ Navigator::RequestMediaKeySystemAccess(c
 {
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
   nsRefPtr<Promise> p = Promise::Create(go, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   if (!Preferences::GetBool("media.eme.enabled", false)) {
+    // EME disabled by user, send notification to chrome so UI can
+    // inform user.
+    MediaKeySystemAccess::NotifyObservers(aKeySystem,
+                                          MediaKeySystemStatus::Api_disabled);
     p->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return p.forget();
   }
 
   if (aKeySystem.IsEmpty() ||
       (aOptions.WasPassed() && aOptions.Value().IsEmpty())) {
     p->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     return p.forget();
   }
 
-  if (!MediaKeySystemAccess::IsKeySystemSupported(aKeySystem)) {
+  MediaKeySystemStatus status = MediaKeySystemAccess::GetKeySystemStatus(aKeySystem);
+  if (status != MediaKeySystemStatus::Available) {
+    if (status != MediaKeySystemStatus::Error) {
+      // Failed due to user disabling something, send a notification to
+      // chrome, so we can show some UI to explain how the user can rectify
+      // the situation.
+      MediaKeySystemAccess::NotifyObservers(aKeySystem, status);
+    }
     p->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return p.forget();
   }
 
   // TODO: Wait (async) until the CDM is downloaded, if it's not already.
 
   if (!aOptions.WasPassed() ||
       MediaKeySystemAccess::IsSupported(aKeySystem, aOptions.Value())) {
--- a/dom/media/eme/MediaKeyStatusMap.cpp
+++ b/dom/media/eme/MediaKeyStatusMap.cpp
@@ -1,16 +1,16 @@
 /* -*- 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/MediaKeyStatusMap.h"
 #include "nsPIDOMWindow.h"
-#include "mozilla/dom/MediaKeyStatusMap.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/dom/ToJSValue.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeyStatusMap)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeyStatusMap)
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -13,16 +13,18 @@
 #endif
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
 #endif
 #include "nsContentCID.h"
 #include "nsServiceManagerUtils.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "VideoUtils.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
@@ -85,45 +87,58 @@ HaveGMPFor(mozIGeckoMediaPluginService* 
                                              &tags,
                                              &hasPlugin))) {
     return false;
   }
   return hasPlugin;
 }
 
 /* static */
-bool
-MediaKeySystemAccess::IsKeySystemSupported(const nsAString& aKeySystem)
+MediaKeySystemStatus
+MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem)
 {
+  MOZ_ASSERT(Preferences::GetBool("media.eme.enabled", false));
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (NS_WARN_IF(!mps)) {
-    return false;
+    return MediaKeySystemStatus::Error;
   }
 
-  if (aKeySystem.EqualsLiteral("org.w3.clearkey") &&
-      HaveGMPFor(mps,
-                 NS_LITERAL_CSTRING("org.w3.clearkey"),
-                 NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
-    return true;
+  if (aKeySystem.EqualsLiteral("org.w3.clearkey")) {
+    if (!Preferences::GetBool("media.eme.clearkey.enabled", true)) {
+      return MediaKeySystemStatus::Cdm_disabled;
+    }
+    if (!HaveGMPFor(mps,
+                    NS_LITERAL_CSTRING("org.w3.clearkey"),
+                    NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
+      return MediaKeySystemStatus::Cdm_not_installed;
+    }
+    return MediaKeySystemStatus::Available;
   }
 
 #ifdef XP_WIN
   if ((aKeySystem.EqualsLiteral("com.adobe.access") ||
-       aKeySystem.EqualsLiteral("com.adobe.primetime")) &&
-      Preferences::GetBool("media.eme.adobe-access.enabled", false) &&
-      IsVistaOrLater() && // Win Vista and later only.
-      HaveGMPFor(mps,
-                 NS_ConvertUTF16toUTF8(aKeySystem),
-                 NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
-      return true;
+       aKeySystem.EqualsLiteral("com.adobe.primetime"))) {
+    // Win Vista and later only.
+    if (!IsVistaOrLater()) {
+      return MediaKeySystemStatus::Cdm_not_supported;
+    }
+    if (!Preferences::GetBool("media.eme.adobe-access.enabled", false)) {
+      return MediaKeySystemStatus::Cdm_disabled;
+    }
+    if (!HaveGMPFor(mps,
+                    NS_ConvertUTF16toUTF8(aKeySystem),
+                    NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
+      return MediaKeySystemStatus::Cdm_not_installed;
+    }
+    return MediaKeySystemStatus::Available;
   }
 #endif
 
-  return false;
+  return MediaKeySystemStatus::Cdm_not_supported;
 }
 
 static bool
 IsPlayableWithGMP(mozIGeckoMediaPluginService* aGMPS,
                   const nsAString& aKeySystem,
                   const nsAString& aContentType)
 {
 #ifdef MOZ_FMP4
@@ -204,10 +219,26 @@ MediaKeySystemAccess::IsSupported(const 
     // and on for specific GMPs/CDMs, so we don't check the uniqueidentifier
     // and stateful attributes here.
 
     return true;
   }
   return false;
 }
 
+/* static */
+void
+MediaKeySystemAccess::NotifyObservers(const nsAString& aKeySystem,
+                                      MediaKeySystemStatus aStatus)
+{
+  RequestMediaKeySystemAccessNotification data;
+  data.mKeySystem = aKeySystem;
+  data.mStatus = aStatus;
+  nsAutoString json;
+  data.ToJSON(json);
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "mediakeys-request", json.get());
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/eme/MediaKeySystemAccess.h
+++ b/dom/media/eme/MediaKeySystemAccess.h
@@ -9,16 +9,18 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozilla/dom/MediaKeysRequestStatusBinding.h"
+
 #include "js/TypeDecls.h"
 
 namespace mozilla {
 namespace dom {
 
 class MediaKeySystemAccess MOZ_FINAL : public nsISupports,
                                        public nsWrapperCache
 {
@@ -37,21 +39,24 @@ public:
   nsPIDOMWindow* GetParentObject() const;
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   void GetKeySystem(nsString& aRetVal) const;
 
   already_AddRefed<Promise> CreateMediaKeys(ErrorResult& aRv);
 
-  static bool IsKeySystemSupported(const nsAString& aKeySystem);
+  static MediaKeySystemStatus GetKeySystemStatus(const nsAString& aKeySystem);
 
   static bool IsSupported(const nsAString& aKeySystem,
                           const Sequence<MediaKeySystemOptions>& aOptions);
 
+  static void NotifyObservers(const nsAString& aKeySystem,
+                              MediaKeySystemStatus aStatus);
+
 private:
   nsCOMPtr<nsPIDOMWindow> mParent;
   const nsString mKeySystem;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -22,16 +22,17 @@
 #include "MP4Decoder.h"
 #endif
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
 #endif
 #include "nsContentCID.h"
 #include "nsServiceManagerUtils.h"
 #include "mozIGeckoMediaPluginService.h"
+#include "mozilla/dom/MediaKeySystemAccess.h"
 
 namespace mozilla {
 
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeys,
                                       mElement,
                                       mParent,
@@ -360,16 +361,19 @@ MediaKeys::OnCDMCreated(PromiseId aId, c
     return;
   }
   mNodeId = aNodeId;
   nsRefPtr<MediaKeys> keys(this);
   promise->MaybeResolve(keys);
   if (mCreatePromiseId == aId) {
     Release();
   }
+
+  MediaKeySystemAccess::NotifyObservers(mKeySystem,
+                                        MediaKeySystemStatus::Cdm_created);
 }
 
 already_AddRefed<MediaKeySession>
 MediaKeys::CreateSession(JSContext* aCx,
                          SessionType aSessionType,
                          ErrorResult& aRv)
 {
   nsRefPtr<MediaKeySession> session = new MediaKeySession(aCx,
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -400,24 +400,22 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo
   } else if (mPlatform && !IsWaitingMediaResources()) {
     *aInfo = mInfo;
     *aTags = nullptr;
     return NS_OK;
   }
 
   if (mDemuxer->Crypto().valid) {
 #ifdef MOZ_EME
-    if (!sIsEMEEnabled) {
-      // TODO: Need to signal DRM/EME required somehow...
-      return NS_ERROR_FAILURE;
-    }
-
     // We have encrypted audio or video. We'll need a CDM to decrypt and
     // possibly decode this. Wait until we've received a CDM from the
-    // JavaScript player app.
+    // JavaScript player app. Note: we still go through the motions here
+    // even if EME is disabled, so that if script tries and fails to create
+    // a CDM, we can detect that and notify chrome and show some UI explaining
+    // that we failed due to EME being disabled.
     nsRefPtr<CDMProxy> proxy;
     nsTArray<uint8_t> initData;
     ExtractCryptoInitData(initData);
     if (initData.Length() == 0) {
       return NS_ERROR_FAILURE;
     }
     if (!mInitDataEncountered.Contains(initData)) {
       mInitDataEncountered.AppendElement(initData);
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -177,16 +177,20 @@ GeckoMediaPluginService::Init()
 
   nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
   MOZ_ASSERT(obsService);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "profile-change-teardown", false)));
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)));
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "last-pb-context-exited", false)));
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "browser:purge-session-history", false)));
 
+#ifdef DEBUG
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "mediakeys-request", false)));
+#endif
+
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs) {
     prefs->AddObserver("media.gmp.plugin.crash", this, false);
   }
 
 #ifndef MOZ_WIDGET_GONK
   // Directory service is main thread only, so cache the profile dir here
   // so that we can use it off main thread.
@@ -213,17 +217,18 @@ GeckoMediaPluginService::Init()
   return GetThread(getter_AddRefs(thread));
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::Observe(nsISupports* aSubject,
                                  const char* aTopic,
                                  const char16_t* aSomeData)
 {
-  LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, aTopic));
+  LOGD(("%s::%s topic='%s' data='%s'", __CLASS__, __FUNCTION__,
+       aTopic, NS_ConvertUTF16toUTF8(aSomeData).get()));
   if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
     nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
     if (branch) {
       bool crashNow = false;
       if (NS_LITERAL_STRING("media.gmp.plugin.crash").Equals(aSomeData)) {
         branch->GetBoolPref("media.gmp.plugin.crash",  &crashNow);
       }
       if (crashNow) {
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaKeysRequestStatus.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+enum MediaKeySystemStatus {
+  "available",
+  "api-disabled",
+  "cdm-disabled",
+  "cdm-not-supported",
+  "cdm-not-installed",
+  "cdm-created",
+  "error"
+};
+
+dictionary RequestMediaKeySystemAccessNotification {
+  required DOMString keySystem;
+  required MediaKeySystemStatus status;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -809,16 +809,17 @@ if CONFIG['MOZ_BUILD_APP'] in ['browser'
 
 if CONFIG['MOZ_EME']:
     WEBIDL_FILES += [
         'MediaEncryptedEvent.webidl',
         'MediaKeyError.webidl',
         'MediaKeyMessageEvent.webidl',
         'MediaKeys.webidl',
         'MediaKeySession.webidl',
+        'MediaKeysRequestStatus.webidl',
         'MediaKeyStatusMap.webidl',
         'MediaKeySystemAccess.webidl',
     ]
 
 if CONFIG['MOZ_PAY']:
     WEBIDL_FILES += [
         'MozPaymentProvider.webidl'
     ]