Bug 1060179 - Generate a random node id for every EME (origin,topLevelOrigin) pair. r=bz
☠☠ backed out by c2d1a6487731 ☠ ☠
authorChris Pearce <cpearce@mozilla.com>
Thu, 09 Oct 2014 17:01:02 +1300
changeset 209520 48b65dba63876d4cbdb4149126233604fdf94519
parent 209519 e84a9df80ec5e3704ad8673d22b6934be306e975
child 209521 e7545921e875391d0942980d634668fc5ca38ef3
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbz
bugs1060179
milestone35.0a1
Bug 1060179 - Generate a random node id for every EME (origin,topLevelOrigin) pair. r=bz
content/html/content/public/HTMLMediaElement.h
content/html/content/src/HTMLMediaElement.cpp
content/media/eme/CDMProxy.cpp
content/media/eme/CDMProxy.h
content/media/eme/MediaKeys.cpp
content/media/eme/MediaKeys.h
content/media/gmp/GMPService.cpp
content/media/gmp/GMPStorageParent.cpp
content/media/gmp/mozIGeckoMediaPluginService.idl
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -540,16 +540,21 @@ public:
   mozilla::dom::EventHandlerNonNull* GetOnencrypted();
   void SetOnencrypted(mozilla::dom::EventHandlerNonNull* listener);
 
   void DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
                          const nsAString& aInitDataType);
 
 
   bool IsEventAttributeName(nsIAtom* aName) MOZ_OVERRIDE;
+
+  // Returns the principal of the "top level" document; the origin displayed
+  // in the URL bar of the browser window.
+  already_AddRefed<nsIPrincipal> GetTopLevelPrincipal();
+
 #endif // MOZ_EME
 
   bool MozAutoplayEnabled() const
   {
     return mAutoplayEnabled;
   }
 
   already_AddRefed<DOMMediaStream> MozCaptureStream(ErrorResult& aRv);
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -3402,16 +3402,21 @@ void HTMLMediaElement::NotifyDecoderPrin
   mDecoder->UpdateSameOriginStatus(
     !principal ||
     (NS_SUCCEEDED(NodePrincipal()->Subsumes(principal, &subsumes)) && subsumes));
 
   for (uint32_t i = 0; i < mOutputStreams.Length(); ++i) {
     OutputMediaStream* ms = &mOutputStreams[i];
     ms->mStream->CombineWithPrincipal(principal);
   }
+#ifdef MOZ_EME
+  if (mMediaKeys && NS_FAILED(mMediaKeys->CheckPrincipals())) {
+    mMediaKeys->Shutdown();
+  }
+#endif
 }
 
 void HTMLMediaElement::UpdateMediaSize(nsIntSize size)
 {
   mMediaSize = size;
 }
 
 void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendEvents)
