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,
key_statuses_map_(new MediaKeyStatusMap()),
is_uninitialized_(true),
is_callable_(false),
is_closed_(false),
is_closing_or_closed_(false),
closed_promise_(new ClosedPromise(ExecutionContext::From(script_state),
this,
ClosedPromise::kClosed)),
......@@ -440,9 +440,9 @@ ScriptPromise MediaKeySession::generateRequest(
// Generates a request based on the initData. When this method is invoked,
// the user agent must run the following steps:
// 1. If this object is closed, return a promise rejected with an
// InvalidStateError.
if (is_closed_)
// 1. If this object's closing or closed value is true, return a promise
// rejected with an InvalidStateError.
if (is_closing_or_closed_)
return CreateRejectedPromiseAlreadyClosed(script_state);
// 2. If this object's uninitialized value is false, return a promise
......@@ -556,9 +556,9 @@ ScriptPromise MediaKeySession::load(ScriptState* script_state,
// Loads the data stored for the specified session into this object. When
// this method is invoked, the user agent must run the following steps:
// 1. If this object is closed, return a promise rejected with an
// InvalidStateError.
if (is_closed_)
// 1. If this object's closing or closed value is true, return a promise
// rejected with an InvalidStateError.
if (is_closing_or_closed_)
return CreateRejectedPromiseAlreadyClosed(script_state);
// 2. If this object's uninitialized value is false, return a promise
......@@ -688,9 +688,9 @@ ScriptPromise MediaKeySession::update(ScriptState* script_state,
// Provides messages, including licenses, to the CDM. When this method is
// invoked, the user agent must run the following steps:
// 1. If this object is closed, return a promise rejected with an
// InvalidStateError.
if (is_closed_)
// 1. If this object's closing or closed value is true, return a promise
// rejected with an InvalidStateError.
if (is_closing_or_closed_)
return CreateRejectedPromiseAlreadyClosed(script_state);
// 2. If this object's callable value is false, return a promise
......@@ -747,21 +747,24 @@ ScriptPromise MediaKeySession::close(ScriptState* script_state) {
// Persisted data should not be released or cleared.
// When this method is invoked, the user agent must run the following steps:
// 1. Let session be the associated MediaKeySession object.
// 2. If session is closed, return a resolved promise.
if (is_closed_)
// 1. If this object's closing or closed value is true, return a resolved
// promise.
if (is_closing_or_closed_)
return ScriptPromise::CastUndefined(script_state);
// 3. If session's callable value is false, return a promise rejected with
// an InvalidStateError.
// 2. If this object's callable value is false, return a promise rejected
// with an InvalidStateError.
if (!is_callable_)
return CreateRejectedPromiseNotCallable(script_state);
// 4. Let promise be a new promise.
// 3. Let promise be a new promise.
SimpleResultPromise* result =
new SimpleResultPromise(script_state, this, "MediaKeySession", "close");
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()).
pending_actions_.push_back(PendingAction::CreatePendingClose(result));
if (!action_timer_.IsActive())
......@@ -788,9 +791,9 @@ ScriptPromise MediaKeySession::remove(ScriptState* script_state) {
// Removes stored session data associated with this object. When this
// method is invoked, the user agent must run the following steps:
// 1. If this object is closed, return a promise rejected with an
// InvalidStateError.
if (is_closed_)
// 1. If this object's closing or closed value is true, return a promise
// rejected with an InvalidStateError.
if (is_closing_or_closed_)
return CreateRejectedPromiseAlreadyClosed(script_state);
// 2. If this object's callable value is false, return a promise rejected
......@@ -909,29 +912,23 @@ void MediaKeySession::Close() {
// From http://w3c.github.io/encrypted-media/#session-closed
// 1. Let session be the associated MediaKeySession object.
// 2. If session's session type is "persistent-usage-record", execute the
// following steps in parallel:
// 1. Let cdm be the CDM instance represented by session's cdm instance
// value.
// 2. Use cdm to store session's record of key usage, if it exists.
// ("persistent-usage-record" not supported by Chrome.)
// 3. Run the Update Key Statuses algorithm on the session, providing an
// empty sequence.
// 2. Let promise be the session's closed attribute.
// 3. If promise is resolved, abort these steps.
if (closed_promise_->GetState() == ScriptPromisePropertyBase::kResolved)
return;
// 4. Set the session's closing or closed value to true.
is_closing_or_closed_ = true;
// 5. Run the Update Key Statuses algorithm on the session, providing
// an empty sequence.
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());
// 5. Let promise be the closed attribute of the session.
// 6. Resolve promise.
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;
// 7. Resolve promise.
closed_promise_->ResolveWithUndefined();
}
void MediaKeySession::ExpirationChanged(double updated_expiry_time_in_ms) {
......@@ -1013,16 +1010,19 @@ bool MediaKeySession::HasPendingActivity() const {
<< (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() ||
async_event_queue_->HasPendingEvents() || (media_keys_ && !is_closed_);
async_event_queue_->HasPendingEvents() ||
(media_keys_ && !is_closing_or_closed_);
}
void MediaKeySession::ContextDestroyed(ExecutionContext*) {
// Stop the CDM from firing any more events for this session.
session_.reset();
is_closed_ = true;
is_closing_or_closed_ = true;
action_timer_.Stop();
pending_actions_.clear();
}
......
......@@ -149,7 +149,7 @@ class MediaKeySession final
// Session states.
bool is_uninitialized_;
bool is_callable_;
bool is_closed_; // Is the CDM finished with this session?
bool is_closing_or_closed_;
// Keep track of the closed promise.
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