2 * Copyright (C) 2013 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "modules/encryptedmedia/MediaKeySession.h"
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/Logging.h"
42 #include "platform/Timer.h"
43 #include "public/platform/WebContentDecryptionModule.h"
44 #include "public/platform/WebContentDecryptionModuleException.h"
45 #include "public/platform/WebContentDecryptionModuleSession.h"
46 #include "public/platform/WebString.h"
47 #include "public/platform/WebURL.h"
48 #include "wtf/ArrayBuffer.h"
49 #include "wtf/ArrayBufferView.h"
53 // A class holding a pending action.
54 class MediaKeySession::PendingAction : public GarbageCollectedFinalized<MediaKeySession::PendingAction> {
62 Type type() const { return m_type; }
64 const Persistent<ContentDecryptionModuleResult> result() const
66 ASSERT(m_type == Update || m_type == Release);
70 const RefPtr<ArrayBuffer> data() const
72 ASSERT(m_type == Update);
76 RefPtrWillBeRawPtr<Event> event()
78 ASSERT(m_type == Message);
82 static PendingAction* CreatePendingUpdate(ContentDecryptionModuleResult* result, PassRefPtr<ArrayBuffer> data)
86 return new PendingAction(Update, result, data);
89 static PendingAction* CreatePendingRelease(ContentDecryptionModuleResult* result)
92 return new PendingAction(Release, result, PassRefPtr<ArrayBuffer>());
95 static PendingAction* CreatePendingMessage(PassRefPtrWillBeRawPtr<Event> event)
98 return new PendingAction(Message, event);
105 void trace(Visitor* visitor)
107 visitor->trace(m_result);
108 visitor->trace(m_event);
112 PendingAction(Type type, ContentDecryptionModuleResult* result, PassRefPtr<ArrayBuffer> data)
119 PendingAction(Type type, PassRefPtrWillBeRawPtr<Event> event)
126 const Member<ContentDecryptionModuleResult> m_result;
127 const RefPtr<ArrayBuffer> m_data;
128 const RefPtrWillBeMember<Event> m_event;
131 // This class allows a MediaKeySession object to be created asynchronously.
132 class MediaKeySessionInitializer : public ScriptPromiseResolver {
133 WTF_MAKE_NONCOPYABLE(MediaKeySessionInitializer);
136 static ScriptPromise create(ScriptState*, MediaKeys*, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType);
137 virtual ~MediaKeySessionInitializer();
139 void completeWithSession(WebContentDecryptionModuleResult::SessionStatus);
140 void completeWithDOMException(ExceptionCode, const String& errorMessage);
143 MediaKeySessionInitializer(ScriptState*, MediaKeys*, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType);
144 void timerFired(Timer<MediaKeySessionInitializer>*);
146 Persistent<MediaKeys> m_mediaKeys;
147 OwnPtr<WebContentDecryptionModuleSession> m_cdmSession;
149 // The next 3 values are simply the initialization data saved so that the
150 // asynchronous creation has the data needed.
151 String m_initDataType;
152 RefPtr<ArrayBuffer> m_initData;
153 String m_sessionType;
155 Timer<MediaKeySessionInitializer> m_timer;
158 // Represents the result used when a new WebContentDecryptionModuleSession
159 // object has been created. Needed as MediaKeySessionInitializer can't be both
160 // a ScriptPromiseResolver and ContentDecryptionModuleResult at the same time.
161 class NewMediaKeySessionResult FINAL : public ContentDecryptionModuleResult {
163 NewMediaKeySessionResult(MediaKeySessionInitializer* initializer)
164 : m_initializer(initializer)
168 // ContentDecryptionModuleResult implementation.
169 virtual void complete() OVERRIDE
171 ASSERT_NOT_REACHED();
172 m_initializer->completeWithDOMException(InvalidStateError, "Unexpected completion.");
175 virtual void completeWithSession(WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE
177 m_initializer->completeWithSession(status);
180 virtual void completeWithError(WebContentDecryptionModuleException code, unsigned long systemCode, const WebString& message) OVERRIDE
182 m_initializer->completeWithDOMException(WebCdmExceptionToExceptionCode(code), message);
186 MediaKeySessionInitializer* m_initializer;
189 ScriptPromise MediaKeySessionInitializer::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType)
191 RefPtr<MediaKeySessionInitializer> initializer = adoptRef(new MediaKeySessionInitializer(scriptState, mediaKeys, initDataType, initData, sessionType));
192 initializer->suspendIfNeeded();
193 initializer->keepAliveWhilePending();
194 return initializer->promise();
197 MediaKeySessionInitializer::MediaKeySessionInitializer(ScriptState* scriptState, MediaKeys* mediaKeys, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType)
198 : ScriptPromiseResolver(scriptState)
199 , m_mediaKeys(mediaKeys)
200 , m_initDataType(initDataType)
201 , m_initData(initData)
202 , m_sessionType(sessionType)
203 , m_timer(this, &MediaKeySessionInitializer::timerFired)
205 WTF_LOG(Media, "MediaKeySessionInitializer::MediaKeySessionInitializer");
207 // Start the timer so that MediaKeySession can be created asynchronously.
208 m_timer.startOneShot(0, FROM_HERE);
211 MediaKeySessionInitializer::~MediaKeySessionInitializer()
213 WTF_LOG(Media, "MediaKeySessionInitializer::~MediaKeySessionInitializer");
216 void MediaKeySessionInitializer::timerFired(Timer<MediaKeySessionInitializer>*)
218 WTF_LOG(Media, "MediaKeySessionInitializer::timerFired");
220 // Continue MediaKeys::createSession() at step 7.
221 // 7.1 Let request be null. (Request provided by cdm in message event).
222 // 7.2 Let default URL be null. (Also provided by cdm in message event).
224 // 7.3 Let cdm be the cdm loaded in create().
225 WebContentDecryptionModule* cdm = m_mediaKeys->contentDecryptionModule();
227 // 7.4 Use the cdm to execute the following steps:
228 // 7.4.1 If the init data is not valid for initDataType, reject promise
229 // with a new DOMException whose name is "InvalidAccessError".
230 // 7.4.2 If the init data is not supported by the cdm, reject promise with
231 // a new DOMException whose name is "NotSupportedError".
232 // 7.4.3 Let request be a request (e.g. a license request) generated based
233 // on the init data, which is interpreteted per initDataType, and
234 // sessionType. If sessionType is "temporary", the request is for a
235 // temporary non-persisted license. If sessionType is "persistent",
236 // the request is for a persistable license.
237 // 7.4.4 If the init data indicates a default URL, let default URL be
238 // that URL. The URL may be validated and/or normalized.
239 m_cdmSession = adoptPtr(cdm->createSession());
240 NewMediaKeySessionResult* result = new NewMediaKeySessionResult(this);
241 m_cdmSession->initializeNewSession(m_initDataType, static_cast<unsigned char*>(m_initData->data()), m_initData->byteLength(), m_sessionType, result->result());
243 WTF_LOG(Media, "MediaKeySessionInitializer::timerFired done");
244 // Note: As soon as the promise is resolved (or rejected), the
245 // ScriptPromiseResolver object (|this|) is freed. So if
246 // initializeNewSession() is synchronous, access to any members will crash.
249 void MediaKeySessionInitializer::completeWithSession(WebContentDecryptionModuleResult::SessionStatus status)
251 WTF_LOG(Media, "MediaKeySessionInitializer::completeWithSession");
254 case WebContentDecryptionModuleResult::NewSession: {
255 // Resume MediaKeys::createSession().
256 // 7.5 Let the session ID be a unique Session ID string. It may be
257 // obtained from cdm (it is).
258 // 7.6 Let session be a new MediaKeySession object, and initialize it.
259 // (Object was created previously, complete the steps for 7.6).
260 RefPtrWillBeRawPtr<MediaKeySession> session = adoptRefCountedGarbageCollectedWillBeNoop(new MediaKeySession(executionContext(), m_mediaKeys, m_cdmSession.release()));
261 session->suspendIfNeeded();
263 // 7.7 If any of the preceding steps failed, reject promise with a
264 // new DOMException whose name is the appropriate error name
265 // and that has an appropriate message.
266 // (Implemented by CDM/Chromium calling completeWithError()).
268 // 7.8 Add an entry for the value of the sessionId attribute to the
269 // list of active session IDs for this object.
270 // (Implemented in SessionIdAdapter).
272 // 7.9 Run the Queue a "message" Event algorithm on the session,
273 // providing request and default URL.
274 // (Done by the CDM).
276 // 7.10 Resolve promise with session.
277 resolve(session.release());
278 WTF_LOG(Media, "MediaKeySessionInitializer::completeWithSession done w/session");
282 case WebContentDecryptionModuleResult::SessionNotFound:
283 // Step 4.7.1 of MediaKeys::loadSession(): If there is no data
284 // stored for the sessionId in the origin, resolve promise with
286 resolve(V8UndefinedType());
287 WTF_LOG(Media, "MediaKeySessionInitializer::completeWithSession done w/undefined");
290 case WebContentDecryptionModuleResult::SessionAlreadyExists:
291 // If a session already exists, resolve the promise with null.
292 resolve(V8NullType());
293 WTF_LOG(Media, "MediaKeySessionInitializer::completeWithSession done w/null");
296 ASSERT_NOT_REACHED();
299 void MediaKeySessionInitializer::completeWithDOMException(ExceptionCode code, const String& errorMessage)
301 WTF_LOG(Media, "MediaKeySessionInitializer::completeWithDOMException");
302 reject(DOMException::create(code, errorMessage));
305 ScriptPromise MediaKeySession::create(ScriptState* scriptState, MediaKeys* mediaKeys, const String& initDataType, PassRefPtr<ArrayBuffer> initData, const String& sessionType)
307 // Since creation is done asynchronously, use MediaKeySessionInitializer
309 return MediaKeySessionInitializer::create(scriptState, mediaKeys, initDataType, initData, sessionType);
312 MediaKeySession::MediaKeySession(ExecutionContext* context, MediaKeys* keys, PassOwnPtr<WebContentDecryptionModuleSession> cdmSession)
313 : ActiveDOMObject(context)
314 , m_keySystem(keys->keySystem())
315 , m_asyncEventQueue(GenericEventQueue::create(this))
316 , m_session(cdmSession)
319 , m_closedPromise(new ClosedPromise(context, this, ClosedPromise::Closed))
320 , m_actionTimer(this, &MediaKeySession::actionTimerFired)
322 WTF_LOG(Media, "MediaKeySession(%p)::MediaKeySession", this);
323 ScriptWrappable::init(this);
324 m_session->setClientInterface(this);
326 // Resume MediaKeys::createSession() at step 7.6.
327 // 7.6.1 Set the error attribute to null.
330 // 7.6.2 Set the sessionId attribute to session ID.
331 ASSERT(!sessionId().isEmpty());
333 // 7.6.3 Let expiration be NaN.
334 // 7.6.4 Let closed be a new promise.
335 // 7.6.5 Let the session type be sessionType.
336 // FIXME: Implement the previous 3 values.
339 MediaKeySession::~MediaKeySession()
341 WTF_LOG(Media, "MediaKeySession(%p)::~MediaKeySession", this);
344 // MediaKeySession and m_asyncEventQueue always become unreachable
345 // together. So MediaKeySession and m_asyncEventQueue are destructed in the
346 // same GC. We don't need to call cancelAllEvents explicitly in Oilpan.
347 m_asyncEventQueue->cancelAllEvents();
351 void MediaKeySession::setError(MediaKeyError* error)
356 String MediaKeySession::sessionId() const
358 return m_session->sessionId();
361 ScriptPromise MediaKeySession::closed(ScriptState* scriptState)
363 return m_closedPromise->promise(scriptState->world());
366 ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBuffer* response)
368 RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->data(), response->byteLength());
369 return updateInternal(scriptState, responseCopy.release());
372 ScriptPromise MediaKeySession::update(ScriptState* scriptState, ArrayBufferView* response)
374 RefPtr<ArrayBuffer> responseCopy = ArrayBuffer::create(response->baseAddress(), response->byteLength());
375 return updateInternal(scriptState, responseCopy.release());
378 ScriptPromise MediaKeySession::updateInternal(ScriptState* scriptState, PassRefPtr<ArrayBuffer> response)
380 WTF_LOG(Media, "MediaKeySession(%p)::update", this);
383 // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-update>:
384 // The update(response) method provides messages, including licenses, to the
385 // CDM. It must run the following steps:
387 // 1. If response is an empty array, return a promise rejected with a new
388 // DOMException whose name is "InvalidAccessError" and that has the
389 // message "The response parameter is empty."
390 if (!response->byteLength()) {
391 return ScriptPromise::rejectWithDOMException(
392 scriptState, DOMException::create(InvalidAccessError, "The response parameter is empty."));
395 // 2. Let message be a copy of the contents of the response parameter.
396 // (Copied in the caller.)
398 // 3. Let promise be a new promise.
399 SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
400 ScriptPromise promise = result->promise();
402 // 4. Run the following steps asynchronously (documented in
403 // actionTimerFired())
404 m_pendingActions.append(PendingAction::CreatePendingUpdate(result, response));
405 if (!m_actionTimer.isActive())
406 m_actionTimer.startOneShot(0, FROM_HERE);
408 // 5. Return promise.
412 ScriptPromise MediaKeySession::release(ScriptState* scriptState)
414 WTF_LOG(Media, "MediaKeySession(%p)::release", this);
415 SimpleContentDecryptionModuleResult* result = new SimpleContentDecryptionModuleResult(scriptState);
416 ScriptPromise promise = result->promise();
418 // From <https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-close>:
419 // The close() method allows an application to indicate that it no longer
420 // needs the session and the CDM should release any resources associated
421 // with this object and close it. The returned promise is resolved when the
422 // request has been processed, and the closed attribute promise is resolved
423 // when the session is closed. It must run the following steps:
425 // 1. If the Session Close algorithm has been run on this object, return a
426 // promise fulfilled with undefined.
432 // 2. Let promise be a new promise.
433 // (Created earlier so it was available in step 1.)
435 // 3. Run the following steps asynchronously (documented in
436 // actionTimerFired()).
437 m_pendingActions.append(PendingAction::CreatePendingRelease(result));
438 if (!m_actionTimer.isActive())
439 m_actionTimer.startOneShot(0, FROM_HERE);
441 // 4. Return promise.
445 void MediaKeySession::actionTimerFired(Timer<MediaKeySession>*)
447 ASSERT(m_pendingActions.size());
449 // Resolving promises now run synchronously and may result in additional
450 // actions getting added to the queue. As a result, swap the queue to
451 // a local copy to avoid problems if this happens.
452 HeapDeque<Member<PendingAction> > pendingActions;
453 pendingActions.swap(m_pendingActions);
455 while (!pendingActions.isEmpty()) {
456 PendingAction* action = pendingActions.takeFirst();
458 switch (action->type()) {
459 case PendingAction::Update:
460 WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Update", this);
461 // NOTE: Continued from step 4 of MediaKeySession::update().
462 // Continue the update call by passing message to the cdm. Once
463 // completed, it will resolve/reject the promise.
464 m_session->update(static_cast<unsigned char*>(action->data()->data()), action->data()->byteLength(), action->result()->result());
466 case PendingAction::Release:
467 WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Release", this);
468 // NOTE: Continued from step 3 of MediaKeySession::release().
469 // 3.1 Let cdm be the cdm loaded in create().
470 // 3.2 Use the cdm to execute the following steps:
471 // 3.2.1 Process the close request. Do not remove stored session data.
472 // 3.2.2 If the previous step caused the session to be closed, run the
473 // Session Close algorithm on this object.
474 // 3.3 Resolve promise with undefined.
475 m_session->release(action->result()->result());
477 case PendingAction::Message:
478 WTF_LOG(Media, "MediaKeySession(%p)::actionTimerFired: Message", this);
479 m_asyncEventQueue->enqueueEvent(action->event().release());
485 // Queue a task to fire a simple event named keymessage at the new object
486 void MediaKeySession::message(const unsigned char* message, size_t messageLength, const WebURL& destinationURL)
488 WTF_LOG(Media, "MediaKeySession(%p)::message", this);
490 MediaKeyMessageEventInit init;
491 init.bubbles = false;
492 init.cancelable = false;
493 init.message = ArrayBuffer::create(static_cast<const void*>(message), messageLength);
494 init.destinationURL = destinationURL.string();
496 RefPtrWillBeRawPtr<MediaKeyMessageEvent> event = MediaKeyMessageEvent::create(EventTypeNames::message, init);
497 event->setTarget(this);
499 if (!hasEventListeners()) {
500 // Since this event may be generated immediately after resolving the
501 // CreateSession() promise, it is possible that the JavaScript hasn't
502 // had time to run the .then() action and bind any necessary event
503 // handlers. If there are no event handlers connected, delay enqueuing
504 // this message to provide time for the JavaScript to run. This will
505 // also affect the (rare) case where there is no message handler
506 // attched during normal operation.
507 m_pendingActions.append(PendingAction::CreatePendingMessage(event.release()));
508 if (!m_actionTimer.isActive())
509 m_actionTimer.startOneShot(0, FROM_HERE);
513 m_asyncEventQueue->enqueueEvent(event.release());
516 void MediaKeySession::ready()
518 WTF_LOG(Media, "MediaKeySession(%p)::ready", this);
520 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::ready);
521 event->setTarget(this);
522 m_asyncEventQueue->enqueueEvent(event.release());
525 void MediaKeySession::close()
527 WTF_LOG(Media, "MediaKeySession(%p)::close", this);
529 // Once closed, the session can no longer be the target of events from
530 // the CDM so this object can be garbage collected.
533 // Resolve the closed promise.
534 m_closedPromise->resolve(V8UndefinedType());
537 // Queue a task to fire a simple event named keyadded at the MediaKeySession object.
538 void MediaKeySession::error(MediaKeyErrorCode errorCode, unsigned long systemCode)
540 WTF_LOG(Media, "MediaKeySession(%p)::error: errorCode=%d, systemCode=%lu", this, errorCode, systemCode);
542 MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
544 case MediaKeyErrorCodeUnknown:
545 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
547 case MediaKeyErrorCodeClient:
548 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
552 // 1. Create a new MediaKeyError object with the following attributes:
553 // code = the appropriate MediaKeyError code
554 // systemCode = a Key System-specific value, if provided, and 0 otherwise
555 // 2. Set the MediaKeySession object's error attribute to the error object created in the previous step.
556 m_error = MediaKeyError::create(mediaKeyErrorCode, systemCode);
558 // 3. queue a task to fire a simple event named keyerror at the MediaKeySession object.
559 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::error);
560 event->setTarget(this);
561 m_asyncEventQueue->enqueueEvent(event.release());
564 void MediaKeySession::error(WebContentDecryptionModuleException exception, unsigned long systemCode, const WebString& errorMessage)
566 WTF_LOG(Media, "MediaKeySession::error: exception=%d, systemCode=%lu", exception, systemCode);
568 // FIXME: EME-WD MediaKeyError now derives from DOMException. Figure out how
569 // to implement this without breaking prefixed EME, which has a totally
570 // different definition. The spec may also change to be just a DOMException.
571 // For now, simply generate an existing MediaKeyError.
572 MediaKeyErrorCode errorCode;
574 case WebContentDecryptionModuleExceptionClientError:
575 errorCode = MediaKeyErrorCodeClient;
578 // All other exceptions get converted into Unknown.
579 errorCode = MediaKeyErrorCodeUnknown;
582 error(errorCode, systemCode);
585 const AtomicString& MediaKeySession::interfaceName() const
587 return EventTargetNames::MediaKeySession;
590 ExecutionContext* MediaKeySession::executionContext() const
592 return ActiveDOMObject::executionContext();
595 bool MediaKeySession::hasPendingActivity() const
597 // Remain around if there are pending events or MediaKeys is still around
598 // and we're not closed.
599 WTF_LOG(Media, "MediaKeySession(%p)::hasPendingActivity %s%s%s%s", this,
600 ActiveDOMObject::hasPendingActivity() ? " ActiveDOMObject::hasPendingActivity()" : "",
601 !m_pendingActions.isEmpty() ? " !m_pendingActions.isEmpty()" : "",
602 m_asyncEventQueue->hasPendingEvents() ? " m_asyncEventQueue->hasPendingEvents()" : "",
603 (m_keys && !m_isClosed) ? " m_keys && !m_isClosed" : "");
605 return ActiveDOMObject::hasPendingActivity()
606 || !m_pendingActions.isEmpty()
607 || m_asyncEventQueue->hasPendingEvents()
608 || (m_keys && !m_isClosed);
611 void MediaKeySession::stop()
613 // Stop the CDM from firing any more events for this session.
617 if (m_actionTimer.isActive())
618 m_actionTimer.stop();
619 m_pendingActions.clear();
620 m_asyncEventQueue->close();
623 void MediaKeySession::trace(Visitor* visitor)
625 visitor->trace(m_error);
626 visitor->trace(m_asyncEventQueue);
627 visitor->trace(m_pendingActions);
628 visitor->trace(m_keys);
629 visitor->trace(m_closedPromise);
630 EventTargetWithInlineData::trace(visitor);