@@ -4003,26 +4008,44 @@ HTMLMediaElement::SetMediaKeys(mozilla::
                                ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global =
     do_QueryInterface(OwnerDoc()->GetInnerWindow());
   if (!global) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
-  // TODO: Need to shutdown existing MediaKeys instance? bug 1016709.
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
-  if (mMediaKeys != aMediaKeys) {
-    mMediaKeys = aMediaKeys;
-  }
-  if (mDecoder) {
-    mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
+  if (mMediaKeys == aMediaKeys) {
+    promise->MaybeResolve(JS::UndefinedHandleValue);
+    return promise.forget();
+  }
+  if (aMediaKeys && aMediaKeys->IsBoundToMediaElement()) {
+    promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
+    return promise.forget();
+  }
+  if (mMediaKeys) {
+    // Existing MediaKeys object. Shut it down.
+    mMediaKeys->Shutdown();
+    mMediaKeys = nullptr;
+  }
+  
+  mMediaKeys = aMediaKeys;
+  if (mMediaKeys) {
+    if (NS_FAILED(mMediaKeys->Bind(this))) {
+      promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+      mMediaKeys = nullptr;
+      return promise.forget();
+    }
+    if (mDecoder) {
+      mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
+    }
   }
   promise->MaybeResolve(JS::UndefinedHandleValue);
   return promise.forget();
 }
 
 MediaWaitingFor
 HTMLMediaElement::WaitingFor() const
 {
@@ -4058,16 +4081,38 @@ HTMLMediaElement::DispatchEncrypted(cons
 }
 
 bool
 HTMLMediaElement::IsEventAttributeName(nsIAtom* aName)
 {
   return aName == nsGkAtoms::onencrypted ||
          nsGenericHTMLElement::IsEventAttributeName(aName);
 }
+
+already_AddRefed<nsIPrincipal>
+HTMLMediaElement::GetTopLevelPrincipal()
+{
+  nsRefPtr<nsIPrincipal> principal;
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(OwnerDoc()->GetParentObject());
+  nsCOMPtr<nsIDOMWindow> topWindow;
+  if (!window) {
+    return nullptr;
+  }
+  window->GetTop(getter_AddRefs(topWindow));
+  nsCOMPtr<nsPIDOMWindow> top = do_QueryInterface(topWindow);
+  if (!top) {
+    return nullptr;
+  }
+  nsIDocument* doc = top->GetExtantDoc();
+  if (!doc) {
+    return nullptr;
+  }
+  principal = doc->NodePrincipal();
+  return principal.forget();
+}
 #endif // MOZ_EME
 
 NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged()
 {
   SetVolumeInternal();
   return NS_OK;
 }
 
--- a/content/media/eme/CDMProxy.cpp
+++ b/content/media/eme/CDMProxy.cpp
@@ -31,87 +31,115 @@ CDMProxy::CDMProxy(dom::MediaKeys* aKeys
 }
 
 CDMProxy::~CDMProxy()
 {
   MOZ_COUNT_DTOR(CDMProxy);
 }
 
 void
-CDMProxy::Init(PromiseId aPromiseId)
+CDMProxy::Init(PromiseId aPromiseId,
+               const nsAString& aOrigin,
+               const nsAString& aTopLevelOrigin,
+               bool aInPrivateBrowsing)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
 
-  mNodeId = mKeys->GetNodeId();
-  if (mNodeId.IsEmpty()) {
-    RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  EME_LOG("Creating CDMProxy for origin='%s'", GetNodeId().get());
+  EME_LOG("CDMProxy::Init (%s, %s) %s",
+          NS_ConvertUTF16toUTF8(aOrigin).get(),
+          NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+          (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
 
   if (!mGMPThread) {
     nsCOMPtr<mozIGeckoMediaPluginService> mps =
       do_GetService("@mozilla.org/gecko-media-plugin-service;1");
     if (!mps) {
       RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
       return;
     }
     mps->GetThread(getter_AddRefs(mGMPThread));
     if (!mGMPThread) {
       RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
       return;
     }
   }
-
-  nsRefPtr<nsIRunnable> task(NS_NewRunnableMethodWithArg<uint32_t>(this, &CDMProxy::gmp_Init, aPromiseId));
+  nsAutoPtr<InitData> data(new InitData());
+  data->mPromiseId = aPromiseId;
+  data->mOrigin = aOrigin;
+  data->mTopLevelOrigin = aTopLevelOrigin;
+  data->mInPrivateBrowsing = aInPrivateBrowsing;
+  nsRefPtr<nsIRunnable> task(
+    NS_NewRunnableMethodWithArg<nsAutoPtr<InitData>>(this,
+                                                     &CDMProxy::gmp_Init,
+                                                     data));
   mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
 #ifdef DEBUG
 bool
 CDMProxy::IsOnGMPThread()
 {
   return NS_GetCurrentThread() == mGMPThread;
 }
 #endif
 
 void
-CDMProxy::gmp_Init(uint32_t aPromiseId)
+CDMProxy::gmp_Init(nsAutoPtr<InitData> aData)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (!mps) {
-    RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
+    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
+  nsresult rv = mps->GetNodeId(aData->mOrigin,
+                               aData->mTopLevelOrigin,
+                               aData->mInPrivateBrowsing,
+                               mNodeId);
+  MOZ_ASSERT(!GetNodeId().IsEmpty());
+  if (NS_FAILED(rv)) {
+    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  EME_LOG("CDMProxy::gmp_Init (%s, %s) %s NodeId=%s",
+          NS_ConvertUTF16toUTF8(aData->mOrigin).get(),
+          NS_ConvertUTF16toUTF8(aData->mTopLevelOrigin).get(),
+          (aData->mInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"),
+          GetNodeId().get());
+
   nsTArray<nsCString> tags;
   tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
-  nsresult rv = mps->GetGMPDecryptor(&tags, GetNodeId(), &mCDM);
+  rv = mps->GetGMPDecryptor(&tags, GetNodeId(), &mCDM);
   if (NS_FAILED(rv) || !mCDM) {
-    RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
+    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
   } else {
     mCallback = new CDMCallbackProxy(this);
     mCDM->Init(mCallback);
-    nsRefPtr<nsIRunnable> task(NS_NewRunnableMethodWithArg<uint32_t>(this, &CDMProxy::OnCDMCreated, aPromiseId));
+    nsRefPtr<nsIRunnable> task(
+      NS_NewRunnableMethodWithArg<uint32_t>(this,
+                                            &CDMProxy::OnCDMCreated,
+                                            aData->mPromiseId));
     NS_DispatchToMainThread(task);
   }
 }
 
 void
 CDMProxy::OnCDMCreated(uint32_t aPromiseId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
     return;
   }
-  mKeys->OnCDMCreated(aPromiseId);
+  MOZ_ASSERT(!GetNodeId().IsEmpty());
+  mKeys->OnCDMCreated(aPromiseId, GetNodeId());
 }
 
 void
 CDMProxy::CreateSession(dom::SessionType aSessionType,
                         PromiseId aPromiseId,
                         const nsAString& aInitDataType,
                         nsTArray<uint8_t>& aInitData)
 {
@@ -298,19 +326,17 @@ CDMProxy::gmp_RemoveSession(nsAutoPtr<Se
 
 void
 CDMProxy::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mKeys.Clear();
   // Note: This may end up being the last owning reference to the CDMProxy.
   nsRefPtr<nsIRunnable> task(NS_NewRunnableMethod(this, &CDMProxy::gmp_Shutdown));
-  if (mGMPThread) {
-    mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
-  }
+  mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
 void
 CDMProxy::gmp_Shutdown()
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   // Abort any pending decrypt jobs, to awaken any clients waiting on a job.
--- a/content/media/eme/CDMProxy.h
+++ b/content/media/eme/CDMProxy.h
@@ -43,17 +43,20 @@ public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CDMProxy)
 
   // Main thread only.
   CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem);
 
   // Main thread only.
   // Loads the CDM corresponding to mKeySystem.
   // Calls MediaKeys::OnCDMCreated() when the CDM is created.
-  void Init(PromiseId aPromiseId);
+  void Init(PromiseId aPromiseId,
+            const nsAString& aOrigin,
+            const nsAString& aTopLevelOrigin,
+            bool aInPrivateBrowsing);
 
   // Main thread only.
   // Uses the CDM to create a key session.
   // Calls MediaKeys::OnSessionActivated() when session is created.
   // Assumes ownership of (Move()s) aInitData's contents.
   void CreateSession(dom::SessionType aSessionType,
                      PromiseId aPromiseId,
                      const nsAString& aInitDataType,
@@ -160,18 +163,25 @@ public:
   CDMCaps& Capabilites();
 
 #ifdef DEBUG
   bool IsOnGMPThread();
 #endif
 
 private:
 
+  struct InitData {
+    uint32_t mPromiseId;
+    nsAutoString mOrigin;
+    nsAutoString mTopLevelOrigin;
+    bool mInPrivateBrowsing;
+  };
+
   // GMP thread only.
-  void gmp_Init(uint32_t aPromiseId);
+  void gmp_Init(nsAutoPtr<InitData> aData);
 
   // GMP thread only.
   void gmp_Shutdown();
 
   // Main thread only.
   void OnCDMCreated(uint32_t aPromiseId);
 
   struct CreateSessionData {
--- a/content/media/eme/MediaKeys.cpp
+++ b/content/media/eme/MediaKeys.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/WindowsVersion.h"
 #endif
 
 namespace mozilla {
 
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeys,
+                                      mElement,
                                       mParent,
                                       mKeySessions,
                                       mPromises,
                                       mPendingSessions);
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeys)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeys)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeys)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
@@ -219,58 +220,124 @@ MediaKeys::ResolvePromise(PromiseId aId)
 already_AddRefed<Promise>
 MediaKeys::Create(const GlobalObject& aGlobal,
                   const nsAString& aKeySystem,
                   ErrorResult& aRv)
 {
   // CDMProxy keeps MediaKeys alive until it resolves the promise and thus
   // returns the MediaKeys object to JS.
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
-  if (!window) {
+  if (!window || !window->GetExtantDoc()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<MediaKeys> keys = new MediaKeys(window, aKeySystem);
-  nsRefPtr<Promise> promise(keys->MakePromise(aRv));
+  return keys->Init(aRv);
+}
+
+already_AddRefed<Promise>
+MediaKeys::Init(ErrorResult& aRv)
+{
+  nsRefPtr<Promise> promise(MakePromise(aRv));
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  if (!IsSupportedKeySystem(aKeySystem)) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-    return nullptr;
+  if (!IsSupportedKeySystem(mKeySystem)) {
+    promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return promise.forget();
   }
 
-  keys->mProxy = new CDMProxy(keys, aKeySystem);
+  mProxy = new CDMProxy(this, mKeySystem);
+
+  // Determine principal (at creation time) of the MediaKeys object.
+  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject());
+  if (!sop) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+  mPrincipal = sop->GetPrincipal();
+
+  // Determine principal of the "top-level" window; the principal of the
+  // page that will display in the URL bar.
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetParentObject());
+  if (!window) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+  nsCOMPtr<nsIDOMWindow> topWindow;
+  window->GetTop(getter_AddRefs(topWindow));
+  nsCOMPtr<nsPIDOMWindow> top = do_QueryInterface(topWindow);
+  if (!top || !top->GetExtantDoc()) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+  
+  mTopLevelPrincipal = top->GetExtantDoc()->NodePrincipal();
+
+  if (!mPrincipal || !mTopLevelPrincipal) {
+    NS_WARNING("Failed to get principals when creating MediaKeys");
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+
+  nsAutoString origin;
+  nsresult rv = nsContentUtils::GetUTFOrigin(mPrincipal, origin);
+  if (NS_FAILED(rv)) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+  nsAutoString topLevelOrigin;
+  rv = nsContentUtils::GetUTFOrigin(mTopLevelPrincipal, topLevelOrigin);
+  if (NS_FAILED(rv)) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+
+  if (!window) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+  nsIDocument* doc = window->GetExtantDoc();
+  const bool inPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(doc);
+
+  EME_LOG("MediaKeys::Create() (%s, %s), %s",
+          NS_ConvertUTF16toUTF8(origin).get(),
+          NS_ConvertUTF16toUTF8(topLevelOrigin).get(),
+          (inPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
 
   // The CDMProxy's initialization is asynchronous. The MediaKeys is
   // refcounted, and its instance is returned to JS by promise once
   // it's been initialized. No external refs exist to the MediaKeys while
   // we're waiting for the promise to be resolved, so we must hold a
   // reference to the new MediaKeys object until it's been created,
   // or its creation has failed. Store the id of the promise returned
   // here, and hold a self-reference until that promise is resolved or
   // rejected.
-  MOZ_ASSERT(!keys->mCreatePromiseId, "Should only be created once!");
-  keys->mCreatePromiseId = keys->StorePromise(promise);
-  keys->AddRef();
-  keys->mProxy->Init(keys->mCreatePromiseId);
+  MOZ_ASSERT(!mCreatePromiseId, "Should only be created once!");
+  mCreatePromiseId = StorePromise(promise);
+  AddRef();
+  mProxy->Init(mCreatePromiseId,
+               origin,
+               topLevelOrigin,
+               inPrivateBrowsing);
 
   return promise.forget();
 }
 
 void
-MediaKeys::OnCDMCreated(PromiseId aId)
+MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId)
 {
   nsRefPtr<Promise> promise(RetrievePromise(aId));
   if (!promise) {
     NS_WARNING("MediaKeys tried to resolve a non-existent promise");
     return;
   }
+  mNodeId = aNodeId;
   nsRefPtr<MediaKeys> keys(this);
   promise->MaybeResolve(keys);
   if (mCreatePromiseId == aId) {
     Release();
   }
 }
 
 already_AddRefed<MediaKeySession>
@@ -362,40 +429,67 @@ already_AddRefed<MediaKeySession>
 MediaKeys::GetSession(const nsAString& aSessionId)
 {
   nsRefPtr<MediaKeySession> session;
   mKeySessions.Get(aSessionId, getter_AddRefs(session));
   return session.forget();
 }
 
 const nsCString&
-MediaKeys::GetNodeId()
+MediaKeys::GetNodeId() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mNodeId;
+}
+
+bool
+MediaKeys::IsBoundToMediaElement() const
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  // TODO: Bug 1035637, return a combination of origin and URL bar origin.
+  return mElement != nullptr;
+}
 
-  if (!mNodeId.IsEmpty()) {
-    return mNodeId;
+nsresult
+MediaKeys::Bind(HTMLMediaElement* aElement)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (IsBoundToMediaElement()) {
+    return NS_ERROR_FAILURE;
   }
 
-  nsIPrincipal* principal = nullptr;
-  nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(GetParentObject());
-  nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
-    do_QueryInterface(pWindow);
-  if (scriptPrincipal) {
-    principal = scriptPrincipal->GetPrincipal();
-  }
-  nsAutoString id;
-  if (principal && NS_SUCCEEDED(nsContentUtils::GetUTFOrigin(principal, id))) {
-    CopyUTF16toUTF8(id, mNodeId);
-    EME_LOG("EME Origin = '%s'", mNodeId.get());
+  mElement = aElement;
+  nsresult rv = CheckPrincipals();
+  if (NS_FAILED(rv)) {
+    mElement = nullptr;
+    return rv;
   }
 
-  return mNodeId;
+  return NS_OK;
+}
+
+nsresult
+MediaKeys::CheckPrincipals()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!IsBoundToMediaElement()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<nsIPrincipal> elementPrincipal(mElement->GetCurrentPrincipal());
+  nsRefPtr<nsIPrincipal> elementTopLevelPrincipal(mElement->GetTopLevelPrincipal());
+  if (!elementPrincipal ||
+      !mPrincipal ||
+      !elementPrincipal->Equals(mPrincipal) ||
+      !elementTopLevelPrincipal ||
+      !mTopLevelPrincipal ||
+      !elementTopLevelPrincipal->Equals(mTopLevelPrincipal)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
 }
 
 bool
 CopyArrayBufferViewOrArrayBufferData(const ArrayBufferViewOrArrayBuffer& aBufferOrView,
                                      nsTArray<uint8_t>& aOutData)
 {
   if (aBufferOrView.IsArrayBuffer()) {
     const ArrayBuffer& buffer = aBufferOrView.GetAsArrayBuffer();
--- a/content/media/eme/MediaKeys.h
+++ b/content/media/eme/MediaKeys.h
@@ -13,24 +13,26 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsRefPtrHashtable.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/MediaKeysBinding.h"
 #include "mozilla/dom/UnionTypes.h"
+#include "mozIGeckoMediaPluginService.h"
 
 namespace mozilla {
 
 class CDMProxy;
 
 namespace dom {
 
 class MediaKeySession;
+class HTMLMediaElement;
 
 typedef nsRefPtrHashtable<nsStringHashKey, MediaKeySession> KeySessionHashMap;
 typedef nsRefPtrHashtable<nsUint32HashKey, dom::Promise> PromiseHashMap;
 typedef nsRefPtrHashtable<nsUint32HashKey, MediaKeySession> PendingKeySessionsHashMap;
 typedef uint32_t PromiseId;
 
 // Helper function to extract data coming in from JS in an
 // (ArrayBuffer or ArrayBufferView) IDL typed function argument.
@@ -50,16 +52,18 @@ public:
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeys)
 
   MediaKeys(nsPIDOMWindow* aParentWindow, const nsAString& aKeySystem);
 
   nsPIDOMWindow* GetParentObject() const;
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
+  nsresult Bind(HTMLMediaElement* aElement);
+
   // Javascript: readonly attribute DOMString keySystem;
   void GetKeySystem(nsString& retval) const;
 
   // JavaScript: MediaKeys.createSession()
   already_AddRefed<MediaKeySession> CreateSession(SessionType aSessionType,
                                                   ErrorResult& aRv);
 
   // JavaScript: MediaKeys.SetServerCertificate()
@@ -77,17 +81,17 @@ public:
                                                const nsAString& aKeySystem,
                                                const Optional<nsAString>& aInitDataType,
                                                const Optional<nsAString>& aContentType,
                                                const Optional<nsAString>& aCapability);
 
   already_AddRefed<MediaKeySession> GetSession(const nsAString& aSessionId);
 
   // Called once a Create() operation succeeds.
-  void OnCDMCreated(PromiseId aId);
+  void OnCDMCreated(PromiseId aId, const nsACString& aNodeId);
   // Called when GenerateRequest or Load have been called on a MediaKeySession
   // and we are waiting for its initialisation to finish.
   void OnSessionPending(PromiseId aId, MediaKeySession* aSession);
   // Called once a CreateSession succeeds.
   void OnSessionCreated(PromiseId aId, const nsAString& aSessionId);
   // Called once a LoadSession succeeds.
   void OnSessionLoaded(PromiseId aId, bool aSuccess);
   // Called once a session has closed.
@@ -102,34 +106,50 @@ public:
   // promises to be resolved.
   PromiseId StorePromise(Promise* aPromise);
 
   // Reject promise with DOMException corresponding to aExceptionCode.
   void RejectPromise(PromiseId aId, nsresult aExceptionCode);
   // Resolves promise with "undefined".
   void ResolvePromise(PromiseId aId);
 
-  const nsCString& GetNodeId();
+  const nsCString& GetNodeId() const;
 
   void Shutdown();
 
+  // Returns true if this MediaKeys has been bound to a media element.
+  bool IsBoundToMediaElement() const;
+
+  // Return NS_OK if the principals are the same as when the MediaKeys
+  // was created, failure otherwise.
+  nsresult CheckPrincipals();
+
 private:
 
+  bool IsInPrivateBrowsing();
+  already_AddRefed<Promise> Init(ErrorResult& aRv);
+
   // Removes promise from mPromises, and returns it.
   already_AddRefed<Promise> RetrievePromise(PromiseId aId);
 
   // Owning ref to proxy. The proxy has a weak reference back to the MediaKeys,
   // and the MediaKeys destructor clears the proxy's reference to the MediaKeys.
   nsRefPtr<CDMProxy> mProxy;
 
+  nsRefPtr<HTMLMediaElement> mElement;
+
   nsCOMPtr<nsPIDOMWindow> mParent;
   nsString mKeySystem;
   nsCString mNodeId;
   KeySessionHashMap mKeySessions;
   PromiseHashMap mPromises;
   PendingKeySessionsHashMap mPendingSessions;
   PromiseId mCreatePromiseId;
+
+  nsRefPtr<nsIPrincipal> mPrincipal;
+  nsRefPtr<nsIPrincipal> mTopLevelPrincipal;
+
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_mediakeys_h__
--- a/content/media/gmp/GMPService.cpp
+++ b/content/media/gmp/GMPService.cpp
@@ -17,16 +17,17 @@
 #include "nsNativeCharsetUtils.h"
 #include "nsIConsoleService.h"
 #include "mozilla/unused.h"
 #include "GMPDecryptorParent.h"
 #include "GMPAudioDecoderParent.h"
 #include "nsComponentManagerUtils.h"
 #include "mozilla/Preferences.h"
 #include "runnable_utils.h"
+#include "VideoUtils.h"
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
 #include "mozilla/Sandbox.h"
 #endif
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
@@ -849,10 +850,33 @@ GeckoMediaPluginService::ReAddOnGMPThrea
   MutexAutoLock lock(mMutex);
   mPlugins.RemoveElement(aOld);
 
   // Schedule aOld to be destroyed.  We can't destroy it from here since we
   // may be inside ActorDestroyed() for it.
   NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld));
 }
 
+NS_IMETHODIMP
+GeckoMediaPluginService::GetNodeId(const nsAString& aOrigin,
+                                   const nsAString& aTopLevelOrigin,
+                                   bool aInPrivateBrowsing,
+                                   nsACString& aOutId)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+  LOGD(("%s::%s: (%s, %s), %s", __CLASS__, __FUNCTION__,
+       NS_ConvertUTF16toUTF8(aOrigin).get(),
+       NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+       (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing")));
+
+  nsAutoCString salt;
+  nsresult rv = GenerateRandomPathName(salt, 32);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aOutId = salt;
+
+  // TODO: Store salt, so it can be retrieved in subsequent sessions.
+
+  return NS_OK;
+}
+
 } // namespace gmp
 } // namespace mozilla
--- a/content/media/gmp/GMPStorageParent.cpp
+++ b/content/media/gmp/GMPStorageParent.cpp
@@ -73,22 +73,17 @@ GetGMPStorageDir(nsIFile** aTempDir, con
     return rv;
   }
 
   rv = tmpFile->AppendNative(nsDependentCString("mozilla-gmp-storage"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  // TODO: When aOrigin is the same node-id as the GMP sees in the child
-  // process (a UUID or somesuch), we can just append it un-hashed here.
-  // This should reduce the chance of hash collsions exposing data.
-  nsAutoString nodeIdHash;
-  nodeIdHash.AppendInt(HashString(aNodeId));
-  rv = tmpFile->Append(nodeIdHash);
+  rv = tmpFile->AppendNative(aNodeId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
   if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
--- a/content/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/content/media/gmp/mozIGeckoMediaPluginService.idl
@@ -20,17 +20,17 @@ class GMPVideoHost;
 [ptr] native GMPVideoDecoderProxy(GMPVideoDecoderProxy);
 [ptr] native GMPVideoEncoderProxy(GMPVideoEncoderProxy);
 [ptr] native GMPVideoHost(GMPVideoHost);
 [ptr] native MessageLoop(MessageLoop);
 [ptr] native TagArray(nsTArray<nsCString>);
 [ptr] native GMPDecryptorProxy(GMPDecryptorProxy);
 [ptr] native GMPAudioDecoderProxy(GMPAudioDecoderProxy);
 
-[scriptable, uuid(88ade941-a423-48f9-aa3d-a383af8de4b8)]
+[scriptable, uuid(3d811f9f-e1f8-48a5-a385-3657a641ee76)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
 
   /**
    * The GMP thread. Callable from any thread.
    */
   readonly attribute nsIThread thread;
 
@@ -84,9 +84,16 @@ interface mozIGeckoMediaPluginService : 
    */
   void addPluginDirectory(in AString directory);
 
   /**
    * Remove a directory for gecko media plugins.
    * @note Main-thread API.
    */
   void removePluginDirectory(in AString directory);
+
+  /**
+   * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
+   */
+  ACString getNodeId(in AString origin,
+                     in AString topLevelOrigin,
+                     in bool inPrivateBrowsingMode);
 };