1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "modules/encryptedmedia/MediaKeySession.h" 28 29#include "bindings/core/v8/DOMWrapperWorld.h" 30#include "bindings/core/v8/ScriptPromise.h" 31#include "bindings/core/v8/ScriptPromiseResolver.h" 32#include "bindings/core/v8/ScriptState.h" 33#include "core/dom/ExceptionCode.h" 34#include "core/events/Event.h" 35#include "core/events/GenericEventQueue.h" 36#include "core/html/MediaKeyError.h" 37#include "modules/encryptedmedia/MediaKeyMessageEvent.h" 38#include "modules/encryptedmedia/MediaKeys.h" 39#include "modules/encryptedmedia/SimpleContentDecryptionModuleResult.h" 40#include "platform/ContentDecryptionModuleResult.h" 41#include "platform/ContentType.h" 42#include "platform/Logging.h" 43#include "platform/MIMETypeRegistry.h" 44#include "platform/Timer.h" 45#include "public/platform/WebContentDecryptionModule.h" 46#include "public/platform/WebContentDecryptionModuleException.h" 47#include "public/platform/WebContentDecryptionModuleSession.h" 48#include "public/platform/WebString.h" 49#include "public/platform/WebURL.h" 50#include "wtf/ArrayBuffer.h" 51#include "wtf/ArrayBufferView.h" 52 53namespace blink { 54 55static bool isKeySystemSupportedWithInitDataType(const String& keySystem, const String& initDataType) 56{ 57 ASSERT(!keySystem.isEmpty()); 58 59 // FIXME: initDataType != contentType. Implement this properly. 60 // http://crbug.com/385874. 61 String contentType = initDataType; 62 if (initDataType == "webm") { 63 contentType = "video/webm"; 64 } else if (initDataType == "cenc") { 65 contentType = "video/mp4"; 66 } 67 68 ContentType type(contentType); 69 return MIMETypeRegistry::isSupportedEncryptedMediaMIMEType(keySystem, type.type(), type.parameter("codecs")); 70} 71 72// A class holding a pending action. 73class MediaKeySession::PendingAction : public GarbageCollectedFinalized<MediaKeySession::PendingAction> { 74public: 75 enum Type { 76 GenerateRequest, 77 Update, 78 Release 79 }; 80 81 Type type() const { return m_type; } 82 83 const Persistent<ContentDecryptionModuleResult> result() const 84 { 85 return m_result; 86 } 87 88 const RefPtr<ArrayBuffer> data() const 89 { 90 ASSERT(m_type == GenerateRequest || m_type == Update); 91 return m_data; 92 } 93 94 const String& initDataType() const 95 { 96 ASSERT(m_type == GenerateRequest); 97 return m_initDataType; 98 } 99 100 static PendingAction* CreatePendingGenerateRequest(ContentDecryptionModuleResult* result, const String& initDataType, PassRefPtr<ArrayBuffer> initData) 101 { 102 ASSERT(result); 103 ASSERT(initData); 104 return new PendingAction(GenerateRequest, result, initDataType, initData); 105 } 106 107 static PendingAction* CreatePendingUpdate(ContentDecryptionModuleResult* result, PassRefPtr<ArrayBuffer> data) 108 { 109 ASSERT(result); 110 ASSERT(data); 111 return new PendingAction(Update, result, String(), data); 112 } 113 114 static PendingAction* CreatePendingRelease(ContentDecryptionModuleResult* result) 115 { 116 ASSERT(result); 117 return new PendingAction(Release, result, String(), PassRefPtr<ArrayBuffer>()); 118 } 119 120 ~PendingAction() 121 { 122 } 123 124 void trace(Visitor* visitor) 125 { 126 visitor->trace(m_result); 127 } 128 129private: 130 PendingAction(Type type, ContentDecryptionModuleResult* result, const String& initDataType, PassRefPtr<ArrayBuffer> data) 131 : m_type(type) 132 , m_result(result) 133 , m_initDataType(initDataType) 134 , m_data(data) 135 { 136 } 137 138 const Type m_type; 139 const Member<ContentDecryptionModuleResult> m_result; 140 const String m_initDataType; 141 const RefPtr<ArrayBuffer> m_data; 142}; 143 144// This class wraps the promise resolver used when initializing a new session 145// and is passed to Chromium to fullfill the promise. This implementation of 146// completeWithSession() will resolve the promise with void, while 147// completeWithError() will reject the promise with an exception. complete() 148// is not expected to be called, and will reject the promise. 149class NewSessionResult : public ContentDecryptionModuleResult { 150public: 151 NewSessionResult(ScriptState* scriptState, MediaKeySession* session) 152 : m_resolver(ScriptPromiseResolver::create(scriptState)) 153 , m_session(session) 154 { 155 WTF_LOG(Media, "NewSessionResult(%p)", this); 156 } 157 158 virtual ~NewSessionResult() 159 { 160 WTF_LOG(Media, "~NewSessionResult(%p)", this); 161 } 162 163 // ContentDecryptionModuleResult implementation. 164 virtual void complete() OVERRIDE 165 { 166 ASSERT_NOT_REACHED(); 167 completeWithDOMException(InvalidStateError, "Unexpected completion."); 168 } 169 170 virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE 171 { 172 if (status != WebContentDecryptionModuleResult::NewSession) { 173 ASSERT_NOT_REACHED(); 174 completeWithDOMException(InvalidStateError, "Unexpected completion."); 175 } 176 177 m_session->finishGenerateRequest(); 178 m_resolver->resolve(); 179 m_resolver.clear(); 180 } 181 182 virtual void completeWithError(WebContentDecryptionModuleException exceptionCode, unsigned long systemCode, const WebString& errorMessage) OVERRIDE 183 { 184 completeWithDOMException(WebCdmExceptionToExceptionCode(exceptionCode), errorMessage); 185 } 186 187 // It is only valid to call this before completion. 188 ScriptPromise promise() { return m_resolver->promise(); } 189 190 void trace(Visitor* visitor) 191 { 192 visitor->trace(m_session); 193 ContentDecryptionModuleResult::trace(visitor); 194 } 195 196private: 197 // Reject the promise with a DOMException. 198 void completeWithDOMException(ExceptionCode code, const String& errorMessage) 199 { 200 m_resolver->reject(DOMException::create(code, errorMessage)); 201 m_resolver.clear(); 202 } 203 204 RefPtr<ScriptPromiseResolver> m_resolver; 205 Member<MediaKeySession> m_session; 206}; 207 208MediaKeySession* MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType) 209{ 210 RefPtrWillBeRawPtr<MediaKeySession> session = adoptRefCountedGarbageCollectedWillBeNoop(new MediaKeySession(scriptState, mediaKeys, sessionType)); 211 session->suspendIfNeeded(); 212 return session.get(); 213} 214 215MediaKeySession::MediaKeySession(ScriptState* scriptState, MediaKeys* mediaKeys, const String& sessionType) 216 : ActiveDOMObject(scriptState->executionContext()) 217 , m_keySystem(mediaKeys->keySystem()) 218 , m_asyncEventQueue(GenericEventQueue::create(this)) 219 , m_mediaKeys(mediaKeys) 220 , m_sessionType(sessionType) 221 , m_isUninitialized(true) 222 , m_isCallable(false) 223 , m_isClosed(false) 224 , m_closedPromise(new ClosedPromise(scriptState->executionContext(), this, ClosedPromise::Closed)) 225 , m_actionTimer(this, &MediaKeySession::actionTimerFired) 226{ 227 WTF_LOG(Media, "MediaKeySession(%p)::MediaKeySession", this); 228 229 // Create the matching Chromium object. It will not be usable until 230 // initializeNewSession() is called in response to the user calling 231 // generateRequest(). 232 WebContentDecryptionModule* cdm = mediaKeys->contentDecryptionModule(); 233 m_session = adoptPtr(cdm->createSession()); 234 m_session->setClientInterface(this); 235 236 // MediaKeys::createSession(), step 2. 237 // 2.1 Let the sessionId attribute be the empty string. 238 ASSERT(sessionId().isEmpty()); 239 240 // 2.2 Let the expiration attribute be NaN. 241 // FIXME: Add expiration property. 242 243 // 2.3 Let the closed attribute be a new promise. 244 ASSERT(!closed(scriptState).isUndefinedOrNull()); 245 246 // 2.4 Let the session type be sessionType. 247 ASSERT(sessionType == m_sessionType); 248 249 // 2.5 Let uninitialized be true. 250 ASSERT(m_isUninitialized); 251 252 // 2.6 Let callable be false. 253 ASSERT(!m_isCallable); 254} 255 256MediaKeySession::~MediaKeySession() 257{ 258 WTF_LOG(Media, "MediaKeySession(%p)::~MediaKeySession", this); 259 m_session.clear(); 260#if !ENABLE(OILPAN) 261 // MediaKeySession and m_asyncEventQueue always become unreachable 262 // together. So MediaKeySession and m_asyncEventQueue are destructed in the 263 // same GC. We don't need to call cancelAllEvents explicitly in Oilpan. 264 m_asyncEventQueue->cancelAllEvents(); 265#endif 266} 267 268void MediaKeySession::setError(MediaKeyError* error) 269{ 270 m_error = error; 271} 272 273String MediaKeySession::sessionId() const 274{ 275 return m_session->sessionId(); 276} 277 278ScriptPromise MediaKeySession::closed(ScriptState* scriptState) 279{ 280 return m_closedPromise->promise(scriptState->world()); 281} 282 283ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, ArrayBuffer* initData) 284{ 285 RefPtr<ArrayBuffer> initDataCopy = ArrayBuffer::create(initData->data(), initData->byteLength()); 286 return generateRequestInternal(scriptState, initDataType, initDataCopy.release()); 287} 288 289ScriptPromise MediaKeySession::generateRequest(ScriptState* scriptState, const String& initDataType, ArrayBufferView* initData) 290{ 291 RefPtr<ArrayBuffer> initDataCopy = ArrayBuffer::create(initData->baseAddress(), initData->byteLength()); 292 return generateRequestInternal(scriptState, initDataType, initDataCopy.release()); 293} 294 295ScriptPromise MediaKeySession::generateRequestInternal(ScriptState* scriptState, const String& initDataType, PassRefPtr<ArrayBuffer> initData) 296{ 297 WTF_LOG(Media, "MediaKeySession(%p)::generateRequest %s", this, initDataType.ascii().data()); 298 299 // From https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-generaterequest: 300 // The generateRequest(initDataType, initData) method creates a new session 301 // for the specified initData. It must run the following steps: 302 303 // 1. If this object's uninitialized value is false, return a promise 304 // rejected with a new DOMException whose name is "InvalidStateError". 305 if (!m_isUninitialized) { 306 return ScriptPromise::rejectWithDOMException( 307 scriptState, DOMException::create(InvalidStateError, "The session is already initialized.")); 308 } 309 310 // 2. Let this object's uninitialized be false. 311 m_isUninitialized = false; 312 313 // 3. If initDataType is an empty string, return a promise rejected with a 314 // new DOMException whose name is "InvalidAccessError". 315 if (initDataType.isEmpty()) { 316 return ScriptPromise::rejectWithDOMException( 317 scriptState, DOMException::create(InvalidAccessError, "The initDataType parameter is empty.")); 318 } 319 320 // 4. If initData is an empty array, return a promise rejected with a new 321 // DOMException whose name is"InvalidAccessError". 322 if (!initData->byteLength()) { 323 return ScriptPromise::rejectWithDOMException( 324 scriptState, DOMException::create(InvalidAccessError, "The initData parameter is empty.")); 325 } 326 327 // 5. Let media keys be the MediaKeys object that created this object. 328 // (Use m_mediaKey, which was set in the constructor.) 329 330 // 6. If the content decryption module corresponding to media keys's 331 // keySystem attribute does not support initDataType as an initialization 332 // data type, return a promise rejected with a new DOMException whose 333 // name is "NotSupportedError". String comparison is case-sensitive. 334 if (!isKeySystemSupportedWithInitDataType(m_keySystem, initDataType)) { 335 return ScriptPromise::rejectWithDOMException( 336 scriptState, DOMException::create(NotSupportedError, "The initialization data type '" + initDataType + "' is not supported by the key system.")); 337 } 338 339 // 7. Let init data be a copy of the contents of the initData parameter. 340 // (Done before calling this method.) 341 342 // 8. Let session type be this object's session type. 343 // (Done in constructor.) 344 345 // 9. Let promise be a new promise. 346 NewSessionResult* result = new NewSessionResult(scriptState, this); 347 ScriptPromise promise = result->promise(); 348 349 // 10. Run the following steps asynchronously (documented in 350 // actionTimerFired()) 351 m_pendingActions.append(PendingAction::CreatePendingGenerateRequest(result, initDataType, initData)); 352 ASSERT(!m_actionTimer.isActive()); 353 m_actionTimer.startOneShot(0, FROM_HERE); 354 355 // 11. Return promise. 356 return promise; 357} 358 359ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBuffer* response) 360{ 361 RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->data(), response->byteLength()); 362 return updateInternal(scriptState, responseCopy.release()); 363} 364 365ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBufferView* response) 366{ 367 RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->baseAddress(), response->byteLength()); 368 return updateInternal(scriptState, responseCopy.release()); 369} 370 371ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefPtr<ArrayBuffer> response) 372{ 373 WTF_LOG(Media, "MediaKeySession(%p)::update", this); 374 ASSERT(!m_isClosed); 375 376 // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-update>: 377 // The update(response) method provides messages, including licenses, to the 378 // CDM. It must run the following steps: 379 // 380 // 1. If response is an empty array, return a promise rejected with a new 381 // DOMException whose name is "InvalidAccessError" and that has the 382 // message "The response parameter is empty." 383 if (!response->byteLength()) { 384 return ScriptPromise::rejectWithDOMException( 385 scriptState, DOMException::create(InvalidAccessError, "The response parameter is empty.")); 386 } 387 388 // 2. Let message be a copy of the contents of the response parameter. 389 // (Copied in the caller.) 390 391 // 3. Let promise be a new promise. 392 SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState); 393 ScriptPromise promise = result->promise(); 394 395 // 4. Run the following steps asynchronously (documented in 396 // actionTimerFired()) 397 m_pendingActions.append(PendingAction::CreatePendingUpdate(result, response)); 398 if (!m_actionTimer.isActive()) 399 m_actionTimer.startOneShot(0, FROM_HERE); 400 401 // 5. Return promise. 402 return promise; 403} 404 405ScriptPromise MediaKeySession::release(ScriptState* scriptState) 406{ 407 WTF_LOG(Media, "MediaKeySession(%p)::release", this); 408 SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState); 409 ScriptPromise promise = result->promise(); 410 411 // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-close>: 412 // The close() method allows an application to indicate that it no longer 413 // needs the session and the CDM should release any resources associated 414 // with this object and close it. The returned promise is resolved when the 415 // request has been processed, and the closed attribute promise is resolved 416 // when the session is closed. It must run the following steps: 417 // 418 // 1. If the Session Close algorithm has been run on this object, return a 419 // promise fulfilled with undefined. 420 if (m_isClosed) { 421 result->complete(); 422 return promise; 423 } 424 425 // 2. Let promise be a new promise. 426 // (Created earlier so it was available in step 1.) 427 428 // 3. Run the following steps asynchronously (documented in 429 // actionTimerFired()). 430 m_pendingActions.append(PendingAction::CreatePendingRelease(result)); 431 if (!m_actionTimer.isActive()) 432 m_actionTimer.startOneShot(0, FROM_HERE); 433 434 // 4. Return promise. 435 return promise; 436} 437 438void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*) 439{ 440 ASSERT(m_pendingActions.size()); 441 442 // Resolving promises now run synchronously and may result in additional 443 // actions getting added to the queue. As a result, swap the queue to 444 // a local copy to avoid problems if this happens. 445 HeapDeque<Member<PendingAction> > pendingActions; 446 pendingActions.swap(m_pendingActions); 447 448 while (!pendingActions.isEmpty()) { 449 PendingAction* action = pendingActions.takeFirst(); 450 451 switch (action->type()) { 452 case PendingAction::GenerateRequest: 453 WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: GenerateRequest", this); 454 455 // 10.1 Let request be null. 456 // 10.2 Let cdm be the CDM loaded during the initialization of 457 // media keys. 458 // 10.3 Use the cdm to execute the following steps: 459 // 10.3.1 If the init data is not valid for initDataType, reject 460 // promise with a new DOMException whose name is 461 // "InvalidAccessError". 462 // 10.3.2 If the init data is not supported by the cdm, reject 463 // promise with a new DOMException whose name is 464 // "NotSupportedError". 465 // 10.3.3 Let request be a request (e.g. a license request) 466 // generated based on the init data, which is interpreted 467 // per initDataType, and session type. 468 m_session->initializeNewSession(action->initDataType(), static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), m_sessionType, action->result()->result()); 469 470 // Remainder of steps executed in finishGenerateRequest(), called 471 // when |result| is resolved. 472 break; 473 474 case PendingAction::Update: 475 WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Update", this); 476 // NOTE: Continued from step 4 of MediaKeySession::update(). 477 // Continue the update call by passing message to the cdm. Once 478 // completed, it will resolve/reject the promise. 479 m_session->update(static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), action->result()->result()); 480 break; 481 482 case PendingAction::Release: 483 WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Release", this); 484 // NOTE: Continued from step 3 of MediaKeySession::release(). 485 // 3.1 Let cdm be the cdm loaded in create(). 486 // 3.2 Use the cdm to execute the following steps: 487 // 3.2.1 Process the close request. Do not remove stored session data. 488 // 3.2.2 If the previous step caused the session to be closed, run the 489 // Session Close algorithm on this object. 490 // 3.3 Resolve promise with undefined. 491 m_session->release(action->result()->result()); 492 break; 493 } 494 } 495} 496 497void MediaKeySession::finishGenerateRequest() 498{ 499 // 10.4 Set the sessionId attribute to a unique Session ID string. 500 // It may be obtained from cdm. 501 ASSERT(!sessionId().isEmpty()); 502 503 // 10.5 If any of the preceding steps failed, reject promise with a new 504 // DOMException whose name is the appropriate error name. 505 // (Done by call to completeWithError()). 506 507 // 10.6 Add an entry for the value of the sessionId attribute to 508 // media keys's list of active session IDs. 509 // FIXME: Is this required? 510 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=26758 511 512 // 10.7 Run the Queue a "message" Event algorithm on the session, 513 // providing request and null. 514 // (Done by the CDM). 515 516 // 10.8 Let this object's callable be true. 517 m_isCallable = true; 518} 519 520// Queue a task to fire a simple event named keymessage at the new object. 521void MediaKeySession::message(const unsigned char* message, size_t messageLength, const WebURL& destinationURL) 522{ 523 WTF_LOG(Media, "MediaKeySession(%p)::message", this); 524 525 // Verify that 'message' not fired before session initialization is complete. 526 ASSERT(m_isCallable); 527 528 MediaKeyMessageEventInit init; 529 init.bubbles = false; 530 init.cancelable = false; 531 init.message = ArrayBuffer::create(static_cast<const void*>(message), messageLength); 532 init.destinationURL = destinationURL.string(); 533 534 RefPtrWillBeRawPtr<MediaKeyMessageEvent> event = MediaKeyMessageEvent::create(EventTypeNames::message, init); 535 event->setTarget(this); 536 m_asyncEventQueue->enqueueEvent(event.release()); 537} 538 539void MediaKeySession::ready() 540{ 541 WTF_LOG(Media, "MediaKeySession(%p)::ready", this); 542 543 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::ready); 544 event->setTarget(this); 545 m_asyncEventQueue->enqueueEvent(event.release()); 546} 547 548void MediaKeySession::close() 549{ 550 WTF_LOG(Media, "MediaKeySession(%p)::close", this); 551 552 // Once closed, the session can no longer be the target of events from 553 // the CDM so this object can be garbage collected. 554 m_isClosed = true; 555 556 // Resolve the closed promise. 557 m_closedPromise->resolve(V8UndefinedType()); 558} 559 560// Queue a task to fire a simple event named keyadded at the MediaKeySession object. 561void MediaKeySession::error(MediaKeyErrorCode errorCode, unsigned long systemCode) 562{ 563 WTF_LOG(Media, "MediaKeySession(%p)::error: errorCode=%d, systemCode=%lu", this, errorCode, systemCode); 564 565 MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN; 566 switch (errorCode) { 567 case MediaKeyErrorCodeUnknown: 568 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN; 569 break; 570 case MediaKeyErrorCodeClient: 571 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT; 572 break; 573 } 574 575 // 1. Create a new MediaKeyError object with the following attributes: 576 // code = the appropriate MediaKeyError code 577 // systemCode = a Key System-specific value, if provided, and 0 otherwise 578 // 2. Set the MediaKeySession object's error attribute to the error object created in the previous step. 579 m_error = MediaKeyError::create(mediaKeyErrorCode, systemCode); 580 581 // 3. queue a task to fire a simple event named keyerror at the MediaKeySession object. 582 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::error); 583 event->setTarget(this); 584 m_asyncEventQueue->enqueueEvent(event.release()); 585} 586 587void MediaKeySession::error(WebContentDecryptionModuleException exception, unsigned long systemCode, const WebString& errorMessage) 588{ 589 WTF_LOG(Media, "MediaKeySession::error: exception=%d, systemCode=%lu", exception, systemCode); 590 591 // FIXME: EME-WD MediaKeyError now derives from DOMException. Figure out how 592 // to implement this without breaking prefixed EME, which has a totally 593 // different definition. The spec may also change to be just a DOMException. 594 // For now, simply generate an existing MediaKeyError. 595 MediaKeyErrorCode errorCode; 596 switch (exception) { 597 case WebContentDecryptionModuleExceptionClientError: 598 errorCode = MediaKeyErrorCodeClient; 599 break; 600 default: 601 // All other exceptions get converted into Unknown. 602 errorCode = MediaKeyErrorCodeUnknown; 603 break; 604 } 605 error(errorCode, systemCode); 606} 607 608const AtomicString& MediaKeySession::interfaceName() const 609{ 610 return EventTargetNames::MediaKeySession; 611} 612 613ExecutionContext* MediaKeySession::executionContext() const 614{ 615 return ActiveDOMObject::executionContext(); 616} 617 618bool MediaKeySession::hasPendingActivity() const 619{ 620 // Remain around if there are pending events or MediaKeys is still around 621 // and we're not closed. 622 WTF_LOG(Media, "MediaKeySession(%p)::hasPendingActivity %s%s%s%s", this, 623 ActiveDOMObject::hasPendingActivity() ? " ActiveDOMObject::hasPendingActivity()" : "", 624 !m_pendingActions.isEmpty() ? " !m_pendingActions.isEmpty()" : "", 625 m_asyncEventQueue->hasPendingEvents() ? " m_asyncEventQueue->hasPendingEvents()" : "", 626 (m_mediaKeys && !m_isClosed) ? " m_mediaKeys && !m_isClosed" : ""); 627 628 return ActiveDOMObject::hasPendingActivity() 629 || !m_pendingActions.isEmpty() 630 || m_asyncEventQueue->hasPendingEvents() 631 || (m_mediaKeys && !m_isClosed); 632} 633 634void MediaKeySession::stop() 635{ 636 // Stop the CDM from firing any more events for this session. 637 m_session.clear(); 638 m_isClosed = true; 639 640 if (m_actionTimer.isActive()) 641 m_actionTimer.stop(); 642 m_pendingActions.clear(); 643 m_asyncEventQueue->close(); 644} 645 646void MediaKeySession::trace(Visitor* visitor) 647{ 648 visitor->trace(m_error); 649 visitor->trace(m_asyncEventQueue); 650 visitor->trace(m_pendingActions); 651 visitor->trace(m_mediaKeys); 652 visitor->trace(m_closedPromise); 653 EventTargetWithInlineData::trace(visitor); 654} 655 656} // namespace blink 657