Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / modules / encryptedmedia / HTMLMediaElementEncryptedMedia.cpp
index f8a82cc..46ffd93 100644 (file)
@@ -6,14 +6,21 @@
 #include "modules/encryptedmedia/HTMLMediaElementEncryptedMedia.h"
 
 #include "bindings/core/v8/ExceptionState.h"
+#include "bindings/core/v8/ScriptPromise.h"
+#include "bindings/core/v8/ScriptPromiseResolver.h"
+#include "bindings/core/v8/ScriptState.h"
+#include "core/dom/DOMException.h"
 #include "core/dom/ExceptionCode.h"
 #include "core/html/HTMLMediaElement.h"
 #include "core/html/MediaKeyError.h"
 #include "core/html/MediaKeyEvent.h"
 #include "modules/encryptedmedia/MediaKeyNeededEvent.h"
 #include "modules/encryptedmedia/MediaKeys.h"
+#include "modules/encryptedmedia/SimpleContentDecryptionModuleResult.h"
+#include "platform/ContentDecryptionModuleResult.h"
 #include "platform/Logging.h"
 #include "platform/RuntimeEnabledFeatures.h"
+#include "wtf/Functional.h"
 #include "wtf/Uint8Array.h"
 
 namespace blink {
@@ -38,6 +45,188 @@ static void throwExceptionIfMediaKeyExceptionOccurred(const String& keySystem, c
     return;
 }
 
+// This class allows MediaKeys to be set asynchronously.
+class SetMediaKeysHandler : public ScriptPromiseResolver {
+    WTF_MAKE_NONCOPYABLE(SetMediaKeysHandler);
+
+public:
+    static ScriptPromise create(ScriptState*, HTMLMediaElement&, MediaKeys*);
+    virtual ~SetMediaKeysHandler();
+
+private:
+    SetMediaKeysHandler(ScriptState*, HTMLMediaElement&, MediaKeys*);
+    void timerFired(Timer<SetMediaKeysHandler>*);
+
+    void clearExistingMediaKeys();
+    void setNewMediaKeys();
+    void finish();
+
+    void reportSetFailed(ExceptionCode, const String& errorMessage);
+
+    // Keep media element alive until promise is fulfilled
+    RefPtrWillBePersistent<HTMLMediaElement> m_element;
+    Persistent<MediaKeys> m_newMediaKeys;
+    Timer<SetMediaKeysHandler> m_timer;
+};
+
+typedef Function<void()> SuccessCallback;
+typedef Function<void(ExceptionCode, const String&)> FailureCallback;
+
+// Represents the result used when setContentDecryptionModule() is called.
+// Calls |success| if result is resolved, |failure| is result is rejected.
+class SetContentDecryptionModuleResult FINAL : public ContentDecryptionModuleResult {
+public:
+    SetContentDecryptionModuleResult(SuccessCallback success, FailureCallback failure)
+        : m_successCallback(success)
+        , m_failureCallback(failure)
+    {
+    }
+
+    // ContentDecryptionModuleResult implementation.
+    virtual void complete() OVERRIDE
+    {
+        m_successCallback();
+    }
+
+    virtual void completeWithSession(blink::WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE
+    {
+        ASSERT_NOT_REACHED();
+        m_failureCallback(InvalidStateError, "Unexpected completion.");
+    }
+
+    virtual void completeWithError(blink::WebContentDecryptionModuleException code, unsigned long systemCode, const blink::WebString& message) OVERRIDE
+    {
+        m_failureCallback(WebCdmExceptionToExceptionCode(code), message);
+    }
+
+private:
+    SuccessCallback m_successCallback;
+    FailureCallback m_failureCallback;
+};
+
+ScriptPromise SetMediaKeysHandler::create(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
+{
+    RefPtr<SetMediaKeysHandler> handler = adoptRef(new SetMediaKeysHandler(scriptState, element, mediaKeys));
+    handler->suspendIfNeeded();
+    handler->keepAliveWhilePending();
+    return handler->promise();
+}
+
+SetMediaKeysHandler::SetMediaKeysHandler(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
+    : ScriptPromiseResolver(scriptState)
+    , m_element(element)
+    , m_newMediaKeys(mediaKeys)
+    , m_timer(this, &SetMediaKeysHandler::timerFired)
+{
+    WTF_LOG(Media, "SetMediaKeysHandler::SetMediaKeysHandler");
+
+    // 3. Run the remaining steps asynchronously.
+    m_timer.startOneShot(0, FROM_HERE);
+}
+
+SetMediaKeysHandler::~SetMediaKeysHandler()
+{
+}
+
+void SetMediaKeysHandler::timerFired(Timer<SetMediaKeysHandler>*)
+{
+    clearExistingMediaKeys();
+}
+
+void SetMediaKeysHandler::clearExistingMediaKeys()
+{
+    WTF_LOG(Media, "SetMediaKeysHandler::clearExistingMediaKeys");
+    HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element);
+
+    // 3.1 If mediaKeys is not null, it is already in use by another media
+    //     element, and the user agent is unable to use it with this element,
+    //     reject promise with a new DOMException whose name is
+    //     "QuotaExceededError".
+    // FIXME: Need to check whether mediaKeys is already in use by another
+    //        media element.
+    // 3.2 If the mediaKeys attribute is not null, run the following steps:
+    if (thisElement.m_mediaKeys) {
+        // 3.2.1 If the user agent or CDM do not support removing the
+        //       association, return a promise rejected with a new DOMException
+        //       whose name is "NotSupportedError".
+        //       (supported by blink).
+        // 3.2.2 If the association cannot currently be removed (i.e. during
+        //       playback), return a promise rejected with a new DOMException
+        //       whose name is "InvalidStateError".
+        if (m_element->webMediaPlayer()) {
+            reject(DOMException::create(InvalidStateError, "The existing MediaKeys object cannot be removed while a media resource is loaded."));
+            return;
+        }
+
+        // (next 2 steps not required as there is no player connected).
+        // 3.2.3 Stop using the CDM instance represented by the mediaKeys
+        //       attribute to decrypt media data and remove the association
+        //       with the media element.
+        // 3.2.4 If the preceding step failed, reject promise with a new
+        //       DOMException whose name is the appropriate error name and
+        //       that has an appropriate message.
+    }
+
+    // MediaKeys not currently set or no player connected, so continue on.
+    setNewMediaKeys();
+}
+
+void SetMediaKeysHandler::setNewMediaKeys()
+{
+    WTF_LOG(Media, "SetMediaKeysHandler::setNewMediaKeys");
+
+    // 3.3 If mediaKeys is not null, run the following steps:
+    if (m_newMediaKeys) {
+        // 3.3.1 Associate the CDM instance represented by mediaKeys with the
+        //       media element for decrypting media data.
+        // 3.3.2 If the preceding step failed, run the following steps:
+        //       (done in reportSetFailed()).
+        // 3.3.3 Run the Attempt to Resume Playback If Necessary algorithm on
+        //       the media element. The user agent may choose to skip this
+        //       step if it knows resuming will fail (i.e. mediaKeys has no
+        //       sessions).
+        //       (Handled in Chromium).
+        if (m_element->webMediaPlayer()) {
+            SuccessCallback successCallback = bind(&SetMediaKeysHandler::finish, this);
+            FailureCallback failureCallback = bind<ExceptionCode, const String&>(&SetMediaKeysHandler::reportSetFailed, this);
+            ContentDecryptionModuleResult* result = new SetContentDecryptionModuleResult(successCallback, failureCallback);
+            m_element->webMediaPlayer()->setContentDecryptionModule(m_newMediaKeys->contentDecryptionModule(), result->result());
+
+            // Don't do anything more until |result| is resolved (or rejected).
+            return;
+        }
+    }
+
+    // MediaKeys doesn't need to be set on the player, so continue on.
+    finish();
+}
+
+void SetMediaKeysHandler::finish()
+{
+    WTF_LOG(Media, "SetMediaKeysHandler::finish");
+    HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element);
+
+    // 3.4 Set the mediaKeys attribute to mediaKeys.
+    thisElement.m_mediaKeys = m_newMediaKeys;
+
+    // 3.5 Resolve promise with undefined.
+    resolve();
+}
+
+void SetMediaKeysHandler::reportSetFailed(ExceptionCode code, const String& errorMessage)
+{
+    WTF_LOG(Media, "SetMediaKeysHandler::reportSetFailed");
+    HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element);
+
+    // 3.3.2 If the preceding step failed, run the following steps:
+    // 3.3.2.1 Set the mediaKeys attribute to null.
+    thisElement.m_mediaKeys.clear();
+
+    // 3.3.2.2 Reject promise with a new DOMException whose name is the
+    //         appropriate error name and that has an appropriate message.
+    reject(DOMException::create(code, errorMessage));
+}
+
 HTMLMediaElementEncryptedMedia::HTMLMediaElementEncryptedMedia()
     : m_emeMode(EmeModeNotSelected)
 {
@@ -60,12 +249,11 @@ HTMLMediaElementEncryptedMedia& HTMLMediaElementEncryptedMedia::from(HTMLMediaEl
     return *supplement;
 }
 
-bool HTMLMediaElementEncryptedMedia::setEmeMode(EmeMode emeMode, ExceptionState& exceptionState)
+bool HTMLMediaElementEncryptedMedia::setEmeMode(EmeMode emeMode)
 {
-    if (m_emeMode != EmeModeNotSelected && m_emeMode != emeMode) {
-        exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
+    if (m_emeMode != EmeModeNotSelected && m_emeMode != emeMode)
         return false;
-    }
+
     m_emeMode = emeMode;
     return true;
 }
@@ -81,28 +269,21 @@ MediaKeys* HTMLMediaElementEncryptedMedia::mediaKeys(HTMLMediaElement& element)
     return thisElement.m_mediaKeys.get();
 }
 
-void HTMLMediaElementEncryptedMedia::setMediaKeysInternal(HTMLMediaElement& element, MediaKeys* mediaKeys)
-{
-    if (m_mediaKeys == mediaKeys)
-        return;
-
-    ASSERT(m_emeMode == EmeModeUnprefixed);
-    m_mediaKeys = mediaKeys;
-
-    // If a player is connected, tell it that the CDM has changed.
-    if (element.webMediaPlayer())
-        element.webMediaPlayer()->setContentDecryptionModule(contentDecryptionModule());
-}
-
-void HTMLMediaElementEncryptedMedia::setMediaKeys(HTMLMediaElement& element, MediaKeys* mediaKeys, ExceptionState& exceptionState)
+ScriptPromise HTMLMediaElementEncryptedMedia::setMediaKeys(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
 {
-    WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::setMediaKeys");
     HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
+    WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::setMediaKeys current(%p), new(%p)", thisElement.m_mediaKeys.get(), mediaKeys);
 
-    if (!thisElement.setEmeMode(EmeModeUnprefixed, exceptionState))
-        return;
+    if (!thisElement.setEmeMode(EmeModeUnprefixed))
+        return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed."));
 
-    thisElement.setMediaKeysInternal(element, mediaKeys);
+    // 1. If mediaKeys and the mediaKeys attribute are the same object, return
+    //    a promise resolved with undefined.
+    if (thisElement.m_mediaKeys == mediaKeys)
+        return ScriptPromise::cast(scriptState, V8ValueTraits<V8UndefinedType>::toV8Value(V8UndefinedType(), scriptState->context()->Global(), scriptState->isolate()));
+
+    // 2. Let promise be a new promise. Remaining steps done in handler.
+    return SetMediaKeysHandler::create(scriptState, element, mediaKeys);
 }
 
 // Create a MediaKeyNeededEvent for WD EME.
@@ -139,8 +320,10 @@ void HTMLMediaElementEncryptedMedia::generateKeyRequest(WebMediaPlayer* webMedia
 {
     WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitGenerateKeyRequest");
 
-    if (!setEmeMode(EmeModePrefixed, exceptionState))
+    if (!setEmeMode(EmeModePrefixed)) {
+        exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
         return;
+    }
 
     if (keySystem.isEmpty()) {
         exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
@@ -177,8 +360,10 @@ void HTMLMediaElementEncryptedMedia::addKey(WebMediaPlayer* webMediaPlayer, cons
 {
     WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitAddKey");
 
-    if (!setEmeMode(EmeModePrefixed, exceptionState))
+    if (!setEmeMode(EmeModePrefixed)) {
+        exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
         return;
+    }
 
     if (keySystem.isEmpty()) {
         exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
@@ -225,8 +410,10 @@ void HTMLMediaElementEncryptedMedia::cancelKeyRequest(WebMediaPlayer* webMediaPl
 {
     WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitCancelKeyRequest");
 
-    if (!setEmeMode(EmeModePrefixed, exceptionState))
+    if (!setEmeMode(EmeModePrefixed)) {
+        exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
         return;
+    }
 
     if (keySystem.isEmpty()) {
         exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
@@ -341,7 +528,11 @@ void HTMLMediaElementEncryptedMedia::playerDestroyed(HTMLMediaElement& element)
 #endif
 
     HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
-    thisElement.setMediaKeysInternal(element, 0);
+    if (!thisElement.m_mediaKeys)
+        return;
+
+    ASSERT(thisElement.m_emeMode == EmeModeUnprefixed);
+    thisElement.m_mediaKeys.clear();
 }
 
 WebContentDecryptionModule* HTMLMediaElementEncryptedMedia::contentDecryptionModule(HTMLMediaElement& element)