Commit 3fd0264f authored by John Rummell's avatar John Rummell Committed by Commit Bot

Update MediaKeySession.Close() to match the EME spec

Update the code to match https://w3c.github.io/encrypted-media/#close.
Most noticeable change is that other requests will be rejected once
close() has been called. Previously this only happened after the CDM
actually closed the session (as acknowledged by the close event).

BUG=901372
TEST=new layout tests pass

Change-Id: I59c2ad2705f1928d74e6f41a853c0f25c8975189
Reviewed-on: https://chromium-review.googlesource.com/c/1330349Reviewed-by: default avatarXiaohan Wang <xhwang@chromium.org>
Commit-Queue: John Rummell <jrummell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#607354}
parent 33ae9b1b
<!DOCTYPE html>
<html>
<head>
<title>Test MediaKeySession.update() fails after close()</title>
<script src="encrypted-media-utils.js"></script>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
</head>
<body>
<script>
// This test verifies that calls on MediaKeySession fail after
// close() has been called. For simplicity we'll just call update()
// after close(), and expect it to fail.
promise_test(() => {
var initDataType;
var initData;
var mediaKeySession;
return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration()).then((access) => {
initDataType = access.getConfiguration().initDataTypes[0];
initData = getInitData(initDataType);
return access.createMediaKeys();
}).then((mediaKeys) => {
mediaKeySession = mediaKeys.createSession();
return mediaKeySession.generateRequest(initDataType, initData);
}).then(() => {
var p1 = mediaKeySession.close();
// Update() first checks for closing or closed and returns
// InvalidStateError if true. Later it checks for empty
// array and returns TypeError if true. So pass empty array
// to verify that closing is checked first.
var p2 = mediaKeySession.update(new Uint8Array([])).then(
() => { assert_unreached('Update() resolved unexpectedly'); },
(error) => { assert_equals(error.name, 'InvalidStateError'); } );
return Promise.all([p1, p2]);
});
}, 'Test calls fail after MediaKeySession.close().');
</script>
</body>
</html>
...@@ -357,7 +357,7 @@ MediaKeySession::MediaKeySession(ScriptState* script_state, ...@@ -357,7 +357,7 @@ MediaKeySession::MediaKeySession(ScriptState* script_state,
key_statuses_map_(new MediaKeyStatusMap()), key_statuses_map_(new MediaKeyStatusMap()),
is_uninitialized_(true), is_uninitialized_(true),
is_callable_(false), is_callable_(false),
is_closed_(false), is_closing_or_closed_(false),
closed_promise_(new ClosedPromise(ExecutionContext::From(script_state), closed_promise_(new ClosedPromise(ExecutionContext::From(script_state),
this, this,
ClosedPromise::kClosed)), ClosedPromise::kClosed)),
...@@ -440,9 +440,9 @@ ScriptPromise MediaKeySession::generateRequest( ...@@ -440,9 +440,9 @@ ScriptPromise MediaKeySession::generateRequest(
// Generates a request based on the initData. When this method is invoked, // Generates a request based on the initData. When this method is invoked,
// the user agent must run the following steps: // the user agent must run the following steps:
// 1. If this object is closed, return a promise rejected with an // 1. If this object's closing or closed value is true, return a promise
// InvalidStateError. // rejected with an InvalidStateError.
if (is_closed_) if (is_closing_or_closed_)
return CreateRejectedPromiseAlreadyClosed(script_state); return CreateRejectedPromiseAlreadyClosed(script_state);
// 2. If this object's uninitialized value is false, return a promise // 2. If this object's uninitialized value is false, return a promise
...@@ -556,9 +556,9 @@ ScriptPromise MediaKeySession::load(ScriptState* script_state, ...@@ -556,9 +556,9 @@ ScriptPromise MediaKeySession::load(ScriptState* script_state,
// Loads the data stored for the specified session into this object. When // Loads the data stored for the specified session into this object. When
// this method is invoked, the user agent must run the following steps: // this method is invoked, the user agent must run the following steps:
// 1. If this object is closed, return a promise rejected with an // 1. If this object's closing or closed value is true, return a promise
// InvalidStateError. // rejected with an InvalidStateError.
if (is_closed_) if (is_closing_or_closed_)
return CreateRejectedPromiseAlreadyClosed(script_state); return CreateRejectedPromiseAlreadyClosed(script_state);
// 2. If this object's uninitialized value is false, return a promise // 2. If this object's uninitialized value is false, return a promise
...@@ -688,9 +688,9 @@ ScriptPromise MediaKeySession::update(ScriptState* script_state, ...@@ -688,9 +688,9 @@ ScriptPromise MediaKeySession::update(ScriptState* script_state,
// Provides messages, including licenses, to the CDM. When this method is // Provides messages, including licenses, to the CDM. When this method is
// invoked, the user agent must run the following steps: // invoked, the user agent must run the following steps:
// 1. If this object is closed, return a promise rejected with an // 1. If this object's closing or closed value is true, return a promise
// InvalidStateError. // rejected with an InvalidStateError.
if (is_closed_) if (is_closing_or_closed_)
return CreateRejectedPromiseAlreadyClosed(script_state); return CreateRejectedPromiseAlreadyClosed(script_state);
// 2. If this object's callable value is false, return a promise // 2. If this object's callable value is false, return a promise
...@@ -747,21 +747,24 @@ ScriptPromise MediaKeySession::close(ScriptState* script_state) { ...@@ -747,21 +747,24 @@ ScriptPromise MediaKeySession::close(ScriptState* script_state) {
// Persisted data should not be released or cleared. // Persisted data should not be released or cleared.
// When this method is invoked, the user agent must run the following steps: // When this method is invoked, the user agent must run the following steps:
// 1. Let session be the associated MediaKeySession object. // 1. If this object's closing or closed value is true, return a resolved
// 2. If session is closed, return a resolved promise. // promise.
if (is_closed_) if (is_closing_or_closed_)
return ScriptPromise::CastUndefined(script_state); return ScriptPromise::CastUndefined(script_state);
// 3. If session's callable value is false, return a promise rejected with // 2. If this object's callable value is false, return a promise rejected
// an InvalidStateError. // with an InvalidStateError.
if (!is_callable_) if (!is_callable_)
return CreateRejectedPromiseNotCallable(script_state); return CreateRejectedPromiseNotCallable(script_state);
// 4. Let promise be a new promise. // 3. Let promise be a new promise.
SimpleResultPromise* result = SimpleResultPromise* result =
new SimpleResultPromise(script_state, this, "MediaKeySession", "close"); new SimpleResultPromise(script_state, this, "MediaKeySession", "close");
ScriptPromise promise = result->Promise(); ScriptPromise promise = result->Promise();
// 4. Set this object's closing or closed value to true.
is_closing_or_closed_ = true;
// 5. Run the following steps in parallel (done in closeTask()). // 5. Run the following steps in parallel (done in closeTask()).
pending_actions_.push_back(PendingAction::CreatePendingClose(result)); pending_actions_.push_back(PendingAction::CreatePendingClose(result));
if (!action_timer_.IsActive()) if (!action_timer_.IsActive())
...@@ -788,9 +791,9 @@ ScriptPromise MediaKeySession::remove(ScriptState* script_state) { ...@@ -788,9 +791,9 @@ ScriptPromise MediaKeySession::remove(ScriptState* script_state) {
// Removes stored session data associated with this object. When this // Removes stored session data associated with this object. When this
// method is invoked, the user agent must run the following steps: // method is invoked, the user agent must run the following steps:
// 1. If this object is closed, return a promise rejected with an // 1. If this object's closing or closed value is true, return a promise
// InvalidStateError. // rejected with an InvalidStateError.
if (is_closed_) if (is_closing_or_closed_)
return CreateRejectedPromiseAlreadyClosed(script_state); return CreateRejectedPromiseAlreadyClosed(script_state);
// 2. If this object's callable value is false, return a promise rejected // 2. If this object's callable value is false, return a promise rejected
...@@ -909,29 +912,23 @@ void MediaKeySession::Close() { ...@@ -909,29 +912,23 @@ void MediaKeySession::Close() {
// From http://w3c.github.io/encrypted-media/#session-closed // From http://w3c.github.io/encrypted-media/#session-closed
// 1. Let session be the associated MediaKeySession object. // 1. Let session be the associated MediaKeySession object.
// 2. If session's session type is "persistent-usage-record", execute the // 2. Let promise be the session's closed attribute.
// following steps in parallel: // 3. If promise is resolved, abort these steps.
// 1. Let cdm be the CDM instance represented by session's cdm instance if (closed_promise_->GetState() == ScriptPromisePropertyBase::kResolved)
// value. return;
// 2. Use cdm to store session's record of key usage, if it exists.
// ("persistent-usage-record" not supported by Chrome.) // 4. Set the session's closing or closed value to true.
is_closing_or_closed_ = true;
// 3. Run the Update Key Statuses algorithm on the session, providing an
// empty sequence. // 5. Run the Update Key Statuses algorithm on the session, providing
// an empty sequence.
KeysStatusesChange(WebVector<WebEncryptedMediaKeyInformation>(), false); KeysStatusesChange(WebVector<WebEncryptedMediaKeyInformation>(), false);
// 4. Run the Update Expiration algorithm on the session, providing NaN. // 6. Run the Update Expiration algorithm on the session, providing NaN.
ExpirationChanged(std::numeric_limits<double>::quiet_NaN()); ExpirationChanged(std::numeric_limits<double>::quiet_NaN());
// 5. Let promise be the closed attribute of the session. // 7. Resolve promise.
// 6. Resolve promise. closed_promise_->ResolveWithUndefined();
closed_promise_->Resolve(ToV8UndefinedGenerator());
// After this algorithm has run, event handlers for the events queued by
// this algorithm will be executed, but no further events can be queued.
// As a result, no messages can be sent by the CDM as a result of closing
// the session.
is_closed_ = true;
} }
void MediaKeySession::ExpirationChanged(double updated_expiry_time_in_ms) { void MediaKeySession::ExpirationChanged(double updated_expiry_time_in_ms) {
...@@ -1013,16 +1010,19 @@ bool MediaKeySession::HasPendingActivity() const { ...@@ -1013,16 +1010,19 @@ bool MediaKeySession::HasPendingActivity() const {
<< (async_event_queue_->HasPendingEvents() << (async_event_queue_->HasPendingEvents()
? " async_event_queue_->HasPendingEvents()" ? " async_event_queue_->HasPendingEvents()"
: "") : "")
<< ((media_keys_ && !is_closed_) ? " media_keys_ && !is_closed_" : ""); << ((media_keys_ && !is_closing_or_closed_)
? " media_keys_ && !is_closing_or_closed_"
: "");
return !pending_actions_.IsEmpty() || return !pending_actions_.IsEmpty() ||
async_event_queue_->HasPendingEvents() || (media_keys_ && !is_closed_); async_event_queue_->HasPendingEvents() ||
(media_keys_ && !is_closing_or_closed_);
} }
void MediaKeySession::ContextDestroyed(ExecutionContext*) { void MediaKeySession::ContextDestroyed(ExecutionContext*) {
// Stop the CDM from firing any more events for this session. // Stop the CDM from firing any more events for this session.
session_.reset(); session_.reset();
is_closed_ = true; is_closing_or_closed_ = true;
action_timer_.Stop(); action_timer_.Stop();
pending_actions_.clear(); pending_actions_.clear();
} }
......
...@@ -149,7 +149,7 @@ class MediaKeySession final ...@@ -149,7 +149,7 @@ class MediaKeySession final
// Session states. // Session states.
bool is_uninitialized_; bool is_uninitialized_;
bool is_callable_; bool is_callable_;
bool is_closed_; // Is the CDM finished with this session? bool is_closing_or_closed_;
// Keep track of the closed promise. // Keep track of the closed promise.
typedef ScriptPromiseProperty<Member<MediaKeySession>, typedef ScriptPromiseProperty<Member<MediaKeySession>,
......
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