Bug 1060179 - Generate a random node id for every EME (origin,topLevelOrigin) pair. r=bz
authorChris Pearce <cpearce@mozilla.com>
Mon, 13 Oct 2014 11:53:43 +1300
changeset 233251 d0ad1569d85c52e665cd341ec604b1d7f49e3e95
parent 233250 f14397543760d2c670459f8c7e783e12e86d33bd
child 233252 27ae9275cd05c8922a4f3fff4192cf99962536d7
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1060179
milestone35.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 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);
 };