• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 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 COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include "config.h"
27 #include "core/html/HTMLMediaElement.h"
28 
29 #include <limits>
30 #include "HTMLNames.h"
31 #include "RuntimeEnabledFeatures.h"
32 #include "bindings/v8/ExceptionState.h"
33 #include "bindings/v8/ExceptionStatePlaceholder.h"
34 #include "bindings/v8/ScriptController.h"
35 #include "bindings/v8/ScriptEventListener.h"
36 #include "core/css/MediaList.h"
37 #include "core/css/MediaQueryEvaluator.h"
38 #include "core/dom/Attribute.h"
39 #include "core/dom/ExceptionCode.h"
40 #include "core/dom/FullscreenElementStack.h"
41 #include "core/dom/shadow/ShadowRoot.h"
42 #include "core/events/Event.h"
43 #include "core/events/ThreadLocalEventNames.h"
44 #include "core/frame/ContentSecurityPolicy.h"
45 #include "core/frame/Frame.h"
46 #include "core/frame/Settings.h"
47 #include "core/frame/UseCounter.h"
48 #include "core/html/HTMLMediaSource.h"
49 #include "core/html/HTMLSourceElement.h"
50 #include "core/html/HTMLTrackElement.h"
51 #include "core/html/MediaController.h"
52 #include "core/html/MediaError.h"
53 #include "core/html/MediaFragmentURIParser.h"
54 #include "core/html/MediaKeyError.h"
55 #include "core/html/MediaKeyEvent.h"
56 #include "core/html/TimeRanges.h"
57 #include "core/html/shadow/MediaControls.h"
58 #include "core/html/track/InbandTextTrack.h"
59 #include "core/html/track/TextTrackCueList.h"
60 #include "core/html/track/TextTrackList.h"
61 #include "core/loader/FrameLoader.h"
62 #include "core/rendering/RenderLayerCompositor.h"
63 #include "core/rendering/RenderVideo.h"
64 #include "core/rendering/RenderView.h"
65 // FIXME: Remove dependency on modules/encryptedmedia (http://crbug.com/242754).
66 #include "modules/encryptedmedia/MediaKeyNeededEvent.h"
67 #include "modules/encryptedmedia/MediaKeys.h"
68 #include "modules/mediastream/MediaStreamRegistry.h"
69 #include "platform/ContentType.h"
70 #include "platform/Language.h"
71 #include "platform/Logging.h"
72 #include "platform/MIMETypeFromURL.h"
73 #include "platform/MIMETypeRegistry.h"
74 #include "platform/NotImplemented.h"
75 #include "platform/UserGestureIndicator.h"
76 #include "platform/graphics/GraphicsLayer.h"
77 #include "platform/weborigin/SecurityOrigin.h"
78 #include "public/platform/Platform.h"
79 #include "public/platform/WebInbandTextTrack.h"
80 #include "wtf/CurrentTime.h"
81 #include "wtf/MathExtras.h"
82 #include "wtf/NonCopyingSort.h"
83 #include "wtf/Uint8Array.h"
84 #include "wtf/text/CString.h"
85 
86 #if ENABLE(WEB_AUDIO)
87 #include "platform/audio/AudioSourceProvider.h"
88 #include "modules/webaudio/MediaElementAudioSourceNode.h"
89 #endif
90 
91 using namespace std;
92 using blink::WebInbandTextTrack;
93 using blink::WebMimeRegistry;
94 
95 namespace WebCore {
96 
97 #if !LOG_DISABLED
urlForLoggingMedia(const KURL & url)98 static String urlForLoggingMedia(const KURL& url)
99 {
100     static const unsigned maximumURLLengthForLogging = 128;
101 
102     if (url.string().length() < maximumURLLengthForLogging)
103         return url.string();
104     return url.string().substring(0, maximumURLLengthForLogging) + "...";
105 }
106 
boolString(bool val)107 static const char* boolString(bool val)
108 {
109     return val ? "true" : "false";
110 }
111 #endif
112 
113 #ifndef LOG_MEDIA_EVENTS
114 // Default to not logging events because so many are generated they can overwhelm the rest of
115 // the logging.
116 #define LOG_MEDIA_EVENTS 0
117 #endif
118 
119 #ifndef LOG_CACHED_TIME_WARNINGS
120 // Default to not logging warnings about excessive drift in the cached media time because it adds a
121 // fair amount of overhead and logging.
122 #define LOG_CACHED_TIME_WARNINGS 0
123 #endif
124 
125 // URL protocol used to signal that the media source API is being used.
126 static const char mediaSourceBlobProtocol[] = "blob";
127 
128 using namespace HTMLNames;
129 using namespace std;
130 
131 typedef HashMap<Document*, HashSet<HTMLMediaElement*> > DocumentElementSetMap;
documentToElementSetMap()132 static DocumentElementSetMap& documentToElementSetMap()
133 {
134     DEFINE_STATIC_LOCAL(DocumentElementSetMap, map, ());
135     return map;
136 }
137 
addElementToDocumentMap(HTMLMediaElement * element,Document * document)138 static void addElementToDocumentMap(HTMLMediaElement* element, Document* document)
139 {
140     DocumentElementSetMap& map = documentToElementSetMap();
141     HashSet<HTMLMediaElement*> set = map.take(document);
142     set.add(element);
143     map.add(document, set);
144 }
145 
removeElementFromDocumentMap(HTMLMediaElement * element,Document * document)146 static void removeElementFromDocumentMap(HTMLMediaElement* element, Document* document)
147 {
148     DocumentElementSetMap& map = documentToElementSetMap();
149     HashSet<HTMLMediaElement*> set = map.take(document);
150     set.remove(element);
151     if (!set.isEmpty())
152         map.add(document, set);
153 }
154 
throwExceptionForMediaKeyException(MediaPlayer::MediaKeyException exception,ExceptionState & exceptionState)155 static void throwExceptionForMediaKeyException(MediaPlayer::MediaKeyException exception, ExceptionState& exceptionState)
156 {
157     switch (exception) {
158     case MediaPlayer::NoError:
159         return;
160     case MediaPlayer::InvalidPlayerState:
161         exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
162         return;
163     case MediaPlayer::KeySystemNotSupported:
164         exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError);
165         return;
166     case MediaPlayer::InvalidAccess:
167         exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError);
168         return;
169     }
170 
171     ASSERT_NOT_REACHED();
172     return;
173 }
174 
175 class TrackDisplayUpdateScope {
176 public:
TrackDisplayUpdateScope(HTMLMediaElement * mediaElement)177     TrackDisplayUpdateScope(HTMLMediaElement* mediaElement)
178     {
179         m_mediaElement = mediaElement;
180         m_mediaElement->beginIgnoringTrackDisplayUpdateRequests();
181     }
~TrackDisplayUpdateScope()182     ~TrackDisplayUpdateScope()
183     {
184         ASSERT(m_mediaElement);
185         m_mediaElement->endIgnoringTrackDisplayUpdateRequests();
186     }
187 
188 private:
189     HTMLMediaElement* m_mediaElement;
190 };
191 
canLoadURL(const KURL & url,const ContentType & contentType,const String & keySystem)192 static bool canLoadURL(const KURL& url, const ContentType& contentType, const String& keySystem)
193 {
194     DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
195 
196     String contentMIMEType = contentType.type().lower();
197     String contentTypeCodecs = contentType.parameter(codecs);
198 
199     // If the MIME type is missing or is not meaningful, try to figure it out from the URL.
200     if (contentMIMEType.isEmpty() || contentMIMEType == "application/octet-stream" || contentMIMEType == "text/plain") {
201         if (url.protocolIsData())
202             contentMIMEType = mimeTypeFromDataURL(url.string());
203     }
204 
205     // If no MIME type is specified, always attempt to load.
206     if (contentMIMEType.isEmpty())
207         return true;
208 
209     // 4.8.10.3 MIME types - In the absence of a specification to the contrary, the MIME type "application/octet-stream"
210     // when used with parameters, e.g. "application/octet-stream;codecs=theora", is a type that the user agent knows
211     // it cannot render.
212     if (contentMIMEType != "application/octet-stream" || contentTypeCodecs.isEmpty()) {
213         WebMimeRegistry::SupportsType supported = blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(contentMIMEType, contentTypeCodecs, keySystem.lower());
214         return supported > WebMimeRegistry::IsNotSupported;
215     }
216 
217     return false;
218 }
219 
supportsType(const ContentType & contentType,const String & keySystem)220 WebMimeRegistry::SupportsType HTMLMediaElement::supportsType(const ContentType& contentType, const String& keySystem)
221 {
222     DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
223 
224     if (!RuntimeEnabledFeatures::mediaEnabled())
225         return WebMimeRegistry::IsNotSupported;
226 
227     String type = contentType.type().lower();
228     // The codecs string is not lower-cased because MP4 values are case sensitive
229     // per http://tools.ietf.org/html/rfc4281#page-7.
230     String typeCodecs = contentType.parameter(codecs);
231     String system = keySystem.lower();
232 
233     if (type.isEmpty())
234         return WebMimeRegistry::IsNotSupported;
235 
236     // 4.8.10.3 MIME types - The canPlayType(type) method must return the empty string if type is a type that the
237     // user agent knows it cannot render or is the type "application/octet-stream"
238     if (type == "application/octet-stream")
239         return WebMimeRegistry::IsNotSupported;
240 
241     return blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(type, typeCodecs, system);
242 }
243 
HTMLMediaElement(const QualifiedName & tagName,Document & document,bool createdByParser)244 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document, bool createdByParser)
245     : HTMLElement(tagName, document)
246     , ActiveDOMObject(&document)
247     , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
248     , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
249     , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
250     , m_playedTimeRanges()
251     , m_asyncEventQueue(GenericEventQueue::create(this))
252     , m_playbackRate(1.0f)
253     , m_defaultPlaybackRate(1.0f)
254     , m_networkState(NETWORK_EMPTY)
255     , m_readyState(HAVE_NOTHING)
256     , m_readyStateMaximum(HAVE_NOTHING)
257     , m_volume(1.0f)
258     , m_lastSeekTime(0)
259     , m_previousProgressTime(numeric_limits<double>::max())
260     , m_duration(numeric_limits<double>::quiet_NaN())
261     , m_lastTimeUpdateEventWallTime(0)
262     , m_lastTimeUpdateEventMovieTime(numeric_limits<double>::max())
263     , m_loadState(WaitingForSource)
264     , m_webLayer(0)
265     , m_opaque(false)
266     , m_restrictions(RequirePageConsentToLoadMediaRestriction)
267     , m_preload(MediaPlayer::Auto)
268     , m_displayMode(Unknown)
269     , m_cachedTime(MediaPlayer::invalidTime())
270     , m_cachedTimeWallClockUpdateTime(0)
271     , m_minimumWallClockTimeToCacheMediaTime(0)
272     , m_fragmentStartTime(MediaPlayer::invalidTime())
273     , m_fragmentEndTime(MediaPlayer::invalidTime())
274     , m_pendingActionFlags(0)
275     , m_playing(false)
276     , m_shouldDelayLoadEvent(false)
277     , m_haveFiredLoadedData(false)
278     , m_active(true)
279     , m_autoplaying(true)
280     , m_muted(false)
281     , m_paused(true)
282     , m_seeking(false)
283     , m_sentStalledEvent(false)
284     , m_sentEndEvent(false)
285     , m_pausedInternal(false)
286     , m_closedCaptionsVisible(false)
287     , m_loadInitiatedByUserGesture(false)
288     , m_completelyLoaded(false)
289     , m_havePreparedToPlay(false)
290     , m_parsingInProgress(createdByParser)
291     , m_tracksAreReady(true)
292     , m_haveVisibleTextTrack(false)
293     , m_processingPreferenceChange(false)
294     , m_lastTextTrackUpdateTime(-1)
295     , m_textTracks(0)
296     , m_ignoreTrackDisplayUpdate(0)
297 #if ENABLE(WEB_AUDIO)
298     , m_audioSourceNode(0)
299 #endif
300 {
301     ASSERT(RuntimeEnabledFeatures::mediaEnabled());
302 
303     WTF_LOG(Media, "HTMLMediaElement::HTMLMediaElement");
304     ScriptWrappable::init(this);
305 
306     if (document.settings()) {
307         if (document.settings()->mediaPlaybackRequiresUserGesture()) {
308             addBehaviorRestriction(RequireUserGestureForRateChangeRestriction);
309             addBehaviorRestriction(RequireUserGestureForLoadRestriction);
310         }
311         if (document.settings()->mediaFullscreenRequiresUserGesture()) {
312             addBehaviorRestriction(RequireUserGestureForFullscreenRestriction);
313         }
314     }
315 
316     setHasCustomStyleCallbacks();
317     addElementToDocumentMap(this, &document);
318 
319 }
320 
~HTMLMediaElement()321 HTMLMediaElement::~HTMLMediaElement()
322 {
323     WTF_LOG(Media, "HTMLMediaElement::~HTMLMediaElement");
324 
325     m_asyncEventQueue->close();
326 
327     setShouldDelayLoadEvent(false);
328     if (m_textTracks)
329         m_textTracks->clearOwner();
330     if (m_textTracks) {
331         for (unsigned i = 0; i < m_textTracks->length(); ++i)
332             m_textTracks->item(i)->clearClient();
333     }
334 
335     if (m_mediaController) {
336         m_mediaController->removeMediaElement(this);
337         m_mediaController = 0;
338     }
339 
340     closeMediaSource();
341 
342     setMediaKeys(0);
343 
344     removeElementFromDocumentMap(this, &document());
345 
346     // Destroying the player may cause a resource load to be canceled,
347     // which could result in userCancelledLoad() being called back.
348     // Setting m_completelyLoaded ensures that such a call will not cause
349     // us to dispatch an abort event, which would result in a crash.
350     // See http://crbug.com/233654 for more details.
351     m_completelyLoaded = true;
352 
353     // Destroying the player may cause a resource load to be canceled,
354     // which could result in Document::dispatchWindowLoadEvent() being
355     // called via ResourceFetch::didLoadResource() then
356     // FrameLoader::loadDone(). To prevent load event dispatching during
357     // object destruction, we use Document::incrementLoadEventDelayCount().
358     // See http://crbug.com/275223 for more details.
359     document().incrementLoadEventDelayCount();
360 
361     clearMediaPlayerAndAudioSourceProviderClient();
362 
363     document().decrementLoadEventDelayCount();
364 }
365 
didMoveToNewDocument(Document & oldDocument)366 void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument)
367 {
368     WTF_LOG(Media, "HTMLMediaElement::didMoveToNewDocument");
369 
370     if (m_shouldDelayLoadEvent) {
371         document().incrementLoadEventDelayCount();
372         // Note: Keeping the load event delay count increment on oldDocument that was added
373         // when m_shouldDelayLoadEvent was set so that destruction of m_player can not
374         // cause load event dispatching in oldDocument.
375     } else {
376         // Incrementing the load event delay count so that destruction of m_player can not
377         // cause load event dispatching in oldDocument.
378         oldDocument.incrementLoadEventDelayCount();
379     }
380 
381     removeElementFromDocumentMap(this, &oldDocument);
382     addElementToDocumentMap(this, &document());
383 
384     // FIXME: This is a temporary fix to prevent this object from causing the
385     // MediaPlayer to dereference Frame and FrameLoader pointers from the
386     // previous document. A proper fix would provide a mechanism to allow this
387     // object to refresh the MediaPlayer's Frame and FrameLoader references on
388     // document changes so that playback can be resumed properly.
389     userCancelledLoad();
390 
391     // Decrement the load event delay count on oldDocument now that m_player has been destroyed
392     // and there is no risk of dispatching a load event from within the destructor.
393     oldDocument.decrementLoadEventDelayCount();
394 
395     HTMLElement::didMoveToNewDocument(oldDocument);
396 }
397 
hasCustomFocusLogic() const398 bool HTMLMediaElement::hasCustomFocusLogic() const
399 {
400     return true;
401 }
402 
supportsFocus() const403 bool HTMLMediaElement::supportsFocus() const
404 {
405     if (ownerDocument()->isMediaDocument())
406         return false;
407 
408     // If no controls specified, we should still be able to focus the element if it has tabIndex.
409     return controls() || HTMLElement::supportsFocus();
410 }
411 
isMouseFocusable() const412 bool HTMLMediaElement::isMouseFocusable() const
413 {
414     return false;
415 }
416 
parseAttribute(const QualifiedName & name,const AtomicString & value)417 void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
418 {
419     if (name == srcAttr) {
420         // Trigger a reload, as long as the 'src' attribute is present.
421         if (!value.isNull()) {
422             clearMediaPlayer(LoadMediaResource);
423             scheduleDelayedAction(LoadMediaResource);
424         }
425     } else if (name == controlsAttr)
426         configureMediaControls();
427     else if (name == preloadAttr) {
428         if (equalIgnoringCase(value, "none"))
429             m_preload = MediaPlayer::None;
430         else if (equalIgnoringCase(value, "metadata"))
431             m_preload = MediaPlayer::MetaData;
432         else {
433             // The spec does not define an "invalid value default" but "auto" is suggested as the
434             // "missing value default", so use it for everything except "none" and "metadata"
435             m_preload = MediaPlayer::Auto;
436         }
437 
438         // The attribute must be ignored if the autoplay attribute is present
439         if (!autoplay() && m_player)
440             m_player->setPreload(m_preload);
441 
442     } else if (name == mediagroupAttr)
443         setMediaGroup(value);
444     else if (name == onbeforeloadAttr)
445         setAttributeEventListener(EventTypeNames::beforeload, createAttributeEventListener(this, name, value));
446     else
447         HTMLElement::parseAttribute(name, value);
448 }
449 
finishParsingChildren()450 void HTMLMediaElement::finishParsingChildren()
451 {
452     HTMLElement::finishParsingChildren();
453     m_parsingInProgress = false;
454 
455     if (!RuntimeEnabledFeatures::videoTrackEnabled())
456         return;
457 
458     for (Node* node = firstChild(); node; node = node->nextSibling()) {
459         if (node->hasTagName(trackTag)) {
460             scheduleDelayedAction(LoadTextTrackResource);
461             break;
462         }
463     }
464 }
465 
rendererIsNeeded(const RenderStyle & style)466 bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style)
467 {
468     return controls() ? HTMLElement::rendererIsNeeded(style) : false;
469 }
470 
createRenderer(RenderStyle *)471 RenderObject* HTMLMediaElement::createRenderer(RenderStyle*)
472 {
473     return new RenderMedia(this);
474 }
475 
childShouldCreateRenderer(const Node & child) const476 bool HTMLMediaElement::childShouldCreateRenderer(const Node& child) const
477 {
478     return hasMediaControls() && HTMLElement::childShouldCreateRenderer(child);
479 }
480 
insertedInto(ContainerNode * insertionPoint)481 Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint)
482 {
483     WTF_LOG(Media, "HTMLMediaElement::insertedInto");
484 
485     HTMLElement::insertedInto(insertionPoint);
486     if (insertionPoint->inDocument()) {
487         m_active = true;
488 
489         if (!getAttribute(srcAttr).isEmpty() && m_networkState == NETWORK_EMPTY)
490             scheduleDelayedAction(LoadMediaResource);
491     }
492 
493     configureMediaControls();
494     return InsertionDone;
495 }
496 
removedFrom(ContainerNode * insertionPoint)497 void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint)
498 {
499     WTF_LOG(Media, "HTMLMediaElement::removedFrom");
500 
501     m_active = false;
502     if (insertionPoint->inDocument()) {
503         configureMediaControls();
504         if (m_networkState > NETWORK_EMPTY)
505             pause();
506     }
507 
508     HTMLElement::removedFrom(insertionPoint);
509 }
510 
attach(const AttachContext & context)511 void HTMLMediaElement::attach(const AttachContext& context)
512 {
513     HTMLElement::attach(context);
514 
515     if (renderer())
516         renderer()->updateFromElement();
517 }
518 
didRecalcStyle(StyleRecalcChange)519 void HTMLMediaElement::didRecalcStyle(StyleRecalcChange)
520 {
521     if (renderer())
522         renderer()->updateFromElement();
523 }
524 
scheduleDelayedAction(DelayedActionType actionType)525 void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType)
526 {
527     WTF_LOG(Media, "HTMLMediaElement::scheduleDelayedAction");
528 
529     if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) {
530         prepareForLoad();
531         m_pendingActionFlags |= LoadMediaResource;
532     }
533 
534     if (RuntimeEnabledFeatures::videoTrackEnabled() && (actionType & LoadTextTrackResource))
535         m_pendingActionFlags |= LoadTextTrackResource;
536 
537     if (!m_loadTimer.isActive())
538         m_loadTimer.startOneShot(0);
539 }
540 
scheduleNextSourceChild()541 void HTMLMediaElement::scheduleNextSourceChild()
542 {
543     // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
544     m_pendingActionFlags |= LoadMediaResource;
545     m_loadTimer.startOneShot(0);
546 }
547 
scheduleEvent(const AtomicString & eventName)548 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
549 {
550 #if LOG_MEDIA_EVENTS
551     WTF_LOG(Media, "HTMLMediaElement::scheduleEvent - scheduling '%s'", eventName.string().ascii().data());
552 #endif
553     m_asyncEventQueue->enqueueEvent(Event::createCancelable(eventName));
554 }
555 
loadTimerFired(Timer<HTMLMediaElement> *)556 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
557 {
558     RefPtr<HTMLMediaElement> protect(this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
559 
560     if (RuntimeEnabledFeatures::videoTrackEnabled() && (m_pendingActionFlags & LoadTextTrackResource))
561         configureTextTracks();
562 
563     if (m_pendingActionFlags & LoadMediaResource) {
564         if (m_loadState == LoadingFromSourceElement)
565             loadNextSourceChild();
566         else
567             loadInternal();
568     }
569 
570     m_pendingActionFlags = 0;
571 }
572 
error() const573 PassRefPtr<MediaError> HTMLMediaElement::error() const
574 {
575     return m_error;
576 }
577 
setSrc(const AtomicString & url)578 void HTMLMediaElement::setSrc(const AtomicString& url)
579 {
580     setAttribute(srcAttr, url);
581 }
582 
networkState() const583 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
584 {
585     return m_networkState;
586 }
587 
canPlayType(const String & mimeType,const String & keySystem,const KURL & url) const588 String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem, const KURL& url) const
589 {
590     WebMimeRegistry::SupportsType support = supportsType(ContentType(mimeType), keySystem);
591     String canPlay;
592 
593     // 4.8.10.3
594     switch (support)
595     {
596         case WebMimeRegistry::IsNotSupported:
597             canPlay = emptyString();
598             break;
599         case WebMimeRegistry::MayBeSupported:
600             canPlay = "maybe";
601             break;
602         case WebMimeRegistry::IsSupported:
603             canPlay = "probably";
604             break;
605     }
606 
607     WTF_LOG(Media, "HTMLMediaElement::canPlayType(%s, %s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), url.elidedString().utf8().data(), canPlay.utf8().data());
608 
609     return canPlay;
610 }
611 
load()612 void HTMLMediaElement::load()
613 {
614     RefPtr<HTMLMediaElement> protect(this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations.
615 
616     WTF_LOG(Media, "HTMLMediaElement::load()");
617 
618     if (document().settings() && !document().settings()->mediaEnabled())
619         return;
620 
621     if (userGestureRequiredForLoad() && !UserGestureIndicator::processingUserGesture())
622         return;
623 
624     m_loadInitiatedByUserGesture = UserGestureIndicator::processingUserGesture();
625     if (m_loadInitiatedByUserGesture)
626         removeBehaviorsRestrictionsAfterFirstUserGesture();
627     prepareForLoad();
628     loadInternal();
629     prepareToPlay();
630 }
631 
prepareForLoad()632 void HTMLMediaElement::prepareForLoad()
633 {
634     WTF_LOG(Media, "HTMLMediaElement::prepareForLoad");
635 
636     // Perform the cleanup required for the resource load algorithm to run.
637     stopPeriodicTimers();
638     m_loadTimer.stop();
639     m_sentEndEvent = false;
640     m_sentStalledEvent = false;
641     m_haveFiredLoadedData = false;
642     m_completelyLoaded = false;
643     m_havePreparedToPlay = false;
644     m_displayMode = Unknown;
645 
646     // 1 - Abort any already-running instance of the resource selection algorithm for this element.
647     m_loadState = WaitingForSource;
648     m_currentSourceNode = 0;
649 
650     // 2 - If there are any tasks from the media element's media element event task source in
651     // one of the task queues, then remove those tasks.
652     cancelPendingEventsAndCallbacks();
653 
654     // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
655     // a task to fire a simple event named abort at the media element.
656     if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
657         scheduleEvent(EventTypeNames::abort);
658 
659     closeMediaSource();
660 
661     createMediaPlayer();
662 
663     // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
664     if (m_networkState != NETWORK_EMPTY) {
665         m_networkState = NETWORK_EMPTY;
666         m_readyState = HAVE_NOTHING;
667         m_readyStateMaximum = HAVE_NOTHING;
668         refreshCachedTime();
669         m_paused = true;
670         m_seeking = false;
671         invalidateCachedTime();
672         scheduleEvent(EventTypeNames::emptied);
673         updateMediaController();
674         if (RuntimeEnabledFeatures::videoTrackEnabled())
675             updateActiveTextTrackCues(0);
676     }
677 
678     // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
679     setPlaybackRate(defaultPlaybackRate());
680 
681     // 6 - Set the error attribute to null and the autoplaying flag to true.
682     m_error = 0;
683     m_autoplaying = true;
684 
685     // 7 - Invoke the media element's resource selection algorithm.
686 
687     // 8 - Note: Playback of any previously playing media resource for this element stops.
688 
689     // The resource selection algorithm
690     // 1 - Set the networkState to NETWORK_NO_SOURCE
691     m_networkState = NETWORK_NO_SOURCE;
692 
693     // 2 - Asynchronously await a stable state.
694 
695     m_playedTimeRanges = TimeRanges::create();
696     m_lastSeekTime = 0;
697     m_duration = numeric_limits<double>::quiet_NaN();
698 
699     // The spec doesn't say to block the load event until we actually run the asynchronous section
700     // algorithm, but do it now because we won't start that until after the timer fires and the
701     // event may have already fired by then.
702     setShouldDelayLoadEvent(true);
703 
704     configureMediaControls();
705 }
706 
loadInternal()707 void HTMLMediaElement::loadInternal()
708 {
709     // Some of the code paths below this function dispatch the BeforeLoad event. This ASSERT helps
710     // us catch those bugs more quickly without needing all the branches to align to actually
711     // trigger the event.
712     ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden());
713 
714     // Once the page has allowed an element to load media, it is free to load at will. This allows a
715     // playlist that starts in a foreground tab to continue automatically if the tab is subsequently
716     // put in the the background.
717     removeBehaviorRestriction(RequirePageConsentToLoadMediaRestriction);
718 
719     // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
720     // disabled state when the element's resource selection algorithm last started".
721     if (RuntimeEnabledFeatures::videoTrackEnabled()) {
722         m_textTracksWhenResourceSelectionBegan.clear();
723         if (m_textTracks) {
724             for (unsigned i = 0; i < m_textTracks->length(); ++i) {
725                 TextTrack* track = m_textTracks->item(i);
726                 if (track->mode() != TextTrack::disabledKeyword())
727                     m_textTracksWhenResourceSelectionBegan.append(track);
728             }
729         }
730     }
731 
732     selectMediaResource();
733 }
734 
selectMediaResource()735 void HTMLMediaElement::selectMediaResource()
736 {
737     WTF_LOG(Media, "HTMLMediaElement::selectMediaResource");
738 
739     enum Mode { attribute, children };
740 
741     // 3 - If the media element has a src attribute, then let mode be attribute.
742     Mode mode = attribute;
743     if (!fastHasAttribute(srcAttr)) {
744         Node* node;
745         for (node = firstChild(); node; node = node->nextSibling()) {
746             if (node->hasTagName(sourceTag))
747                 break;
748         }
749 
750         // Otherwise, if the media element does not have a src attribute but has a source
751         // element child, then let mode be children and let candidate be the first such
752         // source element child in tree order.
753         if (node) {
754             mode = children;
755             m_nextChildNodeToConsider = node;
756             m_currentSourceNode = 0;
757         } else {
758             // Otherwise the media element has neither a src attribute nor a source element
759             // child: set the networkState to NETWORK_EMPTY, and abort these steps; the
760             // synchronous section ends.
761             m_loadState = WaitingForSource;
762             setShouldDelayLoadEvent(false);
763             m_networkState = NETWORK_EMPTY;
764 
765             WTF_LOG(Media, "HTMLMediaElement::selectMediaResource, nothing to load");
766             return;
767         }
768     }
769 
770     // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event),
771     // and set its networkState to NETWORK_LOADING.
772     setShouldDelayLoadEvent(true);
773     m_networkState = NETWORK_LOADING;
774 
775     // 5 - Queue a task to fire a simple event named loadstart at the media element.
776     scheduleEvent(EventTypeNames::loadstart);
777 
778     // 6 - If mode is attribute, then run these substeps
779     if (mode == attribute) {
780         m_loadState = LoadingFromSrcAttr;
781 
782         // If the src attribute's value is the empty string ... jump down to the failed step below
783         KURL mediaURL = getNonEmptyURLAttribute(srcAttr);
784         if (mediaURL.isEmpty()) {
785             mediaLoadingFailed(MediaPlayer::FormatError);
786             WTF_LOG(Media, "HTMLMediaElement::selectMediaResource, empty 'src'");
787             return;
788         }
789 
790         if (!isSafeToLoadURL(mediaURL, Complain) || !dispatchBeforeLoadEvent(mediaURL.string())) {
791             mediaLoadingFailed(MediaPlayer::FormatError);
792             return;
793         }
794 
795         // No type or key system information is available when the url comes
796         // from the 'src' attribute so MediaPlayer
797         // will have to pick a media engine based on the file extension.
798         ContentType contentType((String()));
799         loadResource(mediaURL, contentType, String());
800         WTF_LOG(Media, "HTMLMediaElement::selectMediaResource, using 'src' attribute url");
801         return;
802     }
803 
804     // Otherwise, the source elements will be used
805     loadNextSourceChild();
806 }
807 
loadNextSourceChild()808 void HTMLMediaElement::loadNextSourceChild()
809 {
810     ContentType contentType((String()));
811     String keySystem;
812     KURL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
813     if (!mediaURL.isValid()) {
814         waitForSourceChange();
815         return;
816     }
817 
818     // Recreate the media player for the new url
819     createMediaPlayer();
820 
821     m_loadState = LoadingFromSourceElement;
822     loadResource(mediaURL, contentType, keySystem);
823 }
824 
loadResource(const KURL & url,ContentType & contentType,const String & keySystem)825 void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, const String& keySystem)
826 {
827     ASSERT(isSafeToLoadURL(url, Complain));
828 
829     WTF_LOG(Media, "HTMLMediaElement::loadResource(%s, %s, %s)", urlForLoggingMedia(url).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data());
830 
831     Frame* frame = document().frame();
832     if (!frame) {
833         mediaLoadingFailed(MediaPlayer::FormatError);
834         return;
835     }
836 
837     // The resource fetch algorithm
838     m_networkState = NETWORK_LOADING;
839 
840     // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app
841     // cache is an internal detail not exposed through the media element API.
842     m_currentSrc = url;
843 
844     WTF_LOG(Media, "HTMLMediaElement::loadResource - m_currentSrc -> %s", urlForLoggingMedia(m_currentSrc).utf8().data());
845 
846     if (MediaStreamRegistry::registry().lookupMediaStreamDescriptor(url.string()))
847       removeBehaviorRestriction(RequireUserGestureForRateChangeRestriction);
848 
849     startProgressEventTimer();
850 
851     // Reset display mode to force a recalculation of what to show because we are resetting the player.
852     setDisplayMode(Unknown);
853 
854     if (!autoplay())
855         m_player->setPreload(m_preload);
856 
857     if (fastHasAttribute(mutedAttr))
858         m_muted = true;
859     updateVolume();
860 
861     ASSERT(!m_mediaSource);
862 
863     if (url.protocolIs(mediaSourceBlobProtocol))
864         m_mediaSource = HTMLMediaSource::lookup(url.string());
865 
866     if (m_mediaSource) {
867         if (m_mediaSource->attachToElement(this)) {
868             m_player->load(url, m_mediaSource);
869         } else {
870             // Forget our reference to the MediaSource, so we leave it alone
871             // while processing remainder of load failure.
872             m_mediaSource = 0;
873             mediaLoadingFailed(MediaPlayer::FormatError);
874         }
875     } else if (canLoadURL(url, contentType, keySystem)) {
876         m_player->load(url);
877     } else {
878         mediaLoadingFailed(MediaPlayer::FormatError);
879     }
880 
881     // If there is no poster to display, allow the media engine to render video frames as soon as
882     // they are available.
883     updateDisplayState();
884 
885     if (renderer())
886         renderer()->updateFromElement();
887 }
888 
trackIndexCompare(TextTrack * a,TextTrack * b)889 static bool trackIndexCompare(TextTrack* a,
890                               TextTrack* b)
891 {
892     return a->trackIndex() - b->trackIndex() < 0;
893 }
894 
eventTimeCueCompare(const std::pair<double,TextTrackCue * > & a,const std::pair<double,TextTrackCue * > & b)895 static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a,
896                                 const std::pair<double, TextTrackCue*>& b)
897 {
898     // 12 - Sort the tasks in events in ascending time order (tasks with earlier
899     // times first).
900     if (a.first != b.first)
901         return a.first - b.first < 0;
902 
903     // If the cues belong to different text tracks, it doesn't make sense to
904     // compare the two tracks by the relative cue order, so return the relative
905     // track order.
906     if (a.second->track() != b.second->track())
907         return trackIndexCompare(a.second->track(), b.second->track());
908 
909     // 12 - Further sort tasks in events that have the same time by the
910     // relative text track cue order of the text track cues associated
911     // with these tasks.
912     return a.second->cueIndex() - b.second->cueIndex() < 0;
913 }
914 
915 
updateActiveTextTrackCues(double movieTime)916 void HTMLMediaElement::updateActiveTextTrackCues(double movieTime)
917 {
918     // 4.8.10.8 Playing the media resource
919 
920     //  If the current playback position changes while the steps are running,
921     //  then the user agent must wait for the steps to complete, and then must
922     //  immediately rerun the steps.
923     if (ignoreTrackDisplayUpdateRequests())
924         return;
925 
926     WTF_LOG(Media, "HTMLMediaElement::updateActiveTextTrackCues");
927 
928     // 1 - Let current cues be a list of cues, initialized to contain all the
929     // cues of all the hidden, showing, or showing by default text tracks of the
930     // media element (not the disabled ones) whose start times are less than or
931     // equal to the current playback position and whose end times are greater
932     // than the current playback position.
933     CueList currentCues;
934 
935     // The user agent must synchronously unset [the text track cue active] flag
936     // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
937     if (m_readyState != HAVE_NOTHING && m_player)
938         currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime));
939 
940     CueList previousCues;
941     CueList missedCues;
942 
943     // 2 - Let other cues be a list of cues, initialized to contain all the cues
944     // of hidden, showing, and showing by default text tracks of the media
945     // element that are not present in current cues.
946     previousCues = m_currentlyActiveCues;
947 
948     // 3 - Let last time be the current playback position at the time this
949     // algorithm was last run for this media element, if this is not the first
950     // time it has run.
951     double lastTime = m_lastTextTrackUpdateTime;
952 
953     // 4 - If the current playback position has, since the last time this
954     // algorithm was run, only changed through its usual monotonic increase
955     // during normal playback, then let missed cues be the list of cues in other
956     // cues whose start times are greater than or equal to last time and whose
957     // end times are less than or equal to the current playback position.
958     // Otherwise, let missed cues be an empty list.
959     if (lastTime >= 0 && m_lastSeekTime < movieTime) {
960         CueList potentiallySkippedCues =
961             m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime));
962 
963         for (size_t i = 0; i < potentiallySkippedCues.size(); ++i) {
964             double cueStartTime = potentiallySkippedCues[i].low();
965             double cueEndTime = potentiallySkippedCues[i].high();
966 
967             // Consider cues that may have been missed since the last seek time.
968             if (cueStartTime > max(m_lastSeekTime, lastTime) && cueEndTime < movieTime)
969                 missedCues.append(potentiallySkippedCues[i]);
970         }
971     }
972 
973     m_lastTextTrackUpdateTime = movieTime;
974 
975     // 5 - If the time was reached through the usual monotonic increase of the
976     // current playback position during normal playback, and if the user agent
977     // has not fired a timeupdate event at the element in the past 15 to 250ms
978     // and is not still running event handlers for such an event, then the user
979     // agent must queue a task to fire a simple event named timeupdate at the
980     // element. (In the other cases, such as explicit seeks, relevant events get
981     // fired as part of the overall process of changing the current playback
982     // position.)
983     if (!m_seeking && m_lastSeekTime <= lastTime)
984         scheduleTimeupdateEvent(true);
985 
986     // Explicitly cache vector sizes, as their content is constant from here.
987     size_t currentCuesSize = currentCues.size();
988     size_t missedCuesSize = missedCues.size();
989     size_t previousCuesSize = previousCues.size();
990 
991     // 6 - If all of the cues in current cues have their text track cue active
992     // flag set, none of the cues in other cues have their text track cue active
993     // flag set, and missed cues is empty, then abort these steps.
994     bool activeSetChanged = missedCuesSize;
995 
996     for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i)
997         if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
998             activeSetChanged = true;
999 
1000     for (size_t i = 0; i < currentCuesSize; ++i) {
1001         currentCues[i].data()->updateDisplayTree(movieTime);
1002 
1003         if (!currentCues[i].data()->isActive())
1004             activeSetChanged = true;
1005     }
1006 
1007     if (!activeSetChanged)
1008         return;
1009 
1010     // 7 - If the time was reached through the usual monotonic increase of the
1011     // current playback position during normal playback, and there are cues in
1012     // other cues that have their text track cue pause-on-exi flag set and that
1013     // either have their text track cue active flag set or are also in missed
1014     // cues, then immediately pause the media element.
1015     for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1016         if (previousCues[i].data()->pauseOnExit()
1017             && previousCues[i].data()->isActive()
1018             && !currentCues.contains(previousCues[i]))
1019             pause();
1020     }
1021 
1022     for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1023         if (missedCues[i].data()->pauseOnExit())
1024             pause();
1025     }
1026 
1027     // 8 - Let events be a list of tasks, initially empty. Each task in this
1028     // list will be associated with a text track, a text track cue, and a time,
1029     // which are used to sort the list before the tasks are queued.
1030     Vector<std::pair<double, TextTrackCue*> > eventTasks;
1031 
1032     // 8 - Let affected tracks be a list of text tracks, initially empty.
1033     Vector<TextTrack*> affectedTracks;
1034 
1035     for (size_t i = 0; i < missedCuesSize; ++i) {
1036         // 9 - For each text track cue in missed cues, prepare an event named enter
1037         // for the TextTrackCue object with the text track cue start time.
1038         eventTasks.append(std::make_pair(missedCues[i].data()->startTime(),
1039                                          missedCues[i].data()));
1040 
1041         // 10 - For each text track [...] in missed cues, prepare an event
1042         // named exit for the TextTrackCue object with the  with the later of
1043         // the text track cue end time and the text track cue start time.
1044 
1045         // Note: An explicit task is added only if the cue is NOT a zero or
1046         // negative length cue. Otherwise, the need for an exit event is
1047         // checked when these tasks are actually queued below. This doesn't
1048         // affect sorting events before dispatch either, because the exit
1049         // event has the same time as the enter event.
1050         if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime())
1051             eventTasks.append(std::make_pair(missedCues[i].data()->endTime(),
1052                                              missedCues[i].data()));
1053     }
1054 
1055     for (size_t i = 0; i < previousCuesSize; ++i) {
1056         // 10 - For each text track cue in other cues that has its text
1057         // track cue active flag set prepare an event named exit for the
1058         // TextTrackCue object with the text track cue end time.
1059         if (!currentCues.contains(previousCues[i]))
1060             eventTasks.append(std::make_pair(previousCues[i].data()->endTime(),
1061                                              previousCues[i].data()));
1062     }
1063 
1064     for (size_t i = 0; i < currentCuesSize; ++i) {
1065         // 11 - For each text track cue in current cues that does not have its
1066         // text track cue active flag set, prepare an event named enter for the
1067         // TextTrackCue object with the text track cue start time.
1068         if (!previousCues.contains(currentCues[i]))
1069             eventTasks.append(std::make_pair(currentCues[i].data()->startTime(),
1070                                              currentCues[i].data()));
1071     }
1072 
1073     // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1074     // times first).
1075     nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1076 
1077     for (size_t i = 0; i < eventTasks.size(); ++i) {
1078         if (!affectedTracks.contains(eventTasks[i].second->track()))
1079             affectedTracks.append(eventTasks[i].second->track());
1080 
1081         // 13 - Queue each task in events, in list order.
1082         RefPtr<Event> event;
1083 
1084         // Each event in eventTasks may be either an enterEvent or an exitEvent,
1085         // depending on the time that is associated with the event. This
1086         // correctly identifies the type of the event, if the startTime is
1087         // less than the endTime in the cue.
1088         if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) {
1089             event = Event::create(EventTypeNames::enter);
1090             event->setTarget(eventTasks[i].second);
1091             m_asyncEventQueue->enqueueEvent(event.release());
1092 
1093             event = Event::create(EventTypeNames::exit);
1094             event->setTarget(eventTasks[i].second);
1095             m_asyncEventQueue->enqueueEvent(event.release());
1096         } else {
1097             if (eventTasks[i].first == eventTasks[i].second->startTime())
1098                 event = Event::create(EventTypeNames::enter);
1099             else
1100                 event = Event::create(EventTypeNames::exit);
1101 
1102             event->setTarget(eventTasks[i].second);
1103             m_asyncEventQueue->enqueueEvent(event.release());
1104         }
1105     }
1106 
1107     // 14 - Sort affected tracks in the same order as the text tracks appear in
1108     // the media element's list of text tracks, and remove duplicates.
1109     nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1110 
1111     // 15 - For each text track in affected tracks, in the list order, queue a
1112     // task to fire a simple event named cuechange at the TextTrack object, and, ...
1113     for (size_t i = 0; i < affectedTracks.size(); ++i) {
1114         RefPtr<Event> event = Event::create(EventTypeNames::cuechange);
1115         event->setTarget(affectedTracks[i]);
1116 
1117         m_asyncEventQueue->enqueueEvent(event.release());
1118 
1119         // ... if the text track has a corresponding track element, to then fire a
1120         // simple event named cuechange at the track element as well.
1121         if (affectedTracks[i]->trackType() == TextTrack::TrackElement) {
1122             RefPtr<Event> event = Event::create(EventTypeNames::cuechange);
1123             HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i])->trackElement();
1124             ASSERT(trackElement);
1125             event->setTarget(trackElement);
1126 
1127             m_asyncEventQueue->enqueueEvent(event.release());
1128         }
1129     }
1130 
1131     // 16 - Set the text track cue active flag of all the cues in the current
1132     // cues, and unset the text track cue active flag of all the cues in the
1133     // other cues.
1134     for (size_t i = 0; i < currentCuesSize; ++i)
1135         currentCues[i].data()->setIsActive(true);
1136 
1137     for (size_t i = 0; i < previousCuesSize; ++i)
1138         if (!currentCues.contains(previousCues[i]))
1139             previousCues[i].data()->setIsActive(false);
1140 
1141     // Update the current active cues.
1142     m_currentlyActiveCues = currentCues;
1143 
1144     if (activeSetChanged)
1145         updateTextTrackDisplay();
1146 }
1147 
textTracksAreReady() const1148 bool HTMLMediaElement::textTracksAreReady() const
1149 {
1150     // 4.8.10.12.1 Text track model
1151     // ...
1152     // The text tracks of a media element are ready if all the text tracks whose mode was not
1153     // in the disabled state when the element's resource selection algorithm last started now
1154     // have a text track readiness state of loaded or failed to load.
1155     for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1156         if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading
1157             || m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::NotLoaded)
1158             return false;
1159     }
1160 
1161     return true;
1162 }
1163 
textTrackReadyStateChanged(TextTrack * track)1164 void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1165 {
1166     if (m_player && m_textTracksWhenResourceSelectionBegan.contains(track)) {
1167         if (track->readinessState() != TextTrack::Loading)
1168             setReadyState(m_player->readyState());
1169     } else {
1170         // The track readiness state might have changed as a result of the user
1171         // clicking the captions button. In this case, a check whether all the
1172         // resources have failed loading should be done in order to hide the CC button.
1173         if (hasMediaControls() && track->readinessState() == TextTrack::FailedToLoad)
1174             mediaControls()->refreshClosedCaptionsButtonVisibility();
1175     }
1176 }
1177 
textTrackModeChanged(TextTrack * track)1178 void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
1179 {
1180     if (track->trackType() == TextTrack::TrackElement) {
1181         // 4.8.10.12.3 Sourcing out-of-band text tracks
1182         // ... when a text track corresponding to a track element is created with text track
1183         // mode set to disabled and subsequently changes its text track mode to hidden, showing,
1184         // or showing by default for the first time, the user agent must immediately and synchronously
1185         // run the following algorithm ...
1186 
1187         for (Node* node = firstChild(); node; node = node->nextSibling()) {
1188             if (!node->hasTagName(trackTag))
1189                 continue;
1190             HTMLTrackElement* trackElement = toHTMLTrackElement(node);
1191             if (trackElement->track() != track)
1192                 continue;
1193 
1194             // Mark this track as "configured" so configureTextTracks won't change the mode again.
1195             track->setHasBeenConfigured(true);
1196             if (track->mode() != TextTrack::disabledKeyword()) {
1197                 if (trackElement->readyState() == HTMLTrackElement::LOADED)
1198                     textTrackAddCues(track, track->cues());
1199 
1200                 // If this is the first added track, create the list of text tracks.
1201                 if (!m_textTracks)
1202                     m_textTracks = TextTrackList::create(this);
1203             }
1204             break;
1205         }
1206     } else if (track->trackType() == TextTrack::AddTrack && track->mode() != TextTrack::disabledKeyword())
1207         textTrackAddCues(track, track->cues());
1208 
1209     configureTextTrackDisplay(AssumeVisibleChange);
1210 
1211     ASSERT(textTracks()->contains(track));
1212     textTracks()->scheduleChangeEvent();
1213 }
1214 
textTrackKindChanged(TextTrack * track)1215 void HTMLMediaElement::textTrackKindChanged(TextTrack* track)
1216 {
1217     if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->mode() == TextTrack::showingKeyword())
1218         track->setMode(TextTrack::hiddenKeyword());
1219 }
1220 
beginIgnoringTrackDisplayUpdateRequests()1221 void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests()
1222 {
1223     ++m_ignoreTrackDisplayUpdate;
1224 }
1225 
endIgnoringTrackDisplayUpdateRequests()1226 void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests()
1227 {
1228     ASSERT(m_ignoreTrackDisplayUpdate);
1229     --m_ignoreTrackDisplayUpdate;
1230     if (!m_ignoreTrackDisplayUpdate && m_active)
1231         updateActiveTextTrackCues(currentTime());
1232 }
1233 
textTrackAddCues(TextTrack * track,const TextTrackCueList * cues)1234 void HTMLMediaElement::textTrackAddCues(TextTrack* track, const TextTrackCueList* cues)
1235 {
1236     WTF_LOG(Media, "HTMLMediaElement::textTrackAddCues");
1237     if (track->mode() == TextTrack::disabledKeyword())
1238         return;
1239 
1240     TrackDisplayUpdateScope scope(this);
1241     for (size_t i = 0; i < cues->length(); ++i)
1242         textTrackAddCue(cues->item(i)->track(), cues->item(i));
1243 }
1244 
textTrackRemoveCues(TextTrack *,const TextTrackCueList * cues)1245 void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues)
1246 {
1247     WTF_LOG(Media, "HTMLMediaElement::textTrackRemoveCues");
1248 
1249     TrackDisplayUpdateScope scope(this);
1250     for (size_t i = 0; i < cues->length(); ++i)
1251         textTrackRemoveCue(cues->item(i)->track(), cues->item(i));
1252 }
1253 
textTrackAddCue(TextTrack * track,PassRefPtr<TextTrackCue> cue)1254 void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtr<TextTrackCue> cue)
1255 {
1256     if (track->mode() == TextTrack::disabledKeyword())
1257         return;
1258 
1259     // Negative duration cues need be treated in the interval tree as
1260     // zero-length cues.
1261     double endTime = max(cue->startTime(), cue->endTime());
1262 
1263     CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1264     if (!m_cueTree.contains(interval))
1265         m_cueTree.add(interval);
1266     updateActiveTextTrackCues(currentTime());
1267 }
1268 
textTrackRemoveCue(TextTrack *,PassRefPtr<TextTrackCue> cue)1269 void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> cue)
1270 {
1271     // Negative duration cues need to be treated in the interval tree as
1272     // zero-length cues.
1273     double endTime = max(cue->startTime(), cue->endTime());
1274 
1275     CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1276     m_cueTree.remove(interval);
1277 
1278     // Since the cue will be removed from the media element and likely the
1279     // TextTrack might also be destructed, notifying the region of the cue
1280     // removal shouldn't be done.
1281     cue->notifyRegionWhenRemovingDisplayTree(false);
1282 
1283     size_t index = m_currentlyActiveCues.find(interval);
1284     if (index != kNotFound) {
1285         m_currentlyActiveCues.remove(index);
1286         cue->setIsActive(false);
1287     }
1288     cue->removeDisplayTree();
1289     updateActiveTextTrackCues(currentTime());
1290 
1291     cue->notifyRegionWhenRemovingDisplayTree(true);
1292 }
1293 
1294 
isSafeToLoadURL(const KURL & url,InvalidURLAction actionIfInvalid)1295 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid)
1296 {
1297     if (!url.isValid()) {
1298         WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE because url is invalid", urlForLoggingMedia(url).utf8().data());
1299         return false;
1300     }
1301 
1302     Frame* frame = document().frame();
1303     if (!frame || !document().securityOrigin()->canDisplay(url)) {
1304         if (actionIfInvalid == Complain)
1305             FrameLoader::reportLocalLoadFailed(frame, url.elidedString());
1306         WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE rejected by SecurityOrigin", urlForLoggingMedia(url).utf8().data());
1307         return false;
1308     }
1309 
1310     if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) {
1311         WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> rejected by Content Security Policy", urlForLoggingMedia(url).utf8().data());
1312         return false;
1313     }
1314 
1315     return true;
1316 }
1317 
startProgressEventTimer()1318 void HTMLMediaElement::startProgressEventTimer()
1319 {
1320     if (m_progressEventTimer.isActive())
1321         return;
1322 
1323     m_previousProgressTime = WTF::currentTime();
1324     // 350ms is not magic, it is in the spec!
1325     m_progressEventTimer.startRepeating(0.350);
1326 }
1327 
waitForSourceChange()1328 void HTMLMediaElement::waitForSourceChange()
1329 {
1330     WTF_LOG(Media, "HTMLMediaElement::waitForSourceChange");
1331 
1332     stopPeriodicTimers();
1333     m_loadState = WaitingForSource;
1334 
1335     // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
1336     m_networkState = NETWORK_NO_SOURCE;
1337 
1338     // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1339     setShouldDelayLoadEvent(false);
1340 
1341     updateDisplayState();
1342 
1343     if (renderer())
1344         renderer()->updateFromElement();
1345 }
1346 
noneSupported()1347 void HTMLMediaElement::noneSupported()
1348 {
1349     WTF_LOG(Media, "HTMLMediaElement::noneSupported");
1350 
1351     stopPeriodicTimers();
1352     m_loadState = WaitingForSource;
1353     m_currentSourceNode = 0;
1354 
1355     // 4.8.10.5
1356     // 6 - Reaching this step indicates that the media resource failed to load or that the given
1357     // URL could not be resolved. In one atomic operation, run the following steps:
1358 
1359     // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
1360     // MEDIA_ERR_SRC_NOT_SUPPORTED.
1361     m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
1362 
1363     // 6.2 - Forget the media element's media-resource-specific text tracks.
1364 
1365     // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
1366     m_networkState = NETWORK_NO_SOURCE;
1367 
1368     // 7 - Queue a task to fire a simple event named error at the media element.
1369     scheduleEvent(EventTypeNames::error);
1370 
1371     closeMediaSource();
1372 
1373     // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1374     setShouldDelayLoadEvent(false);
1375 
1376     // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
1377     // the element won't attempt to load another resource.
1378 
1379     updateDisplayState();
1380 
1381     if (renderer())
1382         renderer()->updateFromElement();
1383 }
1384 
mediaEngineError(PassRefPtr<MediaError> err)1385 void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err)
1386 {
1387     WTF_LOG(Media, "HTMLMediaElement::mediaEngineError(%d)", static_cast<int>(err->code()));
1388 
1389     // 1 - The user agent should cancel the fetching process.
1390     stopPeriodicTimers();
1391     m_loadState = WaitingForSource;
1392 
1393     // 2 - Set the error attribute to a new MediaError object whose code attribute is
1394     // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1395     m_error = err;
1396 
1397     // 3 - Queue a task to fire a simple event named error at the media element.
1398     scheduleEvent(EventTypeNames::error);
1399 
1400     closeMediaSource();
1401 
1402     // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
1403     // task to fire a simple event called emptied at the element.
1404     m_networkState = NETWORK_EMPTY;
1405     scheduleEvent(EventTypeNames::emptied);
1406 
1407     // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1408     setShouldDelayLoadEvent(false);
1409 
1410     // 6 - Abort the overall resource selection algorithm.
1411     m_currentSourceNode = 0;
1412 }
1413 
cancelPendingEventsAndCallbacks()1414 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
1415 {
1416     WTF_LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks");
1417     m_asyncEventQueue->cancelAllEvents();
1418 
1419     for (Node* node = firstChild(); node; node = node->nextSibling()) {
1420         if (node->hasTagName(sourceTag))
1421             toHTMLSourceElement(node)->cancelPendingErrorEvent();
1422     }
1423 }
1424 
mediaPlayerNetworkStateChanged()1425 void HTMLMediaElement::mediaPlayerNetworkStateChanged()
1426 {
1427     setNetworkState(m_player->networkState());
1428 }
1429 
mediaLoadingFailed(MediaPlayer::NetworkState error)1430 void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
1431 {
1432     stopPeriodicTimers();
1433 
1434     // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
1435     // <source> children, schedule the next one
1436     if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
1437 
1438         if (m_currentSourceNode)
1439             m_currentSourceNode->scheduleErrorEvent();
1440         else
1441             WTF_LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed");
1442 
1443         if (havePotentialSourceChild()) {
1444             WTF_LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>");
1445             scheduleNextSourceChild();
1446         } else {
1447             WTF_LOG(Media, "HTMLMediaElement::setNetworkState - no more <source> elements, waiting");
1448             waitForSourceChange();
1449         }
1450 
1451         return;
1452     }
1453 
1454     if (error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA)
1455         mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK));
1456     else if (error == MediaPlayer::DecodeError)
1457         mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE));
1458     else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr)
1459         noneSupported();
1460 
1461     updateDisplayState();
1462     if (hasMediaControls()) {
1463         mediaControls()->reset();
1464         mediaControls()->reportedError();
1465     }
1466 }
1467 
setNetworkState(MediaPlayer::NetworkState state)1468 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
1469 {
1470     WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%d) - current state is %d", static_cast<int>(state), static_cast<int>(m_networkState));
1471 
1472     if (state == MediaPlayer::Empty) {
1473         // Just update the cached state and leave, we can't do anything.
1474         m_networkState = NETWORK_EMPTY;
1475         return;
1476     }
1477 
1478     if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
1479         mediaLoadingFailed(state);
1480         return;
1481     }
1482 
1483     if (state == MediaPlayer::Idle) {
1484         if (m_networkState > NETWORK_IDLE) {
1485             changeNetworkStateFromLoadingToIdle();
1486             setShouldDelayLoadEvent(false);
1487         } else {
1488             m_networkState = NETWORK_IDLE;
1489         }
1490     }
1491 
1492     if (state == MediaPlayer::Loading) {
1493         if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
1494             startProgressEventTimer();
1495         m_networkState = NETWORK_LOADING;
1496     }
1497 
1498     if (state == MediaPlayer::Loaded) {
1499         if (m_networkState != NETWORK_IDLE)
1500             changeNetworkStateFromLoadingToIdle();
1501         m_completelyLoaded = true;
1502     }
1503 
1504     if (hasMediaControls())
1505         mediaControls()->updateStatusDisplay();
1506 }
1507 
changeNetworkStateFromLoadingToIdle()1508 void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
1509 {
1510     m_progressEventTimer.stop();
1511     if (hasMediaControls() && m_player->didLoadingProgress())
1512         mediaControls()->bufferingProgressed();
1513 
1514     // Schedule one last progress event so we guarantee that at least one is fired
1515     // for files that load very quickly.
1516     scheduleEvent(EventTypeNames::progress);
1517     scheduleEvent(EventTypeNames::suspend);
1518     m_networkState = NETWORK_IDLE;
1519 }
1520 
mediaPlayerReadyStateChanged()1521 void HTMLMediaElement::mediaPlayerReadyStateChanged()
1522 {
1523     setReadyState(m_player->readyState());
1524 }
1525 
setReadyState(MediaPlayer::ReadyState state)1526 void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
1527 {
1528     WTF_LOG(Media, "HTMLMediaElement::setReadyState(%d) - current state is %d,", static_cast<int>(state), static_cast<int>(m_readyState));
1529 
1530     // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
1531     bool wasPotentiallyPlaying = potentiallyPlaying();
1532 
1533     ReadyState oldState = m_readyState;
1534     ReadyState newState = static_cast<ReadyState>(state);
1535 
1536     bool tracksAreReady = !RuntimeEnabledFeatures::videoTrackEnabled() || textTracksAreReady();
1537 
1538     if (newState == oldState && m_tracksAreReady == tracksAreReady)
1539         return;
1540 
1541     m_tracksAreReady = tracksAreReady;
1542 
1543     if (tracksAreReady)
1544         m_readyState = newState;
1545     else {
1546         // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
1547         // the text tracks are ready, regardless of the state of the media file.
1548         if (newState <= HAVE_METADATA)
1549             m_readyState = newState;
1550         else
1551             m_readyState = HAVE_CURRENT_DATA;
1552     }
1553 
1554     if (oldState > m_readyStateMaximum)
1555         m_readyStateMaximum = oldState;
1556 
1557     if (m_networkState == NETWORK_EMPTY)
1558         return;
1559 
1560     if (m_seeking) {
1561         // 4.8.10.9, step 9 note: If the media element was potentially playing immediately before
1562         // it started seeking, but seeking caused its readyState attribute to change to a value
1563         // lower than HAVE_FUTURE_DATA, then a waiting will be fired at the element.
1564         if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
1565             scheduleEvent(EventTypeNames::waiting);
1566 
1567         // 4.8.10.9 steps 12-14
1568         if (m_readyState >= HAVE_CURRENT_DATA)
1569             finishSeek();
1570     } else {
1571         if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
1572             // 4.8.10.8
1573             scheduleTimeupdateEvent(false);
1574             scheduleEvent(EventTypeNames::waiting);
1575         }
1576     }
1577 
1578     if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
1579         prepareMediaFragmentURI();
1580         scheduleEvent(EventTypeNames::durationchange);
1581         scheduleEvent(EventTypeNames::loadedmetadata);
1582         if (hasMediaControls())
1583             mediaControls()->loadedMetadata();
1584         if (renderer())
1585             renderer()->updateFromElement();
1586     }
1587 
1588     bool shouldUpdateDisplayState = false;
1589 
1590     if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
1591         m_haveFiredLoadedData = true;
1592         shouldUpdateDisplayState = true;
1593         scheduleEvent(EventTypeNames::loadeddata);
1594         setShouldDelayLoadEvent(false);
1595         applyMediaFragmentURI();
1596     }
1597 
1598     bool isPotentiallyPlaying = potentiallyPlaying();
1599     if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
1600         scheduleEvent(EventTypeNames::canplay);
1601         if (isPotentiallyPlaying)
1602             scheduleEvent(EventTypeNames::playing);
1603         shouldUpdateDisplayState = true;
1604     }
1605 
1606     if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
1607         if (oldState <= HAVE_CURRENT_DATA)
1608             scheduleEvent(EventTypeNames::canplay);
1609 
1610         scheduleEvent(EventTypeNames::canplaythrough);
1611 
1612         if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA)
1613             scheduleEvent(EventTypeNames::playing);
1614 
1615         if (m_autoplaying && m_paused && autoplay() && !document().isSandboxed(SandboxAutomaticFeatures) && !userGestureRequiredForRateChange()) {
1616             m_paused = false;
1617             invalidateCachedTime();
1618             scheduleEvent(EventTypeNames::play);
1619             scheduleEvent(EventTypeNames::playing);
1620         }
1621 
1622         shouldUpdateDisplayState = true;
1623     }
1624 
1625     if (shouldUpdateDisplayState) {
1626         updateDisplayState();
1627         if (hasMediaControls()) {
1628             mediaControls()->refreshClosedCaptionsButtonVisibility();
1629             mediaControls()->updateStatusDisplay();
1630         }
1631     }
1632 
1633     updatePlayState();
1634     updateMediaController();
1635     if (RuntimeEnabledFeatures::videoTrackEnabled())
1636         updateActiveTextTrackCues(currentTime());
1637 }
1638 
mediaPlayerKeyAdded(const String & keySystem,const String & sessionId)1639 void HTMLMediaElement::mediaPlayerKeyAdded(const String& keySystem, const String& sessionId)
1640 {
1641     MediaKeyEventInit initializer;
1642     initializer.keySystem = keySystem;
1643     initializer.sessionId = sessionId;
1644     initializer.bubbles = false;
1645     initializer.cancelable = false;
1646 
1647     RefPtr<Event> event = MediaKeyEvent::create(EventTypeNames::webkitkeyadded, initializer);
1648     event->setTarget(this);
1649     m_asyncEventQueue->enqueueEvent(event.release());
1650 }
1651 
mediaPlayerKeyError(const String & keySystem,const String & sessionId,MediaPlayerClient::MediaKeyErrorCode errorCode,unsigned short systemCode)1652 void HTMLMediaElement::mediaPlayerKeyError(const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode)
1653 {
1654     MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
1655     switch (errorCode) {
1656     case MediaPlayerClient::UnknownError:
1657         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
1658         break;
1659     case MediaPlayerClient::ClientError:
1660         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
1661         break;
1662     case MediaPlayerClient::ServiceError:
1663         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_SERVICE;
1664         break;
1665     case MediaPlayerClient::OutputError:
1666         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_OUTPUT;
1667         break;
1668     case MediaPlayerClient::HardwareChangeError:
1669         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_HARDWARECHANGE;
1670         break;
1671     case MediaPlayerClient::DomainError:
1672         mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_DOMAIN;
1673         break;
1674     }
1675 
1676     MediaKeyEventInit initializer;
1677     initializer.keySystem = keySystem;
1678     initializer.sessionId = sessionId;
1679     initializer.errorCode = MediaKeyError::create(mediaKeyErrorCode);
1680     initializer.systemCode = systemCode;
1681     initializer.bubbles = false;
1682     initializer.cancelable = false;
1683 
1684     RefPtr<Event> event = MediaKeyEvent::create(EventTypeNames::webkitkeyerror, initializer);
1685     event->setTarget(this);
1686     m_asyncEventQueue->enqueueEvent(event.release());
1687 }
1688 
mediaPlayerKeyMessage(const String & keySystem,const String & sessionId,const unsigned char * message,unsigned messageLength,const KURL & defaultURL)1689 void HTMLMediaElement::mediaPlayerKeyMessage(const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const KURL& defaultURL)
1690 {
1691     MediaKeyEventInit initializer;
1692     initializer.keySystem = keySystem;
1693     initializer.sessionId = sessionId;
1694     initializer.message = Uint8Array::create(message, messageLength);
1695     initializer.defaultURL = defaultURL;
1696     initializer.bubbles = false;
1697     initializer.cancelable = false;
1698 
1699     RefPtr<Event> event = MediaKeyEvent::create(EventTypeNames::webkitkeymessage, initializer);
1700     event->setTarget(this);
1701     m_asyncEventQueue->enqueueEvent(event.release());
1702 }
1703 
mediaPlayerKeyNeeded(const String & keySystem,const String & sessionId,const unsigned char * initData,unsigned initDataLength)1704 bool HTMLMediaElement::mediaPlayerKeyNeeded(const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength)
1705 {
1706     if (!hasEventListeners(EventTypeNames::webkitneedkey)) {
1707         m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
1708         scheduleEvent(EventTypeNames::error);
1709         return false;
1710     }
1711 
1712     MediaKeyEventInit initializer;
1713     initializer.keySystem = keySystem;
1714     initializer.sessionId = sessionId;
1715     initializer.initData = Uint8Array::create(initData, initDataLength);
1716     initializer.bubbles = false;
1717     initializer.cancelable = false;
1718 
1719     RefPtr<Event> event = MediaKeyEvent::create(EventTypeNames::webkitneedkey, initializer);
1720     event->setTarget(this);
1721     m_asyncEventQueue->enqueueEvent(event.release());
1722     return true;
1723 }
1724 
mediaPlayerKeyNeeded(Uint8Array * initData)1725 bool HTMLMediaElement::mediaPlayerKeyNeeded(Uint8Array* initData)
1726 {
1727     if (!hasEventListeners("webkitneedkey")) {
1728         m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
1729         scheduleEvent(EventTypeNames::error);
1730         return false;
1731     }
1732 
1733     MediaKeyNeededEventInit initializer;
1734     initializer.initData = initData;
1735     initializer.bubbles = false;
1736     initializer.cancelable = false;
1737 
1738     RefPtr<Event> event = MediaKeyNeededEvent::create(EventTypeNames::webkitneedkey, initializer);
1739     event->setTarget(this);
1740     m_asyncEventQueue->enqueueEvent(event.release());
1741 
1742     return true;
1743 }
1744 
setMediaKeys(MediaKeys * mediaKeys)1745 void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys)
1746 {
1747     if (m_mediaKeys == mediaKeys)
1748         return;
1749 
1750     if (m_mediaKeys)
1751         m_mediaKeys->setMediaElement(0);
1752     m_mediaKeys = mediaKeys;
1753     if (m_mediaKeys)
1754         m_mediaKeys->setMediaElement(this);
1755 }
1756 
progressEventTimerFired(Timer<HTMLMediaElement> *)1757 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
1758 {
1759     ASSERT(m_player);
1760     if (m_networkState != NETWORK_LOADING)
1761         return;
1762 
1763     double time = WTF::currentTime();
1764     double timedelta = time - m_previousProgressTime;
1765 
1766     if (m_player->didLoadingProgress()) {
1767         scheduleEvent(EventTypeNames::progress);
1768         m_previousProgressTime = time;
1769         m_sentStalledEvent = false;
1770         if (renderer())
1771             renderer()->updateFromElement();
1772         if (hasMediaControls())
1773             mediaControls()->bufferingProgressed();
1774     } else if (timedelta > 3.0 && !m_sentStalledEvent) {
1775         scheduleEvent(EventTypeNames::stalled);
1776         m_sentStalledEvent = true;
1777         setShouldDelayLoadEvent(false);
1778     }
1779 }
1780 
addPlayedRange(double start,double end)1781 void HTMLMediaElement::addPlayedRange(double start, double end)
1782 {
1783     WTF_LOG(Media, "HTMLMediaElement::addPlayedRange(%f, %f)", start, end);
1784     if (!m_playedTimeRanges)
1785         m_playedTimeRanges = TimeRanges::create();
1786     m_playedTimeRanges->add(start, end);
1787 }
1788 
supportsSave() const1789 bool HTMLMediaElement::supportsSave() const
1790 {
1791     return m_player ? m_player->supportsSave() : false;
1792 }
1793 
prepareToPlay()1794 void HTMLMediaElement::prepareToPlay()
1795 {
1796     WTF_LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this);
1797     if (m_havePreparedToPlay)
1798         return;
1799     m_havePreparedToPlay = true;
1800     m_player->prepareToPlay();
1801 }
1802 
seek(double time,ExceptionState & exceptionState)1803 void HTMLMediaElement::seek(double time, ExceptionState& exceptionState)
1804 {
1805     WTF_LOG(Media, "HTMLMediaElement::seek(%f)", time);
1806 
1807     // 4.8.10.9 Seeking
1808 
1809     // 1 - If the media element's readyState is HAVE_NOTHING, then raise an InvalidStateError exception.
1810     if (m_readyState == HAVE_NOTHING || !m_player) {
1811         exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
1812         return;
1813     }
1814 
1815     // If the media engine has been told to postpone loading data, let it go ahead now.
1816     if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
1817         prepareToPlay();
1818 
1819     // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
1820     refreshCachedTime();
1821     double now = currentTime();
1822 
1823     // 2 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
1824     // already running. Abort that other instance of the algorithm without waiting for the step that
1825     // it is running to complete.
1826     // Nothing specific to be done here.
1827 
1828     // 3 - Set the seeking IDL attribute to true.
1829     // The flag will be cleared when the engine tells us the time has actually changed.
1830     m_seeking = true;
1831 
1832     // 5 - If the new playback position is later than the end of the media resource, then let it be the end
1833     // of the media resource instead.
1834     time = min(time, duration());
1835 
1836     // 6 - If the new playback position is less than the earliest possible position, let it be that position instead.
1837     time = max(time, 0.0);
1838 
1839     // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
1840     // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
1841     // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
1842     // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
1843     // fire a 'seeked' event.
1844 #if !LOG_DISABLED
1845     double mediaTime = m_player->mediaTimeForTimeValue(time);
1846     if (time != mediaTime)
1847         WTF_LOG(Media, "HTMLMediaElement::seek(%f) - media timeline equivalent is %f", time, mediaTime);
1848 #endif
1849     time = m_player->mediaTimeForTimeValue(time);
1850 
1851     // 7 - If the (possibly now changed) new playback position is not in one of the ranges given in the
1852     // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
1853     // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
1854     // attribute then set the seeking IDL attribute to false and abort these steps.
1855     RefPtr<TimeRanges> seekableRanges = seekable();
1856 
1857     // Short circuit seeking to the current time by just firing the events if no seek is required.
1858     // Don't skip calling the media engine if we are in poster mode because a seek should always
1859     // cancel poster display.
1860     bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster);
1861 
1862     // Always notify the media engine of a seek if the source is not closed. This ensures that the source is
1863     // always in a flushed state when the 'seeking' event fires.
1864     if (m_mediaSource && m_mediaSource->isClosed())
1865         noSeekRequired = false;
1866 
1867     if (noSeekRequired) {
1868         if (time == now) {
1869             scheduleEvent(EventTypeNames::seeking);
1870             // FIXME: There must be a stable state before timeupdate+seeked are dispatched and seeking
1871             // is reset to false. See http://crbug.com/266631
1872             scheduleTimeupdateEvent(false);
1873             scheduleEvent(EventTypeNames::seeked);
1874         }
1875         m_seeking = false;
1876         return;
1877     }
1878     time = seekableRanges->nearest(time);
1879 
1880     if (m_playing) {
1881         if (m_lastSeekTime < now)
1882             addPlayedRange(m_lastSeekTime, now);
1883     }
1884     m_lastSeekTime = time;
1885     m_sentEndEvent = false;
1886 
1887     // 8 - Queue a task to fire a simple event named seeking at the element.
1888     scheduleEvent(EventTypeNames::seeking);
1889 
1890     // 9 - Set the current playback position to the given new playback position
1891     m_player->seek(time);
1892 
1893     // 10-14 are handled, if necessary, when the engine signals a readystate change or otherwise
1894     // satisfies seek completion and signals a time change.
1895 }
1896 
finishSeek()1897 void HTMLMediaElement::finishSeek()
1898 {
1899     WTF_LOG(Media, "HTMLMediaElement::finishSeek");
1900 
1901     // 4.8.10.9 Seeking completion
1902     // 12 - Set the seeking IDL attribute to false.
1903     m_seeking = false;
1904 
1905     // 13 - Queue a task to fire a simple event named timeupdate at the element.
1906     scheduleTimeupdateEvent(false);
1907 
1908     // 14 - Queue a task to fire a simple event named seeked at the element.
1909     scheduleEvent(EventTypeNames::seeked);
1910 
1911     setDisplayMode(Video);
1912 }
1913 
readyState() const1914 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
1915 {
1916     return m_readyState;
1917 }
1918 
hasAudio() const1919 bool HTMLMediaElement::hasAudio() const
1920 {
1921     return m_player ? m_player->hasAudio() : false;
1922 }
1923 
seeking() const1924 bool HTMLMediaElement::seeking() const
1925 {
1926     return m_seeking;
1927 }
1928 
refreshCachedTime() const1929 void HTMLMediaElement::refreshCachedTime() const
1930 {
1931     m_cachedTime = m_player->currentTime();
1932     m_cachedTimeWallClockUpdateTime = WTF::currentTime();
1933 }
1934 
invalidateCachedTime()1935 void HTMLMediaElement::invalidateCachedTime()
1936 {
1937     WTF_LOG(Media, "HTMLMediaElement::invalidateCachedTime");
1938 
1939     // Don't try to cache movie time when playback first starts as the time reported by the engine
1940     // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it
1941     // too early.
1942     static const double minimumTimePlayingBeforeCacheSnapshot = 0.5;
1943 
1944     m_minimumWallClockTimeToCacheMediaTime = WTF::currentTime() + minimumTimePlayingBeforeCacheSnapshot;
1945     m_cachedTime = MediaPlayer::invalidTime();
1946 }
1947 
1948 // playback state
currentTime() const1949 double HTMLMediaElement::currentTime() const
1950 {
1951 #if LOG_CACHED_TIME_WARNINGS
1952     static const double minCachedDeltaForWarning = 0.01;
1953 #endif
1954 
1955     if (!m_player)
1956         return 0;
1957 
1958     if (m_seeking) {
1959         WTF_LOG(Media, "HTMLMediaElement::currentTime - seeking, returning %f", m_lastSeekTime);
1960         return m_lastSeekTime;
1961     }
1962 
1963     if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) {
1964 #if LOG_CACHED_TIME_WARNINGS
1965         double delta = m_cachedTime - m_player->currentTime();
1966         if (delta > minCachedDeltaForWarning)
1967             WTF_LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when paused", delta);
1968 #endif
1969         return m_cachedTime;
1970     }
1971 
1972     refreshCachedTime();
1973 
1974     return m_cachedTime;
1975 }
1976 
setCurrentTime(double time,ExceptionState & exceptionState)1977 void HTMLMediaElement::setCurrentTime(double time, ExceptionState& exceptionState)
1978 {
1979     if (m_mediaController) {
1980         exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
1981         return;
1982     }
1983     seek(time, exceptionState);
1984 }
1985 
duration() const1986 double HTMLMediaElement::duration() const
1987 {
1988     if (!m_player || m_readyState < HAVE_METADATA)
1989         return numeric_limits<double>::quiet_NaN();
1990 
1991     // FIXME: Refactor so m_duration is kept current (in both MSE and
1992     // non-MSE cases) once we have transitioned from HAVE_NOTHING ->
1993     // HAVE_METADATA. Currently, m_duration may be out of date for at least MSE
1994     // case because MediaSourceBase and SourceBuffer do not notify the element
1995     // directly upon duration changes caused by endOfStream, remove, or append
1996     // operations; rather the notification is triggered by the WebMediaPlayer
1997     // implementation observing that the underlying engine has updated duration
1998     // and notifying the element to consult its MediaSource for current
1999     // duration. See http://crbug.com/266644
2000 
2001     if (m_mediaSource)
2002         return m_mediaSource->duration();
2003 
2004     return m_player->duration();
2005 }
2006 
paused() const2007 bool HTMLMediaElement::paused() const
2008 {
2009     return m_paused;
2010 }
2011 
defaultPlaybackRate() const2012 double HTMLMediaElement::defaultPlaybackRate() const
2013 {
2014     return m_defaultPlaybackRate;
2015 }
2016 
setDefaultPlaybackRate(double rate)2017 void HTMLMediaElement::setDefaultPlaybackRate(double rate)
2018 {
2019     if (m_defaultPlaybackRate != rate) {
2020         m_defaultPlaybackRate = rate;
2021         scheduleEvent(EventTypeNames::ratechange);
2022     }
2023 }
2024 
playbackRate() const2025 double HTMLMediaElement::playbackRate() const
2026 {
2027     return m_playbackRate;
2028 }
2029 
setPlaybackRate(double rate)2030 void HTMLMediaElement::setPlaybackRate(double rate)
2031 {
2032     WTF_LOG(Media, "HTMLMediaElement::setPlaybackRate(%f)", rate);
2033 
2034     if (m_playbackRate != rate) {
2035         m_playbackRate = rate;
2036         invalidateCachedTime();
2037         scheduleEvent(EventTypeNames::ratechange);
2038     }
2039 
2040     if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController)
2041         m_player->setRate(rate);
2042 }
2043 
updatePlaybackRate()2044 void HTMLMediaElement::updatePlaybackRate()
2045 {
2046     double effectiveRate = m_mediaController ? m_mediaController->playbackRate() : m_playbackRate;
2047     if (m_player && potentiallyPlaying() && m_player->rate() != effectiveRate)
2048         m_player->setRate(effectiveRate);
2049 }
2050 
ended() const2051 bool HTMLMediaElement::ended() const
2052 {
2053     // 4.8.10.8 Playing the media resource
2054     // The ended attribute must return true if the media element has ended
2055     // playback and the direction of playback is forwards, and false otherwise.
2056     return endedPlayback() && m_playbackRate > 0;
2057 }
2058 
autoplay() const2059 bool HTMLMediaElement::autoplay() const
2060 {
2061     return fastHasAttribute(autoplayAttr);
2062 }
2063 
preload() const2064 String HTMLMediaElement::preload() const
2065 {
2066     switch (m_preload) {
2067     case MediaPlayer::None:
2068         return "none";
2069         break;
2070     case MediaPlayer::MetaData:
2071         return "metadata";
2072         break;
2073     case MediaPlayer::Auto:
2074         return "auto";
2075         break;
2076     }
2077 
2078     ASSERT_NOT_REACHED();
2079     return String();
2080 }
2081 
setPreload(const String & preload)2082 void HTMLMediaElement::setPreload(const String& preload)
2083 {
2084     WTF_LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data());
2085     setAttribute(preloadAttr, preload);
2086 }
2087 
play()2088 void HTMLMediaElement::play()
2089 {
2090     WTF_LOG(Media, "HTMLMediaElement::play()");
2091 
2092     if (userGestureRequiredForRateChange() && !UserGestureIndicator::processingUserGesture())
2093         return;
2094     if (UserGestureIndicator::processingUserGesture())
2095         removeBehaviorsRestrictionsAfterFirstUserGesture();
2096 
2097     playInternal();
2098 }
2099 
playInternal()2100 void HTMLMediaElement::playInternal()
2101 {
2102     WTF_LOG(Media, "HTMLMediaElement::playInternal");
2103 
2104     // 4.8.10.9. Playing the media resource
2105     if (!m_player || m_networkState == NETWORK_EMPTY)
2106         scheduleDelayedAction(LoadMediaResource);
2107 
2108     if (endedPlayback())
2109         seek(0, IGNORE_EXCEPTION);
2110 
2111     if (m_mediaController)
2112         m_mediaController->bringElementUpToSpeed(this);
2113 
2114     if (m_paused) {
2115         m_paused = false;
2116         invalidateCachedTime();
2117         scheduleEvent(EventTypeNames::play);
2118 
2119         if (m_readyState <= HAVE_CURRENT_DATA)
2120             scheduleEvent(EventTypeNames::waiting);
2121         else if (m_readyState >= HAVE_FUTURE_DATA)
2122             scheduleEvent(EventTypeNames::playing);
2123     }
2124     m_autoplaying = false;
2125 
2126     updatePlayState();
2127     updateMediaController();
2128 }
2129 
pause()2130 void HTMLMediaElement::pause()
2131 {
2132     WTF_LOG(Media, "HTMLMediaElement::pause()");
2133 
2134     if (userGestureRequiredForRateChange() && !UserGestureIndicator::processingUserGesture())
2135         return;
2136 
2137     pauseInternal();
2138 }
2139 
2140 
pauseInternal()2141 void HTMLMediaElement::pauseInternal()
2142 {
2143     WTF_LOG(Media, "HTMLMediaElement::pauseInternal");
2144 
2145     // 4.8.10.9. Playing the media resource
2146     if (!m_player || m_networkState == NETWORK_EMPTY)
2147         scheduleDelayedAction(LoadMediaResource);
2148 
2149     m_autoplaying = false;
2150 
2151     if (!m_paused) {
2152         m_paused = true;
2153         scheduleTimeupdateEvent(false);
2154         scheduleEvent(EventTypeNames::pause);
2155     }
2156 
2157     updatePlayState();
2158 }
2159 
closeMediaSource()2160 void HTMLMediaElement::closeMediaSource()
2161 {
2162     if (!m_mediaSource)
2163         return;
2164 
2165     m_mediaSource->close();
2166     m_mediaSource = 0;
2167 }
2168 
webkitGenerateKeyRequest(const String & keySystem,PassRefPtr<Uint8Array> initData,ExceptionState & exceptionState)2169 void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, PassRefPtr<Uint8Array> initData, ExceptionState& exceptionState)
2170 {
2171     if (keySystem.isEmpty()) {
2172         exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
2173         return;
2174     }
2175 
2176     if (!m_player) {
2177         exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
2178         return;
2179     }
2180 
2181     const unsigned char* initDataPointer = 0;
2182     unsigned initDataLength = 0;
2183     if (initData) {
2184         initDataPointer = initData->data();
2185         initDataLength = initData->length();
2186     }
2187 
2188     MediaPlayer::MediaKeyException result = m_player->generateKeyRequest(keySystem, initDataPointer, initDataLength);
2189     throwExceptionForMediaKeyException(result, exceptionState);
2190 }
2191 
webkitGenerateKeyRequest(const String & keySystem,ExceptionState & exceptionState)2192 void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, ExceptionState& exceptionState)
2193 {
2194     webkitGenerateKeyRequest(keySystem, Uint8Array::create(0), exceptionState);
2195 }
2196 
webkitAddKey(const String & keySystem,PassRefPtr<Uint8Array> key,PassRefPtr<Uint8Array> initData,const String & sessionId,ExceptionState & exceptionState)2197 void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, PassRefPtr<Uint8Array> initData, const String& sessionId, ExceptionState& exceptionState)
2198 {
2199     if (keySystem.isEmpty()) {
2200         exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
2201         return;
2202     }
2203 
2204     if (!key) {
2205         exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
2206         return;
2207     }
2208 
2209     if (!key->length()) {
2210         exceptionState.throwUninformativeAndGenericDOMException(TypeMismatchError);
2211         return;
2212     }
2213 
2214     if (!m_player) {
2215         exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
2216         return;
2217     }
2218 
2219     const unsigned char* initDataPointer = 0;
2220     unsigned initDataLength = 0;
2221     if (initData) {
2222         initDataPointer = initData->data();
2223         initDataLength = initData->length();
2224     }
2225 
2226     MediaPlayer::MediaKeyException result = m_player->addKey(keySystem, key->data(), key->length(), initDataPointer, initDataLength, sessionId);
2227     throwExceptionForMediaKeyException(result, exceptionState);
2228 }
2229 
webkitAddKey(const String & keySystem,PassRefPtr<Uint8Array> key,ExceptionState & exceptionState)2230 void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, ExceptionState& exceptionState)
2231 {
2232     webkitAddKey(keySystem, key, Uint8Array::create(0), String(), exceptionState);
2233 }
2234 
webkitCancelKeyRequest(const String & keySystem,const String & sessionId,ExceptionState & exceptionState)2235 void HTMLMediaElement::webkitCancelKeyRequest(const String& keySystem, const String& sessionId, ExceptionState& exceptionState)
2236 {
2237     if (keySystem.isEmpty()) {
2238         exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
2239         return;
2240     }
2241 
2242     if (!m_player) {
2243         exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError);
2244         return;
2245     }
2246 
2247     MediaPlayer::MediaKeyException result = m_player->cancelKeyRequest(keySystem, sessionId);
2248     throwExceptionForMediaKeyException(result, exceptionState);
2249 }
2250 
loop() const2251 bool HTMLMediaElement::loop() const
2252 {
2253     return fastHasAttribute(loopAttr);
2254 }
2255 
setLoop(bool b)2256 void HTMLMediaElement::setLoop(bool b)
2257 {
2258     WTF_LOG(Media, "HTMLMediaElement::setLoop(%s)", boolString(b));
2259     setBooleanAttribute(loopAttr, b);
2260 }
2261 
controls() const2262 bool HTMLMediaElement::controls() const
2263 {
2264     Frame* frame = document().frame();
2265 
2266     // always show controls when scripting is disabled
2267     if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript))
2268         return true;
2269 
2270     // Always show controls when in full screen mode.
2271     if (isFullscreen())
2272         return true;
2273 
2274     return fastHasAttribute(controlsAttr);
2275 }
2276 
setControls(bool b)2277 void HTMLMediaElement::setControls(bool b)
2278 {
2279     WTF_LOG(Media, "HTMLMediaElement::setControls(%s)", boolString(b));
2280     setBooleanAttribute(controlsAttr, b);
2281 }
2282 
volume() const2283 double HTMLMediaElement::volume() const
2284 {
2285     return m_volume;
2286 }
2287 
setVolume(double vol,ExceptionState & exceptionState)2288 void HTMLMediaElement::setVolume(double vol, ExceptionState& exceptionState)
2289 {
2290     WTF_LOG(Media, "HTMLMediaElement::setVolume(%f)", vol);
2291 
2292     if (vol < 0.0f || vol > 1.0f) {
2293         exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
2294         return;
2295     }
2296 
2297     if (m_volume != vol) {
2298         m_volume = vol;
2299         updateVolume();
2300         scheduleEvent(EventTypeNames::volumechange);
2301     }
2302 }
2303 
muted() const2304 bool HTMLMediaElement::muted() const
2305 {
2306     return m_muted;
2307 }
2308 
setMuted(bool muted)2309 void HTMLMediaElement::setMuted(bool muted)
2310 {
2311     WTF_LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted));
2312 
2313     if (m_muted != muted) {
2314         m_muted = muted;
2315         if (m_player) {
2316             m_player->setMuted(m_muted);
2317             if (hasMediaControls())
2318                 mediaControls()->changedMute();
2319         }
2320         scheduleEvent(EventTypeNames::volumechange);
2321     }
2322 }
2323 
togglePlayState()2324 void HTMLMediaElement::togglePlayState()
2325 {
2326     WTF_LOG(Media, "HTMLMediaElement::togglePlayState - canPlay() is %s", boolString(canPlay()));
2327 
2328     // We can safely call the internal play/pause methods, which don't check restrictions, because
2329     // this method is only called from the built-in media controller
2330     if (canPlay()) {
2331         updatePlaybackRate();
2332         playInternal();
2333     } else
2334         pauseInternal();
2335 }
2336 
beginScrubbing()2337 void HTMLMediaElement::beginScrubbing()
2338 {
2339     WTF_LOG(Media, "HTMLMediaElement::beginScrubbing - paused() is %s", boolString(paused()));
2340 
2341     if (!paused()) {
2342         if (ended()) {
2343             // Because a media element stays in non-paused state when it reaches end, playback resumes
2344             // when the slider is dragged from the end to another position unless we pause first. Do
2345             // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes.
2346             pause();
2347         } else {
2348             // Not at the end but we still want to pause playback so the media engine doesn't try to
2349             // continue playing during scrubbing. Pause without generating an event as we will
2350             // unpause after scrubbing finishes.
2351             setPausedInternal(true);
2352         }
2353     }
2354 }
2355 
endScrubbing()2356 void HTMLMediaElement::endScrubbing()
2357 {
2358     WTF_LOG(Media, "HTMLMediaElement::endScrubbing - m_pausedInternal is %s", boolString(m_pausedInternal));
2359 
2360     if (m_pausedInternal)
2361         setPausedInternal(false);
2362 }
2363 
2364 // The spec says to fire periodic timeupdate events (those sent while playing) every
2365 // "15 to 250ms", we choose the slowest frequency
2366 static const double maxTimeupdateEventFrequency = 0.25;
2367 
startPlaybackProgressTimer()2368 void HTMLMediaElement::startPlaybackProgressTimer()
2369 {
2370     if (m_playbackProgressTimer.isActive())
2371         return;
2372 
2373     m_previousProgressTime = WTF::currentTime();
2374     m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency);
2375 }
2376 
playbackProgressTimerFired(Timer<HTMLMediaElement> *)2377 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*)
2378 {
2379     ASSERT(m_player);
2380 
2381     if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && m_playbackRate > 0) {
2382         m_fragmentEndTime = MediaPlayer::invalidTime();
2383         if (!m_mediaController && !m_paused) {
2384             // changes paused to true and fires a simple event named pause at the media element.
2385             pauseInternal();
2386         }
2387     }
2388 
2389     if (!m_seeking)
2390         scheduleTimeupdateEvent(true);
2391 
2392     if (!m_playbackRate)
2393         return;
2394 
2395     if (!m_paused && hasMediaControls())
2396         mediaControls()->playbackProgressed();
2397 
2398     if (RuntimeEnabledFeatures::videoTrackEnabled())
2399         updateActiveTextTrackCues(currentTime());
2400 }
2401 
scheduleTimeupdateEvent(bool periodicEvent)2402 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
2403 {
2404     double now = WTF::currentTime();
2405     double timedelta = now - m_lastTimeUpdateEventWallTime;
2406 
2407     // throttle the periodic events
2408     if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
2409         return;
2410 
2411     // Some media engines make multiple "time changed" callbacks at the same time, but we only want one
2412     // event at a given time so filter here
2413     double movieTime = currentTime();
2414     if (movieTime != m_lastTimeUpdateEventMovieTime) {
2415         scheduleEvent(EventTypeNames::timeupdate);
2416         m_lastTimeUpdateEventWallTime = now;
2417         m_lastTimeUpdateEventMovieTime = movieTime;
2418     }
2419 }
2420 
canPlay() const2421 bool HTMLMediaElement::canPlay() const
2422 {
2423     return paused() || ended() || m_readyState < HAVE_METADATA;
2424 }
2425 
percentLoaded() const2426 double HTMLMediaElement::percentLoaded() const
2427 {
2428     if (!m_player)
2429         return 0;
2430     double duration = m_player->duration();
2431 
2432     if (!duration || std::isinf(duration))
2433         return 0;
2434 
2435     double buffered = 0;
2436     RefPtr<TimeRanges> timeRanges = m_player->buffered();
2437     for (unsigned i = 0; i < timeRanges->length(); ++i) {
2438         double start = timeRanges->start(i, IGNORE_EXCEPTION);
2439         double end = timeRanges->end(i, IGNORE_EXCEPTION);
2440         buffered += end - start;
2441     }
2442     return buffered / duration;
2443 }
2444 
mediaPlayerDidAddTrack(WebInbandTextTrack * webTrack)2445 void HTMLMediaElement::mediaPlayerDidAddTrack(WebInbandTextTrack* webTrack)
2446 {
2447     if (!RuntimeEnabledFeatures::videoTrackEnabled())
2448         return;
2449 
2450     // 4.8.10.12.2 Sourcing in-band text tracks
2451     // 1. Associate the relevant data with a new text track and its corresponding new TextTrack object.
2452     RefPtr<InbandTextTrack> textTrack = InbandTextTrack::create(document(), this, webTrack);
2453 
2454     // 2. Set the new text track's kind, label, and language based on the semantics of the relevant data,
2455     // as defined by the relevant specification. If there is no label in that data, then the label must
2456     // be set to the empty string.
2457     // 3. Associate the text track list of cues with the rules for updating the text track rendering appropriate
2458     // for the format in question.
2459     // 4. If the new text track's kind is metadata, then set the text track in-band metadata track dispatch type
2460     // as follows, based on the type of the media resource:
2461     // 5. Populate the new text track's list of cues with the cues parsed so far, folllowing the guidelines for exposing
2462     // cues, and begin updating it dynamically as necessary.
2463     //   - Thess are all done by the media engine.
2464 
2465     // 6. Set the new text track's readiness state to loaded.
2466     textTrack->setReadinessState(TextTrack::Loaded);
2467 
2468     // 7. Set the new text track's mode to the mode consistent with the user's preferences and the requirements of
2469     // the relevant specification for the data.
2470     //  - This will happen in configureTextTracks()
2471     scheduleDelayedAction(LoadTextTrackResource);
2472 
2473     // 8. Add the new text track to the media element's list of text tracks.
2474     // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent
2475     // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's
2476     // textTracks attribute's TextTrackList object.
2477     addTrack(textTrack.get());
2478 }
2479 
mediaPlayerDidRemoveTrack(WebInbandTextTrack * webTrack)2480 void HTMLMediaElement::mediaPlayerDidRemoveTrack(WebInbandTextTrack* webTrack)
2481 {
2482     if (!RuntimeEnabledFeatures::videoTrackEnabled())
2483         return;
2484 
2485     if (!m_textTracks)
2486         return;
2487 
2488     // This cast is safe because we created the InbandTextTrack with the WebInbandTextTrack
2489     // passed to mediaPlayerDidAddTrack.
2490     RefPtr<InbandTextTrack> textTrack = static_cast<InbandTextTrack*>(webTrack->client());
2491     if (!textTrack)
2492         return;
2493 
2494     removeTrack(textTrack.get());
2495 }
2496 
closeCaptionTracksChanged()2497 void HTMLMediaElement::closeCaptionTracksChanged()
2498 {
2499     if (hasMediaControls())
2500         mediaControls()->closedCaptionTracksChanged();
2501 }
2502 
addTrack(TextTrack * track)2503 void HTMLMediaElement::addTrack(TextTrack* track)
2504 {
2505     textTracks()->append(track);
2506 
2507     closeCaptionTracksChanged();
2508 }
2509 
removeTrack(TextTrack * track)2510 void HTMLMediaElement::removeTrack(TextTrack* track)
2511 {
2512     TrackDisplayUpdateScope scope(this);
2513     TextTrackCueList* cues = track->cues();
2514     if (cues)
2515         textTrackRemoveCues(track, cues);
2516     m_textTracks->remove(track);
2517 
2518     closeCaptionTracksChanged();
2519 }
2520 
removeAllInbandTracks()2521 void HTMLMediaElement::removeAllInbandTracks()
2522 {
2523     if (!m_textTracks)
2524         return;
2525 
2526     TrackDisplayUpdateScope scope(this);
2527     for (int i = m_textTracks->length() - 1; i >= 0; --i) {
2528         TextTrack* track = m_textTracks->item(i);
2529 
2530         if (track->trackType() == TextTrack::InBand)
2531             removeTrack(track);
2532     }
2533 }
2534 
addTextTrack(const String & kind,const String & label,const String & language,ExceptionState & exceptionState)2535 PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language, ExceptionState& exceptionState)
2536 {
2537     ASSERT(RuntimeEnabledFeatures::videoTrackEnabled());
2538 
2539     // 4.8.10.12.4 Text track API
2540     // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
2541 
2542     // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
2543     if (!TextTrack::isValidKindKeyword(kind)) {
2544         exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
2545         return 0;
2546     }
2547 
2548     // 2. If the label argument was omitted, let label be the empty string.
2549     // 3. If the language argument was omitted, let language be the empty string.
2550     // 4. Create a new TextTrack object.
2551 
2552     // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text
2553     // track label to label, its text track language to language...
2554     RefPtr<TextTrack> textTrack = TextTrack::create(document(), this, kind, label, language);
2555 
2556     // Note, due to side effects when changing track parameters, we have to
2557     // first append the track to the text track list.
2558 
2559     // 6. Add the new text track to the media element's list of text tracks.
2560     addTrack(textTrack.get());
2561 
2562     // ... its text track readiness state to the text track loaded state ...
2563     textTrack->setReadinessState(TextTrack::Loaded);
2564 
2565     // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
2566     textTrack->setMode(TextTrack::hiddenKeyword());
2567 
2568     return textTrack.release();
2569 }
2570 
textTracks()2571 TextTrackList* HTMLMediaElement::textTracks()
2572 {
2573     ASSERT(RuntimeEnabledFeatures::videoTrackEnabled());
2574 
2575     if (!m_textTracks)
2576         m_textTracks = TextTrackList::create(this);
2577 
2578     return m_textTracks.get();
2579 }
2580 
didAddTrack(HTMLTrackElement * trackElement)2581 void HTMLMediaElement::didAddTrack(HTMLTrackElement* trackElement)
2582 {
2583     ASSERT(trackElement->hasTagName(trackTag));
2584 
2585     if (!RuntimeEnabledFeatures::videoTrackEnabled())
2586         return;
2587 
2588     // 4.8.10.12.3 Sourcing out-of-band text tracks
2589     // When a track element's parent element changes and the new parent is a media element,
2590     // then the user agent must add the track element's corresponding text track to the
2591     // media element's list of text tracks ... [continues in TextTrackList::append]
2592     RefPtr<TextTrack> textTrack = trackElement->track();
2593     if (!textTrack)
2594         return;
2595 
2596     addTrack(textTrack.get());
2597 
2598     // Do not schedule the track loading until parsing finishes so we don't start before all tracks
2599     // in the markup have been added.
2600     if (!m_parsingInProgress)
2601         scheduleDelayedAction(LoadTextTrackResource);
2602 
2603     if (hasMediaControls())
2604         mediaControls()->closedCaptionTracksChanged();
2605 }
2606 
didRemoveTrack(HTMLTrackElement * trackElement)2607 void HTMLMediaElement::didRemoveTrack(HTMLTrackElement* trackElement)
2608 {
2609     ASSERT(trackElement->hasTagName(trackTag));
2610 
2611     if (!RuntimeEnabledFeatures::videoTrackEnabled())
2612         return;
2613 
2614 #if !LOG_DISABLED
2615     if (trackElement->hasTagName(trackTag)) {
2616         KURL url = trackElement->getNonEmptyURLAttribute(srcAttr);
2617         WTF_LOG(Media, "HTMLMediaElement::didRemoveTrack - 'src' is %s", urlForLoggingMedia(url).utf8().data());
2618     }
2619 #endif
2620 
2621     RefPtr<TextTrack> textTrack = trackElement->track();
2622     if (!textTrack)
2623         return;
2624 
2625     textTrack->setHasBeenConfigured(false);
2626 
2627     if (!m_textTracks)
2628         return;
2629 
2630     // 4.8.10.12.3 Sourcing out-of-band text tracks
2631     // When a track element's parent element changes and the old parent was a media element,
2632     // then the user agent must remove the track element's corresponding text track from the
2633     // media element's list of text tracks.
2634     removeTrack(textTrack.get());
2635 
2636     size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
2637     if (index != kNotFound)
2638         m_textTracksWhenResourceSelectionBegan.remove(index);
2639 }
2640 
textTrackLanguageSelectionScore(const TextTrack & track)2641 static int textTrackLanguageSelectionScore(const TextTrack& track)
2642 {
2643     if (track.language().isEmpty())
2644         return 0;
2645 
2646     Vector<String> languages = userPreferredLanguages();
2647     size_t languageMatchIndex = indexOfBestMatchingLanguageInList(track.language(), languages);
2648     if (languageMatchIndex >= languages.size())
2649         return 0;
2650 
2651     // Matching a track language is more important than matching track type, so this multiplier must be
2652     // greater than the maximum value returned by textTrackSelectionScore.
2653     return (languages.size() - languageMatchIndex) * 10;
2654 }
2655 
textTrackSelectionScore(const TextTrack & track,Settings * settings)2656 static int textTrackSelectionScore(const TextTrack& track, Settings* settings)
2657 {
2658     int trackScore = 0;
2659 
2660     if (!settings)
2661         return trackScore;
2662 
2663     if (track.kind() != TextTrack::captionsKeyword() && track.kind() != TextTrack::subtitlesKeyword())
2664         return trackScore;
2665 
2666     if (track.kind() == TextTrack::subtitlesKeyword() && settings->shouldDisplaySubtitles())
2667         trackScore = 1;
2668     else if (track.kind() == TextTrack::captionsKeyword() && settings->shouldDisplayCaptions())
2669         trackScore = 1;
2670 
2671     return trackScore + textTrackLanguageSelectionScore(track);
2672 }
2673 
configureTextTrackGroup(const TrackGroup & group)2674 void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group)
2675 {
2676     ASSERT(group.tracks.size());
2677 
2678     WTF_LOG(Media, "HTMLMediaElement::configureTextTrackGroup(%d)", group.kind);
2679 
2680     Settings* settings = document().settings();
2681 
2682     // First, find the track in the group that should be enabled (if any).
2683     Vector<RefPtr<TextTrack> > currentlyEnabledTracks;
2684     RefPtr<TextTrack> trackToEnable;
2685     RefPtr<TextTrack> defaultTrack;
2686     RefPtr<TextTrack> fallbackTrack;
2687     int highestTrackScore = 0;
2688     for (size_t i = 0; i < group.tracks.size(); ++i) {
2689         RefPtr<TextTrack> textTrack = group.tracks[i];
2690 
2691         if (m_processingPreferenceChange && textTrack->mode() == TextTrack::showingKeyword())
2692             currentlyEnabledTracks.append(textTrack);
2693 
2694         int trackScore = textTrackSelectionScore(*textTrack, settings);
2695         if (trackScore) {
2696             // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
2697             // track with this text track kind, text track language, and text track label enabled, and there is no
2698             // other text track in the media element's list of text tracks with a text track kind of either subtitles
2699             // or captions whose text track mode is showing
2700             // ...
2701             // * If the text track kind is chapters and the text track language is one that the user agent has reason
2702             // to believe is appropriate for the user, and there is no other text track in the media element's list of
2703             // text tracks with a text track kind of chapters whose text track mode is showing
2704             //    Let the text track mode be showing.
2705             if (trackScore > highestTrackScore) {
2706                 highestTrackScore = trackScore;
2707                 trackToEnable = textTrack;
2708             }
2709 
2710             if (!defaultTrack && textTrack->isDefault())
2711                 defaultTrack = textTrack;
2712             if (!defaultTrack && !fallbackTrack)
2713                 fallbackTrack = textTrack;
2714         } else if (!group.visibleTrack && !defaultTrack && textTrack->isDefault()) {
2715             // * If the track element has a default attribute specified, and there is no other text track in the media
2716             // element's list of text tracks whose text track mode is showing or showing by default
2717             //    Let the text track mode be showing by default.
2718             defaultTrack = textTrack;
2719         }
2720     }
2721 
2722     if (!trackToEnable && defaultTrack)
2723         trackToEnable = defaultTrack;
2724 
2725     // If no track matches the user's preferred language and non was marked 'default', enable the first track
2726     // because the user has explicitly stated a preference for this kind of track.
2727     if (!fallbackTrack && m_closedCaptionsVisible && group.kind == TrackGroup::CaptionsAndSubtitles)
2728         fallbackTrack = group.tracks[0];
2729 
2730     if (!trackToEnable && fallbackTrack)
2731         trackToEnable = fallbackTrack;
2732 
2733     if (currentlyEnabledTracks.size()) {
2734         for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) {
2735             RefPtr<TextTrack> textTrack = currentlyEnabledTracks[i];
2736             if (textTrack != trackToEnable)
2737                 textTrack->setMode(TextTrack::disabledKeyword());
2738         }
2739     }
2740 
2741     if (trackToEnable)
2742         trackToEnable->setMode(TextTrack::showingKeyword());
2743 }
2744 
configureTextTracks()2745 void HTMLMediaElement::configureTextTracks()
2746 {
2747     TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
2748     TrackGroup descriptionTracks(TrackGroup::Description);
2749     TrackGroup chapterTracks(TrackGroup::Chapter);
2750     TrackGroup metadataTracks(TrackGroup::Metadata);
2751     TrackGroup otherTracks(TrackGroup::Other);
2752 
2753     if (!m_textTracks)
2754         return;
2755 
2756     for (size_t i = 0; i < m_textTracks->length(); ++i) {
2757         RefPtr<TextTrack> textTrack = m_textTracks->item(i);
2758         if (!textTrack)
2759             continue;
2760 
2761         String kind = textTrack->kind();
2762         TrackGroup* currentGroup;
2763         if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
2764             currentGroup = &captionAndSubtitleTracks;
2765         else if (kind == TextTrack::descriptionsKeyword())
2766             currentGroup = &descriptionTracks;
2767         else if (kind == TextTrack::chaptersKeyword())
2768             currentGroup = &chapterTracks;
2769         else if (kind == TextTrack::metadataKeyword())
2770             currentGroup = &metadataTracks;
2771         else
2772             currentGroup = &otherTracks;
2773 
2774         if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::showingKeyword())
2775             currentGroup->visibleTrack = textTrack;
2776         if (!currentGroup->defaultTrack && textTrack->isDefault())
2777             currentGroup->defaultTrack = textTrack;
2778 
2779         // Do not add this track to the group if it has already been automatically configured
2780         // as we only want to call configureTextTrack once per track so that adding another
2781         // track after the initial configuration doesn't reconfigure every track - only those
2782         // that should be changed by the new addition. For example all metadata tracks are
2783         // disabled by default, and we don't want a track that has been enabled by script
2784         // to be disabled automatically when a new metadata track is added later.
2785         if (textTrack->hasBeenConfigured())
2786             continue;
2787 
2788         if (textTrack->language().length())
2789             currentGroup->hasSrcLang = true;
2790         currentGroup->tracks.append(textTrack);
2791     }
2792 
2793     if (captionAndSubtitleTracks.tracks.size())
2794         configureTextTrackGroup(captionAndSubtitleTracks);
2795     if (descriptionTracks.tracks.size())
2796         configureTextTrackGroup(descriptionTracks);
2797     if (chapterTracks.tracks.size())
2798         configureTextTrackGroup(chapterTracks);
2799     if (metadataTracks.tracks.size())
2800         configureTextTrackGroup(metadataTracks);
2801     if (otherTracks.tracks.size())
2802         configureTextTrackGroup(otherTracks);
2803 
2804     if (hasMediaControls())
2805         mediaControls()->closedCaptionTracksChanged();
2806 }
2807 
havePotentialSourceChild()2808 bool HTMLMediaElement::havePotentialSourceChild()
2809 {
2810     // Stash the current <source> node and next nodes so we can restore them after checking
2811     // to see there is another potential.
2812     RefPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode;
2813     RefPtr<Node> nextNode = m_nextChildNodeToConsider;
2814 
2815     KURL nextURL = selectNextSourceChild(0, 0, DoNothing);
2816 
2817     m_currentSourceNode = currentSourceNode;
2818     m_nextChildNodeToConsider = nextNode;
2819 
2820     return nextURL.isValid();
2821 }
2822 
selectNextSourceChild(ContentType * contentType,String * keySystem,InvalidURLAction actionIfInvalid)2823 KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid)
2824 {
2825 #if !LOG_DISABLED
2826     // Don't log if this was just called to find out if there are any valid <source> elements.
2827     bool shouldLog = actionIfInvalid != DoNothing;
2828     if (shouldLog)
2829         WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild");
2830 #endif
2831 
2832     if (!m_nextChildNodeToConsider) {
2833 #if !LOG_DISABLED
2834         if (shouldLog)
2835             WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild -> 0x0000, \"\"");
2836 #endif
2837         return KURL();
2838     }
2839 
2840     KURL mediaURL;
2841     Node* node;
2842     HTMLSourceElement* source = 0;
2843     String type;
2844     String system;
2845     bool lookingForStartNode = m_nextChildNodeToConsider;
2846     bool canUseSourceElement = false;
2847     bool okToLoadSourceURL;
2848 
2849     NodeVector potentialSourceNodes;
2850     getChildNodes(*this, potentialSourceNodes);
2851 
2852     for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) {
2853         node = potentialSourceNodes[i].get();
2854         if (lookingForStartNode && m_nextChildNodeToConsider != node)
2855             continue;
2856         lookingForStartNode = false;
2857 
2858         if (!node->hasTagName(sourceTag))
2859             continue;
2860         if (node->parentNode() != this)
2861             continue;
2862 
2863         UseCounter::count(document(), UseCounter::SourceElementCandidate);
2864         source = toHTMLSourceElement(node);
2865 
2866         // If candidate does not have a src attribute, or if its src attribute's value is the empty string ... jump down to the failed step below
2867         mediaURL = source->getNonEmptyURLAttribute(srcAttr);
2868 #if !LOG_DISABLED
2869         if (shouldLog)
2870             WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'src' is %s", urlForLoggingMedia(mediaURL).utf8().data());
2871 #endif
2872         if (mediaURL.isEmpty())
2873             goto check_again;
2874 
2875         if (source->fastHasAttribute(mediaAttr)) {
2876             MediaQueryEvaluator screenEval("screen", document().frame(), renderer() ? renderer()->style() : 0);
2877             RefPtr<MediaQuerySet> media = MediaQuerySet::create(source->media());
2878 #if !LOG_DISABLED
2879             if (shouldLog)
2880                 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'media' is %s", source->media().string().utf8().data());
2881 #endif
2882             if (!screenEval.eval(media.get())) {
2883                 UseCounter::count(document(), UseCounter::SourceElementNonMatchingMedia);
2884                 goto check_again;
2885             }
2886         }
2887 
2888         type = source->type();
2889         // FIXME(82965): Add support for keySystem in <source> and set system from source.
2890         if (type.isEmpty() && mediaURL.protocolIsData())
2891             type = mimeTypeFromDataURL(mediaURL);
2892         if (!type.isEmpty() || !system.isEmpty()) {
2893 #if !LOG_DISABLED
2894             if (shouldLog)
2895                 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'type' is '%s' - key system is '%s'", type.utf8().data(), system.utf8().data());
2896 #endif
2897             if (!supportsType(ContentType(type), system))
2898                 goto check_again;
2899         }
2900 
2901         // Is it safe to load this url?
2902         okToLoadSourceURL = isSafeToLoadURL(mediaURL, actionIfInvalid) && dispatchBeforeLoadEvent(mediaURL.string());
2903 
2904         // A 'beforeload' event handler can mutate the DOM, so check to see if the source element is still a child node.
2905         if (node->parentNode() != this) {
2906             WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild : 'beforeload' removed current element");
2907             source = 0;
2908             goto check_again;
2909         }
2910 
2911         if (!okToLoadSourceURL)
2912             goto check_again;
2913 
2914         // Making it this far means the <source> looks reasonable.
2915         canUseSourceElement = true;
2916 
2917 check_again:
2918         if (!canUseSourceElement && actionIfInvalid == Complain && source)
2919             source->scheduleErrorEvent();
2920     }
2921 
2922     if (canUseSourceElement) {
2923         if (contentType)
2924             *contentType = ContentType(type);
2925         if (keySystem)
2926             *keySystem = system;
2927         m_currentSourceNode = source;
2928         m_nextChildNodeToConsider = source->nextSibling();
2929     } else {
2930         m_currentSourceNode = 0;
2931         m_nextChildNodeToConsider = 0;
2932     }
2933 
2934 #if !LOG_DISABLED
2935     if (shouldLog)
2936         WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild -> %p, %s", m_currentSourceNode.get(), canUseSourceElement ? urlForLoggingMedia(mediaURL).utf8().data() : "");
2937 #endif
2938     return canUseSourceElement ? mediaURL : KURL();
2939 }
2940 
sourceWasAdded(HTMLSourceElement * source)2941 void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source)
2942 {
2943     WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p)", source);
2944 
2945 #if !LOG_DISABLED
2946     if (source->hasTagName(sourceTag)) {
2947         KURL url = source->getNonEmptyURLAttribute(srcAttr);
2948         WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLoggingMedia(url).utf8().data());
2949     }
2950 #endif
2951 
2952     // We should only consider a <source> element when there is not src attribute at all.
2953     if (fastHasAttribute(srcAttr))
2954         return;
2955 
2956     // 4.8.8 - If a source element is inserted as a child of a media element that has no src
2957     // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke
2958     // the media element's resource selection algorithm.
2959     if (networkState() == HTMLMediaElement::NETWORK_EMPTY) {
2960         scheduleDelayedAction(LoadMediaResource);
2961         m_nextChildNodeToConsider = source;
2962         return;
2963     }
2964 
2965     if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) {
2966         WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded - <source> inserted immediately after current source");
2967         m_nextChildNodeToConsider = source;
2968         return;
2969     }
2970 
2971     if (m_nextChildNodeToConsider)
2972         return;
2973 
2974     // 4.8.9.5, resource selection algorithm, source elements section:
2975     // 21. Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.)
2976     // 22. Asynchronously await a stable state...
2977     // 23. Set the element's delaying-the-load-event flag back to true (this delays the load event again, in case
2978     // it hasn't been fired yet).
2979     setShouldDelayLoadEvent(true);
2980 
2981     // 24. Set the networkState back to NETWORK_LOADING.
2982     m_networkState = NETWORK_LOADING;
2983 
2984     // 25. Jump back to the find next candidate step above.
2985     m_nextChildNodeToConsider = source;
2986     scheduleNextSourceChild();
2987 }
2988 
sourceWasRemoved(HTMLSourceElement * source)2989 void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source)
2990 {
2991     WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p)", source);
2992 
2993 #if !LOG_DISABLED
2994     if (source->hasTagName(sourceTag)) {
2995         KURL url = source->getNonEmptyURLAttribute(srcAttr);
2996         WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLoggingMedia(url).utf8().data());
2997     }
2998 #endif
2999 
3000     if (source != m_currentSourceNode && source != m_nextChildNodeToConsider)
3001         return;
3002 
3003     if (source == m_nextChildNodeToConsider) {
3004         if (m_currentSourceNode)
3005             m_nextChildNodeToConsider = m_currentSourceNode->nextSibling();
3006         WTF_LOG(Media, "HTMLMediaElement::sourceRemoved - m_nextChildNodeToConsider set to %p", m_nextChildNodeToConsider.get());
3007     } else if (source == m_currentSourceNode) {
3008         // Clear the current source node pointer, but don't change the movie as the spec says:
3009         // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already
3010         // inserted in a video or audio element will have no effect.
3011         m_currentSourceNode = 0;
3012         WTF_LOG(Media, "HTMLMediaElement::sourceRemoved - m_currentSourceNode set to 0");
3013     }
3014 }
3015 
mediaPlayerTimeChanged()3016 void HTMLMediaElement::mediaPlayerTimeChanged()
3017 {
3018     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged");
3019 
3020     if (RuntimeEnabledFeatures::videoTrackEnabled())
3021         updateActiveTextTrackCues(currentTime());
3022 
3023     invalidateCachedTime();
3024 
3025     // 4.8.10.9 steps 12-14. Needed if no ReadyState change is associated with the seek.
3026     if (m_seeking && m_readyState >= HAVE_CURRENT_DATA && !m_player->seeking())
3027         finishSeek();
3028 
3029     // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity,
3030     // it will only queue a 'timeupdate' event if we haven't already posted one at the current
3031     // movie time.
3032     scheduleTimeupdateEvent(false);
3033 
3034     double now = currentTime();
3035     double dur = duration();
3036 
3037     // When the current playback position reaches the end of the media resource when the direction of
3038     // playback is forwards, then the user agent must follow these steps:
3039     if (!std::isnan(dur) && dur && now >= dur && m_playbackRate > 0) {
3040         // If the media element has a loop attribute specified and does not have a current media controller,
3041         if (loop() && !m_mediaController) {
3042             m_sentEndEvent = false;
3043             //  then seek to the earliest possible position of the media resource and abort these steps.
3044             seek(0, IGNORE_EXCEPTION);
3045         } else {
3046             // If the media element does not have a current media controller, and the media element
3047             // has still ended playback, and the direction of playback is still forwards, and paused
3048             // is false,
3049             if (!m_mediaController && !m_paused) {
3050                 // changes paused to true and fires a simple event named pause at the media element.
3051                 m_paused = true;
3052                 scheduleEvent(EventTypeNames::pause);
3053             }
3054             // Queue a task to fire a simple event named ended at the media element.
3055             if (!m_sentEndEvent) {
3056                 m_sentEndEvent = true;
3057                 scheduleEvent(EventTypeNames::ended);
3058             }
3059             // If the media element has a current media controller, then report the controller state
3060             // for the media element's current media controller.
3061             updateMediaController();
3062         }
3063     }
3064     else
3065         m_sentEndEvent = false;
3066 
3067     updatePlayState();
3068 }
3069 
mediaPlayerDurationChanged()3070 void HTMLMediaElement::mediaPlayerDurationChanged()
3071 {
3072     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged");
3073     durationChanged(duration());
3074 }
3075 
durationChanged(double duration)3076 void HTMLMediaElement::durationChanged(double duration)
3077 {
3078     WTF_LOG(Media, "HTMLMediaElement::durationChanged(%f)", duration);
3079 
3080     // Abort if duration unchanged.
3081     if (m_duration == duration)
3082         return;
3083 
3084     m_duration = duration;
3085     scheduleEvent(EventTypeNames::durationchange);
3086 
3087     if (hasMediaControls())
3088         mediaControls()->reset();
3089     if (renderer())
3090         renderer()->updateFromElement();
3091 
3092     if (currentTime() > duration)
3093         seek(duration, IGNORE_EXCEPTION);
3094 }
3095 
mediaPlayerPlaybackStateChanged()3096 void HTMLMediaElement::mediaPlayerPlaybackStateChanged()
3097 {
3098     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged");
3099 
3100     if (!m_player || m_pausedInternal)
3101         return;
3102 
3103     if (m_player->paused())
3104         pauseInternal();
3105     else
3106         playInternal();
3107 }
3108 
mediaPlayerRequestFullscreen()3109 void HTMLMediaElement::mediaPlayerRequestFullscreen()
3110 {
3111     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerRequestFullscreen");
3112     enterFullscreen();
3113 }
3114 
mediaPlayerRequestSeek(double time)3115 void HTMLMediaElement::mediaPlayerRequestSeek(double time)
3116 {
3117     // The player is the source of this seek request.
3118     if (m_mediaController) {
3119         m_mediaController->setCurrentTime(time, IGNORE_EXCEPTION);
3120         return;
3121     }
3122     setCurrentTime(time, IGNORE_EXCEPTION);
3123 }
3124 
3125 // MediaPlayerPresentation methods
mediaPlayerRepaint()3126 void HTMLMediaElement::mediaPlayerRepaint()
3127 {
3128     if (m_webLayer)
3129         m_webLayer->invalidate();
3130 
3131     updateDisplayState();
3132     if (renderer())
3133         renderer()->repaint();
3134 }
3135 
mediaPlayerSizeChanged()3136 void HTMLMediaElement::mediaPlayerSizeChanged()
3137 {
3138     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged");
3139 
3140     if (m_readyState > HAVE_NOTHING)
3141         scheduleEvent(EventTypeNames::resize);
3142 
3143     if (renderer())
3144         renderer()->updateFromElement();
3145 }
3146 
buffered() const3147 PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const
3148 {
3149     if (!m_player)
3150         return TimeRanges::create();
3151 
3152     if (m_mediaSource)
3153         return m_mediaSource->buffered();
3154 
3155     return m_player->buffered();
3156 }
3157 
played()3158 PassRefPtr<TimeRanges> HTMLMediaElement::played()
3159 {
3160     if (m_playing) {
3161         double time = currentTime();
3162         if (time > m_lastSeekTime)
3163             addPlayedRange(m_lastSeekTime, time);
3164     }
3165 
3166     if (!m_playedTimeRanges)
3167         m_playedTimeRanges = TimeRanges::create();
3168 
3169     return m_playedTimeRanges->copy();
3170 }
3171 
seekable() const3172 PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const
3173 {
3174     if (m_player) {
3175         double maxTimeSeekable = m_player->maxTimeSeekable();
3176         if (maxTimeSeekable)
3177             return TimeRanges::create(0, maxTimeSeekable);
3178     }
3179     return TimeRanges::create();
3180 }
3181 
potentiallyPlaying() const3182 bool HTMLMediaElement::potentiallyPlaying() const
3183 {
3184     // "pausedToBuffer" means the media engine's rate is 0, but only because it had to stop playing
3185     // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the
3186     // checks in couldPlayIfEnoughData().
3187     bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA;
3188     return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData() && !isBlockedOnMediaController();
3189 }
3190 
couldPlayIfEnoughData() const3191 bool HTMLMediaElement::couldPlayIfEnoughData() const
3192 {
3193     return !paused() && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction();
3194 }
3195 
endedPlayback() const3196 bool HTMLMediaElement::endedPlayback() const
3197 {
3198     double dur = duration();
3199     if (!m_player || std::isnan(dur))
3200         return false;
3201 
3202     // 4.8.10.8 Playing the media resource
3203 
3204     // A media element is said to have ended playback when the element's
3205     // readyState attribute is HAVE_METADATA or greater,
3206     if (m_readyState < HAVE_METADATA)
3207         return false;
3208 
3209     // and the current playback position is the end of the media resource and the direction
3210     // of playback is forwards, Either the media element does not have a loop attribute specified,
3211     // or the media element has a current media controller.
3212     double now = currentTime();
3213     if (m_playbackRate > 0)
3214         return dur > 0 && now >= dur && (!loop() || m_mediaController);
3215 
3216     // or the current playback position is the earliest possible position and the direction
3217     // of playback is backwards
3218     if (m_playbackRate < 0)
3219         return now <= 0;
3220 
3221     return false;
3222 }
3223 
stoppedDueToErrors() const3224 bool HTMLMediaElement::stoppedDueToErrors() const
3225 {
3226     if (m_readyState >= HAVE_METADATA && m_error) {
3227         RefPtr<TimeRanges> seekableRanges = seekable();
3228         if (!seekableRanges->contain(currentTime()))
3229             return true;
3230     }
3231 
3232     return false;
3233 }
3234 
pausedForUserInteraction() const3235 bool HTMLMediaElement::pausedForUserInteraction() const
3236 {
3237 //    return !paused() && m_readyState >= HAVE_FUTURE_DATA && [UA requires a decitions from the user]
3238     return false;
3239 }
3240 
updateVolume()3241 void HTMLMediaElement::updateVolume()
3242 {
3243     if (!m_player)
3244         return;
3245 
3246     double volumeMultiplier = 1;
3247     bool shouldMute = m_muted;
3248 
3249     if (m_mediaController) {
3250         volumeMultiplier *= m_mediaController->volume();
3251         shouldMute = m_mediaController->muted();
3252     }
3253 
3254     m_player->setMuted(shouldMute);
3255     m_player->setVolume(m_volume * volumeMultiplier);
3256 
3257     if (hasMediaControls())
3258         mediaControls()->changedVolume();
3259 }
3260 
updatePlayState()3261 void HTMLMediaElement::updatePlayState()
3262 {
3263     if (!m_player)
3264         return;
3265 
3266     if (m_pausedInternal) {
3267         if (!m_player->paused())
3268             m_player->pause();
3269         refreshCachedTime();
3270         m_playbackProgressTimer.stop();
3271         if (hasMediaControls())
3272             mediaControls()->playbackStopped();
3273         return;
3274     }
3275 
3276     bool shouldBePlaying = potentiallyPlaying();
3277     bool playerPaused = m_player->paused();
3278 
3279     WTF_LOG(Media, "HTMLMediaElement::updatePlayState - shouldBePlaying = %s, playerPaused = %s",
3280         boolString(shouldBePlaying), boolString(playerPaused));
3281 
3282     if (shouldBePlaying) {
3283         setDisplayMode(Video);
3284         invalidateCachedTime();
3285 
3286         if (playerPaused) {
3287             // Set rate, muted before calling play in case they were set before the media engine was setup.
3288             // The media engine should just stash the rate and muted values since it isn't already playing.
3289             m_player->setRate(m_playbackRate);
3290             m_player->setMuted(m_muted);
3291 
3292             m_player->play();
3293         }
3294 
3295         if (hasMediaControls())
3296             mediaControls()->playbackStarted();
3297         startPlaybackProgressTimer();
3298         m_playing = true;
3299 
3300     } else { // Should not be playing right now
3301         if (!playerPaused)
3302             m_player->pause();
3303         refreshCachedTime();
3304 
3305         m_playbackProgressTimer.stop();
3306         m_playing = false;
3307         double time = currentTime();
3308         if (time > m_lastSeekTime)
3309             addPlayedRange(m_lastSeekTime, time);
3310 
3311         if (couldPlayIfEnoughData())
3312             prepareToPlay();
3313 
3314         if (hasMediaControls())
3315             mediaControls()->playbackStopped();
3316     }
3317 
3318     updateMediaController();
3319 
3320     if (renderer())
3321         renderer()->updateFromElement();
3322 }
3323 
setPausedInternal(bool b)3324 void HTMLMediaElement::setPausedInternal(bool b)
3325 {
3326     m_pausedInternal = b;
3327     updatePlayState();
3328 }
3329 
stopPeriodicTimers()3330 void HTMLMediaElement::stopPeriodicTimers()
3331 {
3332     m_progressEventTimer.stop();
3333     m_playbackProgressTimer.stop();
3334 }
3335 
userCancelledLoad()3336 void HTMLMediaElement::userCancelledLoad()
3337 {
3338     WTF_LOG(Media, "HTMLMediaElement::userCancelledLoad");
3339 
3340     // If the media data fetching process is aborted by the user:
3341 
3342     // 1 - The user agent should cancel the fetching process.
3343     clearMediaPlayer(-1);
3344 
3345     if (m_networkState == NETWORK_EMPTY || m_completelyLoaded)
3346         return;
3347 
3348     // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED.
3349     m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED);
3350 
3351     // 3 - Queue a task to fire a simple event named error at the media element.
3352     scheduleEvent(EventTypeNames::abort);
3353 
3354     closeMediaSource();
3355 
3356     // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
3357     // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a
3358     // simple event named emptied at the element. Otherwise, set the element's networkState
3359     // attribute to the NETWORK_IDLE value.
3360     if (m_readyState == HAVE_NOTHING) {
3361         m_networkState = NETWORK_EMPTY;
3362         scheduleEvent(EventTypeNames::emptied);
3363     }
3364     else
3365         m_networkState = NETWORK_IDLE;
3366 
3367     // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
3368     setShouldDelayLoadEvent(false);
3369 
3370     // 6 - Abort the overall resource selection algorithm.
3371     m_currentSourceNode = 0;
3372 
3373     // Reset m_readyState since m_player is gone.
3374     m_readyState = HAVE_NOTHING;
3375     updateMediaController();
3376     if (RuntimeEnabledFeatures::videoTrackEnabled())
3377         updateActiveTextTrackCues(0);
3378 }
3379 
clearMediaPlayerAndAudioSourceProviderClient()3380 void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClient()
3381 {
3382 #if ENABLE(WEB_AUDIO)
3383     if (m_audioSourceNode)
3384         m_audioSourceNode->lock();
3385 
3386     if (audioSourceProvider())
3387         audioSourceProvider()->setClient(0);
3388 #endif
3389 
3390     m_player.clear();
3391 
3392 #if ENABLE(WEB_AUDIO)
3393     if (m_audioSourceNode)
3394         m_audioSourceNode->unlock();
3395 #endif
3396 }
3397 
clearMediaPlayer(int flags)3398 void HTMLMediaElement::clearMediaPlayer(int flags)
3399 {
3400     removeAllInbandTracks();
3401 
3402     closeMediaSource();
3403 
3404     clearMediaPlayerAndAudioSourceProviderClient();
3405 
3406     stopPeriodicTimers();
3407     m_loadTimer.stop();
3408 
3409     m_pendingActionFlags &= ~flags;
3410     m_loadState = WaitingForSource;
3411 
3412     if (m_textTracks)
3413         configureTextTrackDisplay(AssumeNoVisibleChange);
3414 }
3415 
stop()3416 void HTMLMediaElement::stop()
3417 {
3418     WTF_LOG(Media, "HTMLMediaElement::stop");
3419 
3420     m_active = false;
3421     userCancelledLoad();
3422 
3423     // Stop the playback without generating events
3424     m_playing = false;
3425     setPausedInternal(true);
3426 
3427     if (renderer())
3428         renderer()->updateFromElement();
3429 
3430     stopPeriodicTimers();
3431     cancelPendingEventsAndCallbacks();
3432 
3433     m_asyncEventQueue->close();
3434 }
3435 
hasPendingActivity() const3436 bool HTMLMediaElement::hasPendingActivity() const
3437 {
3438     return (hasAudio() && isPlaying()) || m_asyncEventQueue->hasPendingEvents();
3439 }
3440 
contextDestroyed()3441 void HTMLMediaElement::contextDestroyed()
3442 {
3443     if (m_mediaController)
3444         m_mediaController->clearExecutionContext();
3445     ActiveDOMObject::contextDestroyed();
3446 }
3447 
isFullscreen() const3448 bool HTMLMediaElement::isFullscreen() const
3449 {
3450     return FullscreenElementStack::isActiveFullScreenElement(this);
3451 }
3452 
enterFullscreen()3453 void HTMLMediaElement::enterFullscreen()
3454 {
3455     WTF_LOG(Media, "HTMLMediaElement::enterFullscreen");
3456 
3457     if (document().settings() && document().settings()->fullScreenEnabled())
3458         FullscreenElementStack::from(&document())->requestFullScreenForElement(this, 0, FullscreenElementStack::ExemptIFrameAllowFullScreenRequirement);
3459 }
3460 
exitFullscreen()3461 void HTMLMediaElement::exitFullscreen()
3462 {
3463     WTF_LOG(Media, "HTMLMediaElement::exitFullscreen");
3464 
3465     if (document().settings() && document().settings()->fullScreenEnabled() && isFullscreen())
3466         FullscreenElementStack::from(&document())->webkitCancelFullScreen();
3467 }
3468 
didBecomeFullscreenElement()3469 void HTMLMediaElement::didBecomeFullscreenElement()
3470 {
3471     if (hasMediaControls())
3472         mediaControls()->enteredFullscreen();
3473     if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isVideo())
3474         document().renderView()->compositor()->setCompositingLayersNeedRebuild(true);
3475 }
3476 
willStopBeingFullscreenElement()3477 void HTMLMediaElement::willStopBeingFullscreenElement()
3478 {
3479     if (hasMediaControls())
3480         mediaControls()->exitedFullscreen();
3481     if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isVideo())
3482         document().renderView()->compositor()->setCompositingLayersNeedRebuild(true);
3483 }
3484 
platformLayer() const3485 blink::WebLayer* HTMLMediaElement::platformLayer() const
3486 {
3487     return m_webLayer;
3488 }
3489 
hasClosedCaptions() const3490 bool HTMLMediaElement::hasClosedCaptions() const
3491 {
3492     if (RuntimeEnabledFeatures::videoTrackEnabled() && m_textTracks) {
3493         for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3494             if (m_textTracks->item(i)->readinessState() == TextTrack::FailedToLoad)
3495                 continue;
3496 
3497             if (m_textTracks->item(i)->kind() == TextTrack::captionsKeyword()
3498                 || m_textTracks->item(i)->kind() == TextTrack::subtitlesKeyword())
3499                 return true;
3500         }
3501     }
3502     return false;
3503 }
3504 
closedCaptionsVisible() const3505 bool HTMLMediaElement::closedCaptionsVisible() const
3506 {
3507     return m_closedCaptionsVisible;
3508 }
3509 
updateTextTrackDisplay()3510 void HTMLMediaElement::updateTextTrackDisplay()
3511 {
3512     WTF_LOG(Media, "HTMLMediaElement::updateTextTrackDisplay");
3513 
3514     if (!hasMediaControls() && !createMediaControls())
3515         return;
3516 
3517     mediaControls()->updateTextTrackDisplay();
3518 }
3519 
setClosedCaptionsVisible(bool closedCaptionVisible)3520 void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
3521 {
3522     WTF_LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%s)", boolString(closedCaptionVisible));
3523 
3524     if (!m_player || !hasClosedCaptions())
3525         return;
3526 
3527     m_closedCaptionsVisible = closedCaptionVisible;
3528 
3529     if (RuntimeEnabledFeatures::videoTrackEnabled()) {
3530         m_processingPreferenceChange = true;
3531         markCaptionAndSubtitleTracksAsUnconfigured();
3532         m_processingPreferenceChange = false;
3533 
3534         updateTextTrackDisplay();
3535     }
3536 }
3537 
webkitAudioDecodedByteCount() const3538 unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const
3539 {
3540     if (!m_player)
3541         return 0;
3542     return m_player->audioDecodedByteCount();
3543 }
3544 
webkitVideoDecodedByteCount() const3545 unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const
3546 {
3547     if (!m_player)
3548         return 0;
3549     return m_player->videoDecodedByteCount();
3550 }
3551 
isURLAttribute(const Attribute & attribute) const3552 bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const
3553 {
3554     return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
3555 }
3556 
setShouldDelayLoadEvent(bool shouldDelay)3557 void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay)
3558 {
3559     if (m_shouldDelayLoadEvent == shouldDelay)
3560         return;
3561 
3562     WTF_LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%s)", boolString(shouldDelay));
3563 
3564     m_shouldDelayLoadEvent = shouldDelay;
3565     if (shouldDelay)
3566         document().incrementLoadEventDelayCount();
3567     else
3568         document().decrementLoadEventDelayCount();
3569 }
3570 
3571 
mediaControls() const3572 MediaControls* HTMLMediaElement::mediaControls() const
3573 {
3574     return toMediaControls(userAgentShadowRoot()->firstChild());
3575 }
3576 
hasMediaControls() const3577 bool HTMLMediaElement::hasMediaControls() const
3578 {
3579     if (ShadowRoot* userAgent = userAgentShadowRoot()) {
3580         Node* node = userAgent->firstChild();
3581         ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isMediaControls());
3582         return node;
3583     }
3584 
3585     return false;
3586 }
3587 
createMediaControls()3588 bool HTMLMediaElement::createMediaControls()
3589 {
3590     if (hasMediaControls())
3591         return true;
3592 
3593     RefPtr<MediaControls> mediaControls = MediaControls::create(document());
3594     if (!mediaControls)
3595         return false;
3596 
3597     mediaControls->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this));
3598     mediaControls->reset();
3599     if (isFullscreen())
3600         mediaControls->enteredFullscreen();
3601 
3602     ensureUserAgentShadowRoot().appendChild(mediaControls);
3603 
3604     if (!controls() || !inDocument())
3605         mediaControls->hide();
3606 
3607     return true;
3608 }
3609 
configureMediaControls()3610 void HTMLMediaElement::configureMediaControls()
3611 {
3612     if (!controls() || !inDocument()) {
3613         if (hasMediaControls())
3614             mediaControls()->hide();
3615         return;
3616     }
3617 
3618     if (!hasMediaControls() && !createMediaControls())
3619         return;
3620 
3621     mediaControls()->show();
3622 }
3623 
configureTextTrackDisplay(VisibilityChangeAssumption assumption)3624 void HTMLMediaElement::configureTextTrackDisplay(VisibilityChangeAssumption assumption)
3625 {
3626     ASSERT(m_textTracks);
3627     WTF_LOG(Media, "HTMLMediaElement::configureTextTrackDisplay");
3628 
3629     if (m_processingPreferenceChange)
3630         return;
3631 
3632     bool haveVisibleTextTrack = false;
3633     for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3634         if (m_textTracks->item(i)->mode() == TextTrack::showingKeyword()) {
3635             haveVisibleTextTrack = true;
3636             break;
3637         }
3638     }
3639 
3640     if (assumption == AssumeNoVisibleChange
3641         && m_haveVisibleTextTrack == haveVisibleTextTrack) {
3642         updateActiveTextTrackCues(currentTime());
3643         return;
3644     }
3645     m_haveVisibleTextTrack = haveVisibleTextTrack;
3646     m_closedCaptionsVisible = m_haveVisibleTextTrack;
3647 
3648     if (!m_haveVisibleTextTrack && !hasMediaControls())
3649         return;
3650     if (!hasMediaControls() && !createMediaControls())
3651         return;
3652 
3653     mediaControls()->changedClosedCaptionsVisibility();
3654 
3655     if (RuntimeEnabledFeatures::videoTrackEnabled()) {
3656         updateActiveTextTrackCues(currentTime());
3657         updateTextTrackDisplay();
3658     }
3659 }
3660 
markCaptionAndSubtitleTracksAsUnconfigured()3661 void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured()
3662 {
3663     if (!m_textTracks)
3664         return;
3665 
3666     // Mark all tracks as not "configured" so that configureTextTracks()
3667     // will reconsider which tracks to display in light of new user preferences
3668     // (e.g. default tracks should not be displayed if the user has turned off
3669     // captions and non-default tracks should be displayed based on language
3670     // preferences if the user has turned captions on).
3671     for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3672         RefPtr<TextTrack> textTrack = m_textTracks->item(i);
3673         String kind = textTrack->kind();
3674 
3675         if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
3676             textTrack->setHasBeenConfigured(false);
3677     }
3678     configureTextTracks();
3679 }
3680 
3681 
preDispatchEventHandler(Event * event)3682 void* HTMLMediaElement::preDispatchEventHandler(Event* event)
3683 {
3684     if (event && event->type() == EventTypeNames::webkitfullscreenchange)
3685         configureMediaControls();
3686 
3687     return 0;
3688 }
3689 
createMediaPlayer()3690 void HTMLMediaElement::createMediaPlayer()
3691 {
3692 #if ENABLE(WEB_AUDIO)
3693     if (m_audioSourceNode)
3694         m_audioSourceNode->lock();
3695 #endif
3696 
3697     if (m_mediaSource)
3698         closeMediaSource();
3699 
3700     m_player = MediaPlayer::create(this);
3701 
3702 #if ENABLE(WEB_AUDIO)
3703     if (m_audioSourceNode) {
3704         // When creating the player, make sure its AudioSourceProvider knows about the MediaElementAudioSourceNode.
3705         if (audioSourceProvider())
3706             audioSourceProvider()->setClient(m_audioSourceNode);
3707 
3708         m_audioSourceNode->unlock();
3709     }
3710 #endif
3711 }
3712 
3713 #if ENABLE(WEB_AUDIO)
setAudioSourceNode(MediaElementAudioSourceNode * sourceNode)3714 void HTMLMediaElement::setAudioSourceNode(MediaElementAudioSourceNode* sourceNode)
3715 {
3716     m_audioSourceNode = sourceNode;
3717 
3718     if (m_audioSourceNode)
3719         m_audioSourceNode->lock();
3720 
3721     if (audioSourceProvider())
3722         audioSourceProvider()->setClient(m_audioSourceNode);
3723 
3724     if (m_audioSourceNode)
3725         m_audioSourceNode->unlock();
3726 }
3727 
audioSourceProvider()3728 AudioSourceProvider* HTMLMediaElement::audioSourceProvider()
3729 {
3730     if (m_player)
3731         return m_player->audioSourceProvider();
3732 
3733     return 0;
3734 }
3735 #endif
3736 
mediaGroup() const3737 const AtomicString& HTMLMediaElement::mediaGroup() const
3738 {
3739     return fastGetAttribute(mediagroupAttr);
3740 }
3741 
setMediaGroup(const AtomicString & group)3742 void HTMLMediaElement::setMediaGroup(const AtomicString& group)
3743 {
3744     // When a media element is created with a mediagroup attribute, and when a media element's mediagroup
3745     // attribute is set, changed, or removed, the user agent must run the following steps:
3746     // 1. Let m [this] be the media element in question.
3747     // 2. Let m have no current media controller, if it currently has one.
3748     setControllerInternal(0);
3749 
3750     // 3. If m's mediagroup attribute is being removed, then abort these steps.
3751     if (group.isNull() || group.isEmpty())
3752         return;
3753 
3754     // 4. If there is another media element whose Document is the same as m's Document (even if one or both
3755     // of these elements are not actually in the Document),
3756     HashSet<HTMLMediaElement*> elements = documentToElementSetMap().get(&document());
3757     for (HashSet<HTMLMediaElement*>::iterator i = elements.begin(); i != elements.end(); ++i) {
3758         if (*i == this)
3759             continue;
3760 
3761         // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as
3762         // the new value of m's mediagroup attribute,
3763         if ((*i)->mediaGroup() == group) {
3764             //  then let controller be that media element's current media controller.
3765             setControllerInternal((*i)->controller());
3766             return;
3767         }
3768     }
3769 
3770     // Otherwise, let controller be a newly created MediaController.
3771     setControllerInternal(MediaController::create(Node::executionContext()));
3772 }
3773 
controller() const3774 MediaController* HTMLMediaElement::controller() const
3775 {
3776     return m_mediaController.get();
3777 }
3778 
setController(PassRefPtr<MediaController> controller)3779 void HTMLMediaElement::setController(PassRefPtr<MediaController> controller)
3780 {
3781     // 4.8.10.11.2 Media controllers: controller attribute.
3782     // On setting, it must first remove the element's mediagroup attribute, if any,
3783     removeAttribute(mediagroupAttr);
3784     // and then set the current media controller to the given value.
3785     setControllerInternal(controller);
3786 }
3787 
setControllerInternal(PassRefPtr<MediaController> controller)3788 void HTMLMediaElement::setControllerInternal(PassRefPtr<MediaController> controller)
3789 {
3790     if (m_mediaController)
3791         m_mediaController->removeMediaElement(this);
3792 
3793     m_mediaController = controller;
3794 
3795     if (m_mediaController)
3796         m_mediaController->addMediaElement(this);
3797 
3798     if (hasMediaControls())
3799         mediaControls()->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this));
3800 }
3801 
updateMediaController()3802 void HTMLMediaElement::updateMediaController()
3803 {
3804     if (m_mediaController)
3805         m_mediaController->reportControllerState();
3806 }
3807 
isBlocked() const3808 bool HTMLMediaElement::isBlocked() const
3809 {
3810     // A media element is a blocked media element if its readyState attribute is in the
3811     // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state,
3812     if (m_readyState <= HAVE_CURRENT_DATA)
3813         return true;
3814 
3815     // or if the element has paused for user interaction.
3816     return pausedForUserInteraction();
3817 }
3818 
isBlockedOnMediaController() const3819 bool HTMLMediaElement::isBlockedOnMediaController() const
3820 {
3821     if (!m_mediaController)
3822         return false;
3823 
3824     // A media element is blocked on its media controller if the MediaController is a blocked
3825     // media controller,
3826     if (m_mediaController->isBlocked())
3827         return true;
3828 
3829     // or if its media controller position is either before the media resource's earliest possible
3830     // position relative to the MediaController's timeline or after the end of the media resource
3831     // relative to the MediaController's timeline.
3832     double mediaControllerPosition = m_mediaController->currentTime();
3833     if (mediaControllerPosition < 0 || mediaControllerPosition > duration())
3834         return true;
3835 
3836     return false;
3837 }
3838 
prepareMediaFragmentURI()3839 void HTMLMediaElement::prepareMediaFragmentURI()
3840 {
3841     MediaFragmentURIParser fragmentParser(m_currentSrc);
3842     double dur = duration();
3843 
3844     double start = fragmentParser.startTime();
3845     if (start != MediaFragmentURIParser::invalidTimeValue() && start > 0) {
3846         m_fragmentStartTime = start;
3847         if (m_fragmentStartTime > dur)
3848             m_fragmentStartTime = dur;
3849     } else
3850         m_fragmentStartTime = MediaPlayer::invalidTime();
3851 
3852     double end = fragmentParser.endTime();
3853     if (end != MediaFragmentURIParser::invalidTimeValue() && end > 0 && end > m_fragmentStartTime) {
3854         m_fragmentEndTime = end;
3855         if (m_fragmentEndTime > dur)
3856             m_fragmentEndTime = dur;
3857     } else
3858         m_fragmentEndTime = MediaPlayer::invalidTime();
3859 
3860     if (m_fragmentStartTime != MediaPlayer::invalidTime() && m_readyState < HAVE_FUTURE_DATA)
3861         prepareToPlay();
3862 }
3863 
applyMediaFragmentURI()3864 void HTMLMediaElement::applyMediaFragmentURI()
3865 {
3866     if (m_fragmentStartTime != MediaPlayer::invalidTime()) {
3867         m_sentEndEvent = false;
3868         seek(m_fragmentStartTime, IGNORE_EXCEPTION);
3869     }
3870 }
3871 
mediaPlayerCORSMode() const3872 MediaPlayerClient::CORSMode HTMLMediaElement::mediaPlayerCORSMode() const
3873 {
3874     if (!fastHasAttribute(crossoriginAttr))
3875         return Unspecified;
3876     if (equalIgnoringCase(fastGetAttribute(crossoriginAttr), "use-credentials"))
3877         return UseCredentials;
3878     return Anonymous;
3879 }
3880 
removeBehaviorsRestrictionsAfterFirstUserGesture()3881 void HTMLMediaElement::removeBehaviorsRestrictionsAfterFirstUserGesture()
3882 {
3883     m_restrictions = NoRestrictions;
3884 }
3885 
mediaPlayerSetWebLayer(blink::WebLayer * webLayer)3886 void HTMLMediaElement::mediaPlayerSetWebLayer(blink::WebLayer* webLayer)
3887 {
3888     if (webLayer == m_webLayer)
3889         return;
3890 
3891     // If either of the layers is null we need to enable or disable compositing. This is done by triggering a style recalc.
3892     if (!m_webLayer || !webLayer)
3893         scheduleLayerUpdate();
3894 
3895     if (m_webLayer)
3896         GraphicsLayer::unregisterContentsLayer(m_webLayer);
3897     m_webLayer = webLayer;
3898     if (m_webLayer) {
3899         m_webLayer->setOpaque(m_opaque);
3900         GraphicsLayer::registerContentsLayer(m_webLayer);
3901     }
3902 }
3903 
mediaPlayerSetOpaque(bool opaque)3904 void HTMLMediaElement::mediaPlayerSetOpaque(bool opaque)
3905 {
3906     m_opaque = opaque;
3907     if (m_webLayer)
3908         m_webLayer->setOpaque(m_opaque);
3909 }
3910 
isInteractiveContent() const3911 bool HTMLMediaElement::isInteractiveContent() const
3912 {
3913     return fastHasAttribute(controlsAttr);
3914 }
3915 
3916 }
3917