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