Commit 758ef853 authored by jrummell@chromium.org's avatar jrummell@chromium.org

Implement MediaKeySession close() and remove()

Rename existing release() method to close(), and add remove().

BUG=358271
TEST=updated EME tests pass

Review URL: https://codereview.chromium.org/568033003

git-svn-id: svn://svn.chromium.org/blink/trunk@183689 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent b11e6781
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
mediaKeySession = mediaKeys.createSession(); mediaKeySession = mediaKeys.createSession();
return mediaKeySession.generateRequest(initDataType, getInitData(initDataType)); return mediaKeySession.generateRequest(initDataType, getInitData(initDataType));
}).then(function() { }).then(function() {
return mediaKeySession.release(); return mediaKeySession.close();
}).then(function(result) { }).then(function(result) {
test.done(); test.done();
}).catch(function(error) { }).catch(function(error) {
......
...@@ -56,10 +56,10 @@ ...@@ -56,10 +56,10 @@
// Should be just the 2 MediaKeySessions. // Should be just the 2 MediaKeySessions.
assert_equals(numActiveDOMObjectsCreated(), 2, 'After gc()'); assert_equals(numActiveDOMObjectsCreated(), 2, 'After gc()');
// Release the sessions. Once the close() event is received, // Close the sessions. Once the close() event is received,
// they should get garbage collected as there are no JS // they should get garbage collected as there are no JS
// references to them. // references to them.
var promise = mediaKeySession1.release(); var promise = mediaKeySession1.close();
mediaKeySession1 = null; mediaKeySession1 = null;
return promise; return promise;
}).then(function(result) { }).then(function(result) {
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
}).then(function(result) { }).then(function(result) {
// Should be just mediaKeySession2. // Should be just mediaKeySession2.
assert_equals(numActiveDOMObjectsCreated(), 1); assert_equals(numActiveDOMObjectsCreated(), 1);
var promise = mediaKeySession2.release(); var promise = mediaKeySession2.close();
mediaKeySession2 = null; mediaKeySession2 = null;
return promise; return promise;
}).then(function(result) { }).then(function(result) {
......
...@@ -47,11 +47,11 @@ ...@@ -47,11 +47,11 @@
// Should be 2 MediaKeySessions. // Should be 2 MediaKeySessions.
assert_equals(numActiveDOMObjectsCreated(), 2, 'mediaKeys.createSession(2)'); assert_equals(numActiveDOMObjectsCreated(), 2, 'mediaKeys.createSession(2)');
// Release the sessions. Once completed, only the JS // Close the sessions. Once completed, only the JS
// reference to them keeps them around. // reference to them keeps them around.
return mediaKeySession1.release(); return mediaKeySession1.close();
}).then(function(result) { }).then(function(result) {
return mediaKeySession2.release(); return mediaKeySession2.close();
}).then(function(result) { }).then(function(result) {
// Since both sessions have been closed, dropping the // Since both sessions have been closed, dropping the
// reference to them from JS will result in the session // reference to them from JS will result in the session
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
// release() should result in the closed promise being // release() should result in the closed promise being
// fulfilled. // fulfilled.
return mediaKeySession.release(); return mediaKeySession.close();
}).catch(function(error) { }).catch(function(error) {
forceTestFailureFromPromise(test, error); forceTestFailureFromPromise(test, error);
}); });
......
...@@ -378,7 +378,8 @@ ...@@ -378,7 +378,8 @@
assert_equals(typeof mediaKeySession.addEventListener, 'function'); assert_equals(typeof mediaKeySession.addEventListener, 'function');
assert_equals(typeof mediaKeySession.generateRequest, 'function'); assert_equals(typeof mediaKeySession.generateRequest, 'function');
assert_equals(typeof mediaKeySession.update, 'function'); assert_equals(typeof mediaKeySession.update, 'function');
assert_equals(typeof mediaKeySession.release, 'function'); assert_equals(typeof mediaKeySession.close, 'function');
assert_equals(typeof mediaKeySession.remove, 'function');
assert_equals(mediaKeySession.error, null); assert_equals(mediaKeySession.error, null);
assert_equals(mediaKeySession.sessionId, ''); assert_equals(mediaKeySession.sessionId, '');
assert_equals(typeof mediaKeySession.sessionId, 'string'); assert_equals(typeof mediaKeySession.sessionId, 'string');
...@@ -553,16 +554,50 @@ ...@@ -553,16 +554,50 @@
}); });
}, 'Test MediaKeySession update().'); }, 'Test MediaKeySession update().');
function create_release_test(mediaKeys, type, initData) function create_close_exception_test(mediaKeys, type, initData)
{
var mediaKeySession = mediaKeys.createSession();
return mediaKeySession.close().then(function(result) {
assert_unreached('close() should not succeed if session uninitialized');
}).catch(function(error) {
assert_equals(error.name, 'InvalidStateError');
// Return something so the promise resolves.
return Promise.resolve();
});
}
async_test(function(test)
{
MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
var promises = [];
if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) {
promises.push(create_close_exception_test(mediaKeys, 'webm', getInitData('webm')));
}
if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) {
promises.push(create_close_exception_test(mediaKeys, 'cenc', getInitData('cenc')));
}
assert_not_equals(promises.length, 0);
return Promise.all(promises);
}).then(function(result) {
test.done();
}).catch(function(error) {
forceTestFailureFromPromise(test, error, 'close() exception tests failed');
});
}, 'Test MediaKeySession close() exceptions.');
function create_close_test(mediaKeys, type, initData)
{ {
var mediaKeySession = mediaKeys.createSession(); var mediaKeySession = mediaKeys.createSession();
var promise = mediaKeySession.generateRequest(type, initData).then(function(result) { var promise = mediaKeySession.generateRequest(type, initData).then(function(result) {
return mediaKeySession.release(); return mediaKeySession.close();
// FIXME: Uncomment once the code supports multiple release() calls. }).then(function(result) {
// }).then(function(result) { // Call close() again with an extra parameter. The extra
// // Call release() again with an extra parameter. The extra // parameter is ignored.
// // parameter is ignored. return mediaKeySession.close('extra');
// return mediaKeySession.release('extra');
}); });
return promise; return promise;
} }
...@@ -573,11 +608,106 @@ ...@@ -573,11 +608,106 @@
var promises = []; var promises = [];
if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) { if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) {
promises.push(create_release_test(mediaKeys, 'webm', getInitData('webm'))); promises.push(create_close_test(mediaKeys, 'webm', getInitData('webm')));
}
if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) {
promises.push(create_close_test(mediaKeys, 'cenc', getInitData('cenc')));
}
assert_not_equals(promises.length, 0);
return Promise.all(promises);
}).then(function(result) {
test.done();
}).catch(function(error) {
forceTestFailureFromPromise(test, error, 'close() tests failed');
});
}, 'Test MediaKeySession close().');
function create_remove_exception_test(mediaKeys, type, initData)
{
// remove() on an uninitialized session should fail.
var mediaKeySession = mediaKeys.createSession('temporary');
return mediaKeySession.remove().then(function(result) {
assert_unreached('remove() should not succeed if session uninitialized');
}).catch(function(error) {
assert_equals(error.name, 'InvalidStateError');
// remove() on a temporary session should fail.
return mediaKeySession.generateRequest(type, initData);
}).then(function(result) {
return mediaKeySession.remove();
}).then(function(result) {
assert_unreached('remove() should not succeed for temporary sessions');
}).catch(function(error) {
assert_equals(error.name, 'InvalidAccessError');
// remove() on a closed persistent session should also fail.
mediaKeySession = mediaKeys.createSession('persistent');
return mediaKeySession.generateRequest(type, initData);
}).then(function(result) {
return mediaKeySession.close();
}).then(function(result) {
return mediaKeySession.remove();
}).then(function(result) {
assert_unreached('remove() should not succeed for closed persistent sessions');
}).catch(function(error) {
assert_equals(error.name, 'InvalidStateError');
// Return something so the promise resolves.
return Promise.resolve();
});
}
async_test(function(test)
{
MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
var promises = [];
if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) {
promises.push(create_remove_exception_test(mediaKeys, 'webm', getInitData('webm')));
}
if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) {
promises.push(create_remove_exception_test(mediaKeys, 'cenc', getInitData('cenc')));
}
assert_not_equals(promises.length, 0);
return Promise.all(promises);
}).then(function(result) {
test.done();
}).catch(function(error) {
forceTestFailureFromPromise(test, error, 'remove() exception tests failed');
});
}, 'Test MediaKeySession remove() exceptions.');
function create_remove_test(mediaKeys, type, initData)
{
// ClearKey may not support persistent sessions.
var mediaKeySession;
try {
mediaKeySession = mediaKeys.createSession('persistent');
} catch (error) {
// Not supported, so return a resolved promise.
assert_equals(error.name, 'NotSupportedError');
return Promise.resolve();
}
return mediaKeySession.generateRequest(type, initData).then(function(result) {
return mediaKeySession.remove();
});
}
async_test(function(test)
{
MediaKeys.create('org.w3.clearkey').then(function(mediaKeys) {
var promises = [];
if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/webm')) {
promises.push(create_remove_test(mediaKeys, 'webm', getInitData('webm')));
} }
if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) { if (MediaKeys.isTypeSupported('org.w3.clearkey', 'video/mp4')) {
promises.push(create_release_test(mediaKeys, 'cenc', getInitData('cenc'))); promises.push(create_remove_test(mediaKeys, 'cenc', getInitData('cenc')));
} }
assert_not_equals(promises.length, 0); assert_not_equals(promises.length, 0);
...@@ -585,9 +715,9 @@ ...@@ -585,9 +715,9 @@
}).then(function(result) { }).then(function(result) {
test.done(); test.done();
}).catch(function(error) { }).catch(function(error) {
forceTestFailureFromPromise(test, error, 'release() tests failed'); forceTestFailureFromPromise(test, error, 'remove() tests failed');
}); });
}, 'Test MediaKeySession release().'); }, 'Test MediaKeySession remove().');
// FIXME: Add syntax checks for MediaKeys.IsTypeSupported(). // FIXME: Add syntax checks for MediaKeys.IsTypeSupported().
// FIXME: Add syntax checks for MediaKeyError and MediaKeySession events. // FIXME: Add syntax checks for MediaKeyError and MediaKeySession events.
......
...@@ -50,6 +50,16 @@ ...@@ -50,6 +50,16 @@
#include "wtf/ArrayBuffer.h" #include "wtf/ArrayBuffer.h"
#include "wtf/ArrayBufferView.h" #include "wtf/ArrayBufferView.h"
namespace {
// The list of possible values for |sessionType| passed to createSession().
#if ENABLE(ASSERT)
const char* kTemporary = "temporary";
#endif
const char* kPersistent = "persistent";
} // namespace
namespace blink { namespace blink {
static bool isKeySystemSupportedWithInitDataType(const String& keySystem, const String& initDataType) static bool isKeySystemSupportedWithInitDataType(const String& keySystem, const String& initDataType)
...@@ -75,7 +85,8 @@ public: ...@@ -75,7 +85,8 @@ public:
enum Type { enum Type {
GenerateRequest, GenerateRequest,
Update, Update,
Release Close,
Remove
}; };
Type type() const { return m_type; } Type type() const { return m_type; }
...@@ -111,10 +122,16 @@ public: ...@@ -111,10 +122,16 @@ public:
return new PendingAction(Update, result, String(), data); return new PendingAction(Update, result, String(), data);
} }
static PendingAction* CreatePendingRelease(ContentDecryptionModuleResult* result) static PendingAction* CreatePendingClose(ContentDecryptionModuleResult* result)
{ {
ASSERT(result); ASSERT(result);
return new PendingAction(Release, result, String(), PassRefPtr<ArrayBuffer>()); return new PendingAction(Close, result, String(), PassRefPtr<ArrayBuffer>());
}
static PendingAction* CreatePendingRemove(ContentDecryptionModuleResult* result)
{
ASSERT(result);
return new PendingAction(Remove, result, String(), PassRefPtr<ArrayBuffer>());
} }
~PendingAction() ~PendingAction()
...@@ -207,6 +224,7 @@ private: ...@@ -207,6 +224,7 @@ private:
MediaKeySession* MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType) MediaKeySession* MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType)
{ {
ASSERT(sessionType == kTemporary || sessionType == kPersistent);
RefPtrWillBeRawPtr<MediaKeySession> session = new MediaKeySession(scriptState, mediaKeys, sessionType); RefPtrWillBeRawPtr<MediaKeySession> session = new MediaKeySession(scriptState, mediaKeys, sessionType);
session->suspendIfNeeded(); session->suspendIfNeeded();
return session.get(); return session.get();
...@@ -402,36 +420,84 @@ ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefP ...@@ -402,36 +420,84 @@ ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefP
return promise; return promise;
} }
ScriptPromise MediaKeySession::release(ScriptState* scriptState) ScriptPromise MediaKeySession::close(ScriptState* scriptState)
{ {
WTF_LOG(Media, "MediaKeySession(%p)::release", this); WTF_LOG(Media, "MediaKeySession(%p)::close", this);
SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
ScriptPromise promise = result->promise();
// From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-close>: // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-close:
// The close() method allows an application to indicate that it no longer // The close() method allows an application to indicate that it no longer
// needs the session and the CDM should release any resources associated // needs the session and the CDM should release any resources associated
// with this object and close it. The returned promise is resolved when the // with this object and close it. The returned promise is resolved when the
// request has been processed, and the closed attribute promise is resolved // request has been processed, and the closed attribute promise is resolved
// when the session is closed. It must run the following steps: // when the session is closed. It must run the following steps:
// //
// 1. If the Session Close algorithm has been run on this object, return a // 1. If this object's callable value is false, return a promise rejected
// promise fulfilled with undefined. // with a new DOMException whose name is "InvalidStateError".
if (!m_isCallable) {
return ScriptPromise::rejectWithDOMException(
scriptState, DOMException::create(InvalidStateError, "The session is not callable."));
}
// 2. If the Session Close algorithm has been run on this object,
// return a resolved promise.
if (m_isClosed)
return ScriptPromise::cast(scriptState, ScriptValue());
// 3. Let promise be a new promise.
SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
ScriptPromise promise = result->promise();
// 4. Run the following steps asynchronously (documented in
// actionTimerFired()).
m_pendingActions.append(PendingAction::CreatePendingClose(result));
if (!m_actionTimer.isActive())
m_actionTimer.startOneShot(0, FROM_HERE);
// 5. Return promise.
return promise;
}
ScriptPromise MediaKeySession::remove(ScriptState* scriptState)
{
WTF_LOG(Media, "MediaKeySession(%p)::remove", this);
// From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-remove:
// The remove() method allows an application to remove stored session data
// associated with this object. It must run the following steps:
// 1. If this object's callable value is false, return a promise rejected
// with a new DOMException whose name is "InvalidStateError".
if (!m_isCallable) {
return ScriptPromise::rejectWithDOMException(
scriptState, DOMException::create(InvalidStateError, "The session is not callable."));
}
// 2. If this object's session type is not "persistent", return a promise
// rejected with a new DOMException whose name is "InvalidAccessError".
if (m_sessionType != kPersistent) {
return ScriptPromise::rejectWithDOMException(
scriptState, DOMException::create(InvalidAccessError, "The session type is not 'persistent'."));
}
// 3. If the Session Close algorithm has been run on this object, return a
// promise rejected with a new DOMException whose name is
// "InvalidStateError".
if (m_isClosed) { if (m_isClosed) {
result->complete(); return ScriptPromise::rejectWithDOMException(
return promise; scriptState, DOMException::create(InvalidStateError, "The session is already closed."));
} }
// 2. Let promise be a new promise. // 4. Let promise be a new promise.
// (Created earlier so it was available in step 1.) SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
ScriptPromise promise = result->promise();
// 3. Run the following steps asynchronously (documented in // 5. Run the following steps asynchronously (documented in
// actionTimerFired()). // actionTimerFired()).
m_pendingActions.append(PendingAction::CreatePendingRelease(result)); m_pendingActions.append(PendingAction::CreatePendingRemove(result));
if (!m_actionTimer.isActive()) if (!m_actionTimer.isActive())
m_actionTimer.startOneShot(0, FROM_HERE); m_actionTimer.startOneShot(0, FROM_HERE);
// 4. Return promise. // 6. Return promise.
return promise; return promise;
} }
...@@ -479,16 +545,43 @@ void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*) ...@@ -479,16 +545,43 @@ void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*)
m_session->update(static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), action->result()->result()); m_session->update(static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), action->result()->result());
break; break;
case PendingAction::Release: case PendingAction::Close:
WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Release", this); WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Close", this);
// NOTE: Continued from step 3 of MediaKeySession::release(). // NOTE: Continued from step 4 of MediaKeySession::close().
// 3.1 Let cdm be the cdm loaded in create(). // 4.1 Let cdm be the CDM loaded during the initialization of the
// 3.2 Use the cdm to execute the following steps: // MediaKeys object that created this object.
// 3.2.1 Process the close request. Do not remove stored session data. // (Already captured when creating m_session).
// 3.2.2 If the previous step caused the session to be closed, run the // 4.2 Use the cdm to execute the following steps:
// Session Close algorithm on this object. // 4.2.1 Process the close request. Do not remove stored session
// 3.3 Resolve promise with undefined. // data.
m_session->release(action->result()->result()); // 4.2.3 If the previous step caused the session to be closed,
// run the Session Close algorithm on this object.
// 4.3 Resolve promise.
m_session->close(action->result()->result());
break;
case PendingAction::Remove:
WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Remove", this);
// NOTE: Continued from step 5 of MediaKeySession::remove().
// 5.1 Let cdm be the CDM loaded during the initialization of the
// MediaKeys object that created this object.
// (Already captured when creating m_session).
// 5.2 Use the cdm to execute the following steps:
// 5.2.1 Process the remove request. This may involve exchanging
// message(s) with the application. Unless this step fails,
// the CDM must have cleared all stored session data
// associated with this object, including the sessionId,
// before proceeding to the next step. (A subsequent call
// to load() with sessionId would fail because there is no
// data stored for the sessionId.)
// 5.3 Run the following steps asynchronously once the above step
// has completed:
// 5.3.1 If any of the preceding steps failed, reject promise
// with a new DOMException whose name is the appropriate
// error name.
// 5.3.2 Run the Session Close algorithm on this object.
// 5.3.3 Resolve promise.
m_session->remove(action->result()->result());
break; break;
} }
} }
......
...@@ -80,7 +80,8 @@ public: ...@@ -80,7 +80,8 @@ public:
ScriptPromise update(ScriptState*, ArrayBuffer* response); ScriptPromise update(ScriptState*, ArrayBuffer* response);
ScriptPromise update(ScriptState*, ArrayBufferView* response); ScriptPromise update(ScriptState*, ArrayBufferView* response);
ScriptPromise release(ScriptState*); ScriptPromise close(ScriptState*);
ScriptPromise remove(ScriptState*);
void enqueueEvent(PassRefPtrWillBeRawPtr<Event>); void enqueueEvent(PassRefPtrWillBeRawPtr<Event>);
......
...@@ -44,5 +44,6 @@ ...@@ -44,5 +44,6 @@
// session operations // session operations
[CallWith=ScriptState] Promise update(ArrayBuffer response); [CallWith=ScriptState] Promise update(ArrayBuffer response);
[CallWith=ScriptState] Promise update(ArrayBufferView response); [CallWith=ScriptState] Promise update(ArrayBufferView response);
[CallWith=ScriptState] Promise release(); [CallWith=ScriptState] Promise close();
[CallWith=ScriptState] Promise remove();
}; };
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment