• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007, 2008, 2009, 2010 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 
28 #if ENABLE(VIDEO)
29 #include "HTMLMediaElement.h"
30 
31 #include "Chrome.h"
32 #include "ChromeClient.h"
33 #include "ClientRect.h"
34 #include "ClientRectList.h"
35 #include "CSSHelper.h"
36 #include "CSSPropertyNames.h"
37 #include "CSSValueKeywords.h"
38 #include "ContentType.h"
39 #include "DocLoader.h"
40 #include "Event.h"
41 #include "EventNames.h"
42 #include "ExceptionCode.h"
43 #include "Frame.h"
44 #include "FrameLoader.h"
45 #include "FrameLoaderClient.h"
46 #include "FrameView.h"
47 #include "HTMLDocument.h"
48 #include "HTMLNames.h"
49 #include "HTMLSourceElement.h"
50 #include "HTMLVideoElement.h"
51 #include "MIMETypeRegistry.h"
52 #include "MappedAttribute.h"
53 #include "MediaDocument.h"
54 #include "MediaError.h"
55 #include "MediaList.h"
56 #include "MediaPlayer.h"
57 #include "MediaQueryEvaluator.h"
58 #include "Page.h"
59 #include "RenderVideo.h"
60 #include "RenderView.h"
61 #include "ScriptEventListener.h"
62 #include "TimeRanges.h"
63 #include <limits>
64 #include <wtf/CurrentTime.h>
65 #include <wtf/MathExtras.h>
66 
67 #if USE(ACCELERATED_COMPOSITING)
68 #include "RenderView.h"
69 #include "RenderLayerCompositor.h"
70 #endif
71 
72 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
73 #include "RenderPartObject.h"
74 #include "Widget.h"
75 #endif
76 
77 using namespace std;
78 
79 namespace WebCore {
80 
81 using namespace HTMLNames;
82 
HTMLMediaElement(const QualifiedName & tagName,Document * doc)83 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* doc)
84     : HTMLElement(tagName, doc)
85     , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
86     , m_asyncEventTimer(this, &HTMLMediaElement::asyncEventTimerFired)
87     , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
88     , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
89     , m_playedTimeRanges()
90     , m_playbackRate(1.0f)
91     , m_defaultPlaybackRate(1.0f)
92     , m_webkitPreservesPitch(true)
93     , m_networkState(NETWORK_EMPTY)
94     , m_readyState(HAVE_NOTHING)
95     , m_volume(1.0f)
96     , m_lastSeekTime(0)
97     , m_previousProgress(0)
98     , m_previousProgressTime(numeric_limits<double>::max())
99     , m_lastTimeUpdateEventWallTime(0)
100     , m_lastTimeUpdateEventMovieTime(numeric_limits<float>::max())
101     , m_loadState(WaitingForSource)
102     , m_currentSourceNode(0)
103     , m_player(0)
104     , m_restrictions(NoRestrictions)
105     , m_playing(false)
106     , m_processingMediaPlayerCallback(0)
107     , m_processingLoad(false)
108     , m_delayingTheLoadEvent(false)
109     , m_haveFiredLoadedData(false)
110     , m_inActiveDocument(true)
111     , m_autoplaying(true)
112     , m_muted(false)
113     , m_paused(true)
114     , m_seeking(false)
115     , m_sentStalledEvent(false)
116     , m_sentEndEvent(false)
117     , m_pausedInternal(false)
118     , m_sendProgressEvents(true)
119     , m_isFullscreen(false)
120     , m_closedCaptionsVisible(false)
121 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
122     , m_needWidgetUpdate(false)
123 #endif
124 {
125     document()->registerForDocumentActivationCallbacks(this);
126     document()->registerForMediaVolumeCallbacks(this);
127 }
128 
~HTMLMediaElement()129 HTMLMediaElement::~HTMLMediaElement()
130 {
131     document()->unregisterForDocumentActivationCallbacks(this);
132     document()->unregisterForMediaVolumeCallbacks(this);
133 }
134 
willMoveToNewOwnerDocument()135 void HTMLMediaElement::willMoveToNewOwnerDocument()
136 {
137     document()->unregisterForDocumentActivationCallbacks(this);
138     document()->unregisterForMediaVolumeCallbacks(this);
139     HTMLElement::willMoveToNewOwnerDocument();
140 }
141 
didMoveToNewOwnerDocument()142 void HTMLMediaElement::didMoveToNewOwnerDocument()
143 {
144     document()->registerForDocumentActivationCallbacks(this);
145     document()->registerForMediaVolumeCallbacks(this);
146     HTMLElement::didMoveToNewOwnerDocument();
147 }
148 
149 
checkDTD(const Node * newChild)150 bool HTMLMediaElement::checkDTD(const Node* newChild)
151 {
152     return newChild->hasTagName(sourceTag) || HTMLElement::checkDTD(newChild);
153 }
154 
attributeChanged(Attribute * attr,bool preserveDecls)155 void HTMLMediaElement::attributeChanged(Attribute* attr, bool preserveDecls)
156 {
157     HTMLElement::attributeChanged(attr, preserveDecls);
158 
159     const QualifiedName& attrName = attr->name();
160     if (attrName == srcAttr) {
161         // don't have a src or any <source> children, trigger load
162         if (inDocument() && m_loadState == WaitingForSource)
163             scheduleLoad();
164     }
165 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
166     else if (attrName == controlsAttr) {
167         if (!isVideo() && attached() && (controls() != (renderer() != 0))) {
168             detach();
169             attach();
170         }
171         if (renderer())
172             renderer()->updateFromElement();
173     }
174 #endif
175 }
176 
parseMappedAttribute(MappedAttribute * attr)177 void HTMLMediaElement::parseMappedAttribute(MappedAttribute* attr)
178 {
179     const QualifiedName& attrName = attr->name();
180 
181     if (attrName == autobufferAttr) {
182         if (m_player)
183             m_player->setAutobuffer(!attr->isNull());
184     } else if (attrName == onabortAttr)
185         setAttributeEventListener(eventNames().abortEvent, createAttributeEventListener(this, attr));
186     else if (attrName == onbeforeloadAttr)
187         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
188     else if (attrName == oncanplayAttr)
189         setAttributeEventListener(eventNames().canplayEvent, createAttributeEventListener(this, attr));
190     else if (attrName == oncanplaythroughAttr)
191         setAttributeEventListener(eventNames().canplaythroughEvent, createAttributeEventListener(this, attr));
192     else if (attrName == ondurationchangeAttr)
193         setAttributeEventListener(eventNames().durationchangeEvent, createAttributeEventListener(this, attr));
194     else if (attrName == onemptiedAttr)
195         setAttributeEventListener(eventNames().emptiedEvent, createAttributeEventListener(this, attr));
196     else if (attrName == onendedAttr)
197         setAttributeEventListener(eventNames().endedEvent, createAttributeEventListener(this, attr));
198     else if (attrName == onerrorAttr)
199         setAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(this, attr));
200     else if (attrName == onloadAttr)
201         setAttributeEventListener(eventNames().loadEvent, createAttributeEventListener(this, attr));
202     else if (attrName == onloadeddataAttr)
203         setAttributeEventListener(eventNames().loadeddataEvent, createAttributeEventListener(this, attr));
204     else if (attrName == onloadedmetadataAttr)
205         setAttributeEventListener(eventNames().loadedmetadataEvent, createAttributeEventListener(this, attr));
206     else if (attrName == onloadstartAttr)
207         setAttributeEventListener(eventNames().loadstartEvent, createAttributeEventListener(this, attr));
208     else if (attrName == onpauseAttr)
209         setAttributeEventListener(eventNames().pauseEvent, createAttributeEventListener(this, attr));
210     else if (attrName == onplayAttr)
211         setAttributeEventListener(eventNames().playEvent, createAttributeEventListener(this, attr));
212     else if (attrName == onplayingAttr)
213         setAttributeEventListener(eventNames().playingEvent, createAttributeEventListener(this, attr));
214     else if (attrName == onprogressAttr)
215         setAttributeEventListener(eventNames().progressEvent, createAttributeEventListener(this, attr));
216     else if (attrName == onratechangeAttr)
217         setAttributeEventListener(eventNames().ratechangeEvent, createAttributeEventListener(this, attr));
218     else if (attrName == onseekedAttr)
219         setAttributeEventListener(eventNames().seekedEvent, createAttributeEventListener(this, attr));
220     else if (attrName == onseekingAttr)
221         setAttributeEventListener(eventNames().seekingEvent, createAttributeEventListener(this, attr));
222     else if (attrName == onstalledAttr)
223         setAttributeEventListener(eventNames().stalledEvent, createAttributeEventListener(this, attr));
224     else if (attrName == onsuspendAttr)
225         setAttributeEventListener(eventNames().suspendEvent, createAttributeEventListener(this, attr));
226     else if (attrName == ontimeupdateAttr)
227         setAttributeEventListener(eventNames().timeupdateEvent, createAttributeEventListener(this, attr));
228     else if (attrName == onvolumechangeAttr)
229         setAttributeEventListener(eventNames().volumechangeEvent, createAttributeEventListener(this, attr));
230     else if (attrName == onwaitingAttr)
231         setAttributeEventListener(eventNames().waitingEvent, createAttributeEventListener(this, attr));
232     else if (attrName == onwebkitbeginfullscreenAttr)
233         setAttributeEventListener(eventNames().webkitbeginfullscreenEvent, createAttributeEventListener(this, attr));
234     else if (attrName == onwebkitendfullscreenAttr)
235         setAttributeEventListener(eventNames().webkitendfullscreenEvent, createAttributeEventListener(this, attr));
236     else
237         HTMLElement::parseMappedAttribute(attr);
238 }
239 
rendererIsNeeded(RenderStyle * style)240 bool HTMLMediaElement::rendererIsNeeded(RenderStyle* style)
241 {
242 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
243     UNUSED_PARAM(style);
244     Frame* frame = document()->frame();
245     if (!frame)
246         return false;
247 
248     return true;
249 #else
250     return controls() ? HTMLElement::rendererIsNeeded(style) : false;
251 #endif
252 }
253 
createRenderer(RenderArena * arena,RenderStyle *)254 RenderObject* HTMLMediaElement::createRenderer(RenderArena* arena, RenderStyle*)
255 {
256 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
257     return new (arena) RenderEmbeddedObject(this);
258 #else
259     return new (arena) RenderMedia(this);
260 #endif
261 }
262 
insertedIntoDocument()263 void HTMLMediaElement::insertedIntoDocument()
264 {
265     HTMLElement::insertedIntoDocument();
266     if (!src().isEmpty() && m_networkState == NETWORK_EMPTY)
267         scheduleLoad();
268 }
269 
removedFromDocument()270 void HTMLMediaElement::removedFromDocument()
271 {
272     if (m_networkState > NETWORK_EMPTY)
273         pause(processingUserGesture());
274     if (m_isFullscreen)
275         exitFullscreen();
276     HTMLElement::removedFromDocument();
277 }
278 
attach()279 void HTMLMediaElement::attach()
280 {
281     ASSERT(!attached());
282 
283 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
284     m_needWidgetUpdate = true;
285 #endif
286 
287     HTMLElement::attach();
288 
289     if (renderer())
290         renderer()->updateFromElement();
291 }
292 
recalcStyle(StyleChange change)293 void HTMLMediaElement::recalcStyle(StyleChange change)
294 {
295     HTMLElement::recalcStyle(change);
296 
297     if (renderer())
298         renderer()->updateFromElement();
299 }
300 
scheduleLoad()301 void HTMLMediaElement::scheduleLoad()
302 {
303     if (m_loadTimer.isActive())
304         return;
305     prepareForLoad();
306     m_loadTimer.startOneShot(0);
307 }
308 
scheduleNextSourceChild()309 void HTMLMediaElement::scheduleNextSourceChild()
310 {
311     // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
312     m_loadTimer.startOneShot(0);
313 }
314 
scheduleEvent(const AtomicString & eventName)315 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
316 {
317     m_pendingEvents.append(Event::create(eventName, false, true));
318     if (!m_asyncEventTimer.isActive())
319         m_asyncEventTimer.startOneShot(0);
320 }
321 
asyncEventTimerFired(Timer<HTMLMediaElement> *)322 void HTMLMediaElement::asyncEventTimerFired(Timer<HTMLMediaElement>*)
323 {
324     Vector<RefPtr<Event> > pendingEvents;
325     ExceptionCode ec = 0;
326 
327     m_pendingEvents.swap(pendingEvents);
328     unsigned count = pendingEvents.size();
329     for (unsigned ndx = 0; ndx < count; ++ndx)
330         dispatchEvent(pendingEvents[ndx].release(), ec);
331 }
332 
loadTimerFired(Timer<HTMLMediaElement> *)333 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
334 {
335     if (m_loadState == LoadingFromSourceElement)
336         loadNextSourceChild();
337     else
338         loadInternal();
339 }
340 
serializeTimeOffset(float time)341 static String serializeTimeOffset(float time)
342 {
343     String timeString = String::number(time);
344     // FIXME serialize time offset values properly (format not specified yet)
345     timeString.append("s");
346     return timeString;
347 }
348 
parseTimeOffset(const String & timeString,bool * ok=0)349 static float parseTimeOffset(const String& timeString, bool* ok = 0)
350 {
351     const UChar* characters = timeString.characters();
352     unsigned length = timeString.length();
353 
354     if (length && characters[length - 1] == 's')
355         length--;
356 
357     // FIXME parse time offset values (format not specified yet)
358     float val = charactersToFloat(characters, length, ok);
359     return val;
360 }
361 
getTimeOffsetAttribute(const QualifiedName & name,float valueOnError) const362 float HTMLMediaElement::getTimeOffsetAttribute(const QualifiedName& name, float valueOnError) const
363 {
364     bool ok;
365     String timeString = getAttribute(name);
366     float result = parseTimeOffset(timeString, &ok);
367     if (ok)
368         return result;
369     return valueOnError;
370 }
371 
setTimeOffsetAttribute(const QualifiedName & name,float value)372 void HTMLMediaElement::setTimeOffsetAttribute(const QualifiedName& name, float value)
373 {
374     setAttribute(name, serializeTimeOffset(value));
375 }
376 
error() const377 PassRefPtr<MediaError> HTMLMediaElement::error() const
378 {
379     return m_error;
380 }
381 
src() const382 KURL HTMLMediaElement::src() const
383 {
384     return document()->completeURL(getAttribute(srcAttr));
385 }
386 
setSrc(const String & url)387 void HTMLMediaElement::setSrc(const String& url)
388 {
389     setAttribute(srcAttr, url);
390 }
391 
currentSrc() const392 String HTMLMediaElement::currentSrc() const
393 {
394     return m_currentSrc;
395 }
396 
networkState() const397 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
398 {
399     return m_networkState;
400 }
401 
canPlayType(const String & mimeType) const402 String HTMLMediaElement::canPlayType(const String& mimeType) const
403 {
404     MediaPlayer::SupportsType support = MediaPlayer::supportsType(ContentType(mimeType));
405     String canPlay;
406 
407     // 4.8.10.3
408     switch (support)
409     {
410         case MediaPlayer::IsNotSupported:
411             canPlay = "";
412             break;
413         case MediaPlayer::MayBeSupported:
414             canPlay = "maybe";
415             break;
416         case MediaPlayer::IsSupported:
417             canPlay = "probably";
418             break;
419     }
420 
421     return canPlay;
422 }
423 
load(bool isUserGesture,ExceptionCode & ec)424 void HTMLMediaElement::load(bool isUserGesture, ExceptionCode& ec)
425 {
426     if (m_restrictions & RequireUserGestureForLoadRestriction && !isUserGesture)
427         ec = INVALID_STATE_ERR;
428     else {
429         prepareForLoad();
430         loadInternal();
431     }
432 }
433 
prepareForLoad()434 void HTMLMediaElement::prepareForLoad()
435 {
436     // Perform the cleanup required for the resource load algorithm to run.
437     stopPeriodicTimers();
438     m_loadTimer.stop();
439     m_sentStalledEvent = false;
440     m_haveFiredLoadedData = false;
441 
442     // 2 - Abort any already-running instance of the resource selection algorithm for this element.
443     m_currentSourceNode = 0;
444 
445     // 3 - If there are any tasks from the media element's media element event task source in
446     // one of the task queues, then remove those tasks.
447     cancelPendingEventsAndCallbacks();
448 }
449 
loadInternal()450 void HTMLMediaElement::loadInternal()
451 {
452     // If the load() method for this element is already being invoked, then abort these steps.
453     if (m_processingLoad)
454         return;
455     m_processingLoad = true;
456 
457     // Steps 1 and 2 were done in prepareForLoad()
458 
459     // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
460     // a task to fire a simple event named abort at the media element.
461     if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
462         scheduleEvent(eventNames().abortEvent);
463 
464     // 4
465     if (m_networkState != NETWORK_EMPTY) {
466         m_networkState = NETWORK_EMPTY;
467         m_readyState = HAVE_NOTHING;
468         m_paused = true;
469         m_seeking = false;
470         if (m_player) {
471             m_player->pause();
472             m_playing = false;
473             m_player->seek(0);
474         }
475         scheduleEvent(eventNames().emptiedEvent);
476     }
477 
478     // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
479     setPlaybackRate(defaultPlaybackRate());
480 
481     // 6 - Set the error attribute to null and the autoplaying flag to true.
482     m_error = 0;
483     m_autoplaying = true;
484 
485     m_playedTimeRanges = TimeRanges::create();
486     m_lastSeekTime = 0;
487     m_closedCaptionsVisible = false;
488 
489     // 7 - Invoke the media element's resource selection algorithm.
490     selectMediaResource();
491     m_processingLoad = false;
492 }
493 
selectMediaResource()494 void HTMLMediaElement::selectMediaResource()
495 {
496     // 1 - Set the networkState to NETWORK_NO_SOURCE
497     m_networkState = NETWORK_NO_SOURCE;
498 
499     // 2 - Asynchronously await a stable state.
500 
501     // 3 - ... the media element has neither a src attribute ...
502     String mediaSrc = getAttribute(srcAttr);
503     if (!mediaSrc) {
504         // ... nor a source element child: ...
505         Node* node;
506         for (node = firstChild(); node; node = node->nextSibling()) {
507             if (node->hasTagName(sourceTag))
508                 break;
509         }
510 
511         if (!node) {
512             m_loadState = WaitingForSource;
513 
514             // ... set the networkState to NETWORK_EMPTY, and abort these steps
515             m_networkState = NETWORK_EMPTY;
516             ASSERT(!m_delayingTheLoadEvent);
517             return;
518         }
519     }
520 
521     // 4
522     m_delayingTheLoadEvent = true;
523     m_networkState = NETWORK_LOADING;
524 
525     // 5
526     scheduleEvent(eventNames().loadstartEvent);
527 
528     // 6 - If the media element has a src attribute, then run these substeps
529     ContentType contentType("");
530     if (!mediaSrc.isNull()) {
531         KURL mediaURL = document()->completeURL(mediaSrc);
532         if (isSafeToLoadURL(mediaURL, Complain) && dispatchBeforeLoadEvent(mediaURL.string())) {
533             m_loadState = LoadingFromSrcAttr;
534             loadResource(mediaURL, contentType);
535         } else
536             noneSupported();
537 
538         return;
539     }
540 
541     // Otherwise, the source elements will be used
542     m_currentSourceNode = 0;
543     loadNextSourceChild();
544 }
545 
loadNextSourceChild()546 void HTMLMediaElement::loadNextSourceChild()
547 {
548     ContentType contentType("");
549     KURL mediaURL = selectNextSourceChild(&contentType, Complain);
550     if (!mediaURL.isValid()) {
551         waitForSourceChange();
552         return;
553     }
554 
555     m_loadState = LoadingFromSourceElement;
556     loadResource(mediaURL, contentType);
557 }
558 
loadResource(const KURL & initialURL,ContentType & contentType)559 void HTMLMediaElement::loadResource(const KURL& initialURL, ContentType& contentType)
560 {
561     ASSERT(isSafeToLoadURL(initialURL, Complain));
562 
563     Frame* frame = document()->frame();
564     if (!frame)
565         return;
566     FrameLoader* loader = frame->loader();
567     if (!loader)
568         return;
569 
570     KURL url(initialURL);
571     if (!loader->willLoadMediaElementURL(url))
572         return;
573 
574     // The resource fetch algorithm
575     m_networkState = NETWORK_LOADING;
576 
577     m_currentSrc = url;
578 
579     if (m_sendProgressEvents)
580         startProgressEventTimer();
581 
582 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
583     m_player = MediaPlayer::create(this);
584 #else
585     if (!m_player)
586         m_player = MediaPlayer::create(this);
587 #endif
588 
589     m_player->setAutobuffer(autobuffer());
590     m_player->setPreservesPitch(m_webkitPreservesPitch);
591     updateVolume();
592 
593 #if PLATFORM(ANDROID)
594     if (isVideo())
595         m_player->setMediaElementType(MediaPlayer::Video);
596     else
597         m_player->setMediaElementType(MediaPlayer::Audio);
598 #endif
599     m_player->load(m_currentSrc, contentType);
600 
601     if (isVideo() && m_player->canLoadPoster()) {
602         KURL posterUrl = static_cast<HTMLVideoElement*>(this)->poster();
603         if (!posterUrl.isEmpty())
604             m_player->setPoster(posterUrl);
605     }
606 
607     if (renderer())
608         renderer()->updateFromElement();
609 }
610 
isSafeToLoadURL(const KURL & url,InvalidSourceAction actionIfInvalid)611 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidSourceAction actionIfInvalid)
612 {
613     Frame* frame = document()->frame();
614     FrameLoader* loader = frame ? frame->loader() : 0;
615 
616     // don't allow remote to local urls, and check with the frame loader client.
617     if (!loader || !SecurityOrigin::canLoad(url, String(), document())) {
618         if (actionIfInvalid == Complain)
619             FrameLoader::reportLocalLoadFailed(frame, url.string());
620         return false;
621     }
622 
623     return true;
624 }
625 
startProgressEventTimer()626 void HTMLMediaElement::startProgressEventTimer()
627 {
628     if (m_progressEventTimer.isActive())
629         return;
630 
631     m_previousProgressTime = WTF::currentTime();
632     m_previousProgress = 0;
633     // 350ms is not magic, it is in the spec!
634     m_progressEventTimer.startRepeating(0.350);
635 }
636 
waitForSourceChange()637 void HTMLMediaElement::waitForSourceChange()
638 {
639     stopPeriodicTimers();
640     m_loadState = WaitingForSource;
641 
642     // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
643     m_networkState = NETWORK_NO_SOURCE;
644 
645     // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
646     m_delayingTheLoadEvent = false;
647 }
648 
noneSupported()649 void HTMLMediaElement::noneSupported()
650 {
651     stopPeriodicTimers();
652     m_loadState = WaitingForSource;
653     m_currentSourceNode = 0;
654 
655     // 5 - Reaching this step indicates that either the URL failed to resolve, or the media
656     // resource failed to load. Set the error attribute to a new MediaError object whose
657     // code attribute is set to MEDIA_ERR_SRC_NOT_SUPPORTED.
658     m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
659 
660     // 6 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
661     m_networkState = NETWORK_NO_SOURCE;
662 
663     // 7 - Queue a task to fire a progress event called error at the media element, in
664     // the context of the fetching process that was used to try to obtain the media
665     // resource in the resource fetch algorithm.
666     scheduleEvent(eventNames().errorEvent);
667 
668     // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
669     m_delayingTheLoadEvent = false;
670 
671     // 9 -Abort these steps. Until the load() method is invoked, the element won't attempt to load another resource.
672 
673     if (isVideo())
674         static_cast<HTMLVideoElement*>(this)->updatePosterImage();
675     if (renderer())
676         renderer()->updateFromElement();
677 }
678 
mediaEngineError(PassRefPtr<MediaError> err)679 void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err)
680 {
681     // 1 - The user agent should cancel the fetching process.
682     stopPeriodicTimers();
683     m_loadState = WaitingForSource;
684 
685     // 2 - Set the error attribute to a new MediaError object whose code attribute is
686     // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
687     m_error = err;
688 
689     // 3 - Queue a task to fire a progress event called error at the media element, in
690     // the context of the fetching process started by this instance of this algorithm.
691     scheduleEvent(eventNames().errorEvent);
692 
693     // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
694     // task to fire a simple event called emptied at the element.
695     m_networkState = NETWORK_EMPTY;
696     scheduleEvent(eventNames().emptiedEvent);
697 
698     // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
699     m_delayingTheLoadEvent = false;
700 
701     // 6 - Abort the overall resource selection algorithm.
702     m_currentSourceNode = 0;
703 }
704 
cancelPendingEventsAndCallbacks()705 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
706 {
707     m_pendingEvents.clear();
708 
709     for (Node* node = firstChild(); node; node = node->nextSibling()) {
710         if (node->hasTagName(sourceTag))
711             static_cast<HTMLSourceElement*>(node)->cancelPendingErrorEvent();
712     }
713 }
714 
mediaPlayerNetworkStateChanged(MediaPlayer *)715 void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
716 {
717     beginProcessingMediaPlayerCallback();
718     setNetworkState(m_player->networkState());
719     endProcessingMediaPlayerCallback();
720 }
721 
setNetworkState(MediaPlayer::NetworkState state)722 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
723 {
724     if (state == MediaPlayer::Empty) {
725         // just update the cached state and leave, we can't do anything
726         m_networkState = NETWORK_EMPTY;
727         return;
728     }
729 
730     if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
731         stopPeriodicTimers();
732 
733         // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
734         // <source> children, schedule the next one
735         if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
736             m_currentSourceNode->scheduleErrorEvent();
737             if (havePotentialSourceChild())
738                 scheduleNextSourceChild();
739             else
740                 waitForSourceChange();
741 
742             return;
743         }
744 
745         if (state == MediaPlayer::NetworkError)
746             mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK));
747         else if (state == MediaPlayer::DecodeError)
748             mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE));
749         else if (state == MediaPlayer::FormatError && m_loadState == LoadingFromSrcAttr)
750             noneSupported();
751 
752         if (isVideo())
753             static_cast<HTMLVideoElement*>(this)->updatePosterImage();
754 
755         return;
756     }
757 
758     if (state == MediaPlayer::Idle) {
759         if (m_networkState > NETWORK_IDLE) {
760             stopPeriodicTimers();
761             scheduleEvent(eventNames().suspendEvent);
762         }
763         m_networkState = NETWORK_IDLE;
764     }
765 
766     if (state == MediaPlayer::Loading) {
767         if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
768             startProgressEventTimer();
769         m_networkState = NETWORK_LOADING;
770     }
771 
772     if (state == MediaPlayer::Loaded) {
773         NetworkState oldState = m_networkState;
774 
775         m_networkState = NETWORK_LOADED;
776         if (oldState < NETWORK_LOADED || oldState == NETWORK_NO_SOURCE) {
777             m_progressEventTimer.stop();
778 
779             // Schedule one last progress event so we guarantee that at least one is fired
780             // for files that load very quickly.
781             scheduleEvent(eventNames().progressEvent);
782 
783             // Check to see if readyState changes need to be dealt with before sending the
784             // 'load' event so we report 'canplaythrough' first. This is necessary because a
785             //  media engine reports readyState and networkState changes separately
786             MediaPlayer::ReadyState currentState = m_player->readyState();
787             if (static_cast<ReadyState>(currentState) != m_readyState)
788                 setReadyState(currentState);
789 
790             scheduleEvent(eventNames().loadEvent);
791         }
792     }
793 }
794 
mediaPlayerReadyStateChanged(MediaPlayer *)795 void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*)
796 {
797     beginProcessingMediaPlayerCallback();
798 
799     setReadyState(m_player->readyState());
800 
801     endProcessingMediaPlayerCallback();
802 }
803 
setReadyState(MediaPlayer::ReadyState state)804 void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
805 {
806     // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
807     bool wasPotentiallyPlaying = potentiallyPlaying();
808 
809     ReadyState oldState = m_readyState;
810     m_readyState = static_cast<ReadyState>(state);
811 
812     if (m_readyState == oldState)
813         return;
814 
815     if (m_networkState == NETWORK_EMPTY)
816         return;
817 
818     if (m_seeking) {
819         // 4.8.10.10, step 8
820         if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
821             scheduleEvent(eventNames().waitingEvent);
822 
823         // 4.8.10.10, step 9
824         if (m_readyState < HAVE_CURRENT_DATA) {
825             if (oldState >= HAVE_CURRENT_DATA)
826                 scheduleEvent(eventNames().seekingEvent);
827         } else {
828             // 4.8.10.10 step 12 & 13.
829             finishSeek();
830         }
831 
832     } else {
833         if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
834             // 4.8.10.9
835             scheduleTimeupdateEvent(false);
836             scheduleEvent(eventNames().waitingEvent);
837         }
838     }
839 
840     if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
841         scheduleEvent(eventNames().durationchangeEvent);
842         scheduleEvent(eventNames().loadedmetadataEvent);
843 
844 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
845         if (renderer() && renderer()->isVideo()) {
846             toRenderVideo(renderer())->videoSizeChanged();
847         }
848 #endif
849         m_delayingTheLoadEvent = false;
850         m_player->seek(0);
851     }
852 
853     bool shouldUpdatePosterImage = false;
854 
855     // 4.8.10.7 says loadeddata is sent only when the new state *is* HAVE_CURRENT_DATA: "If the
856     // previous ready state was HAVE_METADATA and the new ready state is HAVE_CURRENT_DATA",
857     // but the event table at the end of the spec says it is sent when: "readyState newly
858     // increased to HAVE_CURRENT_DATA  or greater for the first time"
859     // We go with the later because it seems useful to count on getting this event
860     if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
861         m_haveFiredLoadedData = true;
862         shouldUpdatePosterImage = true;
863         scheduleEvent(eventNames().loadeddataEvent);
864     }
865 
866     bool isPotentiallyPlaying = potentiallyPlaying();
867     if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA) {
868         scheduleEvent(eventNames().canplayEvent);
869         if (isPotentiallyPlaying)
870             scheduleEvent(eventNames().playingEvent);
871         shouldUpdatePosterImage = true;
872     }
873 
874     if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA) {
875         if (oldState <= HAVE_CURRENT_DATA)
876             scheduleEvent(eventNames().canplayEvent);
877 
878         scheduleEvent(eventNames().canplaythroughEvent);
879 
880         if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA)
881             scheduleEvent(eventNames().playingEvent);
882 
883         if (m_autoplaying && m_paused && autoplay()) {
884             m_paused = false;
885             scheduleEvent(eventNames().playEvent);
886             scheduleEvent(eventNames().playingEvent);
887         }
888 
889         shouldUpdatePosterImage = true;
890     }
891 
892     if (shouldUpdatePosterImage && isVideo())
893         static_cast<HTMLVideoElement*>(this)->updatePosterImage();
894 
895     updatePlayState();
896 }
897 
progressEventTimerFired(Timer<HTMLMediaElement> *)898 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
899 {
900     ASSERT(m_player);
901     if (m_networkState == NETWORK_EMPTY || m_networkState >= NETWORK_LOADED)
902         return;
903 
904     unsigned progress = m_player->bytesLoaded();
905     double time = WTF::currentTime();
906     double timedelta = time - m_previousProgressTime;
907 
908     if (progress == m_previousProgress) {
909         if (timedelta > 3.0 && !m_sentStalledEvent) {
910             scheduleEvent(eventNames().stalledEvent);
911             m_sentStalledEvent = true;
912         }
913     } else {
914         scheduleEvent(eventNames().progressEvent);
915         m_previousProgress = progress;
916         m_previousProgressTime = time;
917         m_sentStalledEvent = false;
918         if (renderer())
919             renderer()->updateFromElement();
920     }
921 }
922 
rewind(float timeDelta)923 void HTMLMediaElement::rewind(float timeDelta)
924 {
925     ExceptionCode e;
926     setCurrentTime(max(currentTime() - timeDelta, minTimeSeekable()), e);
927 }
928 
returnToRealtime()929 void HTMLMediaElement::returnToRealtime()
930 {
931     ExceptionCode e;
932     setCurrentTime(maxTimeSeekable(), e);
933 }
934 
addPlayedRange(float start,float end)935 void HTMLMediaElement::addPlayedRange(float start, float end)
936 {
937     if (!m_playedTimeRanges)
938         m_playedTimeRanges = TimeRanges::create();
939     m_playedTimeRanges->add(start, end);
940 }
941 
supportsSave() const942 bool HTMLMediaElement::supportsSave() const
943 {
944     return m_player ? m_player->supportsSave() : false;
945 }
946 
seek(float time,ExceptionCode & ec)947 void HTMLMediaElement::seek(float time, ExceptionCode& ec)
948 {
949     // 4.8.10.10. Seeking
950     // 1
951     if (m_readyState == HAVE_NOTHING || !m_player) {
952         ec = INVALID_STATE_ERR;
953         return;
954     }
955 
956     // 2
957     time = min(time, duration());
958 
959     // 3
960     time = max(time, 0.0f);
961 
962     // 4
963     RefPtr<TimeRanges> seekableRanges = seekable();
964     if (!seekableRanges->contain(time)) {
965         ec = INDEX_SIZE_ERR;
966         return;
967     }
968 
969     // avoid generating events when the time won't actually change
970     float now = currentTime();
971     if (time == now)
972         return;
973 
974     // 5
975     if (m_playing) {
976         if (m_lastSeekTime < now)
977             addPlayedRange(m_lastSeekTime, now);
978     }
979     m_lastSeekTime = time;
980 
981     // 6 - set the seeking flag, it will be cleared when the engine tells is the time has actually changed
982     m_seeking = true;
983 
984     // 7
985     scheduleTimeupdateEvent(false);
986 
987     // 8 - this is covered, if necessary, when the engine signals a readystate change
988 
989     // 10
990     m_player->seek(time);
991     m_sentEndEvent = false;
992 }
993 
finishSeek()994 void HTMLMediaElement::finishSeek()
995 {
996     // 4.8.10.10 Seeking step 12
997     m_seeking = false;
998 
999     // 4.8.10.10 Seeking step 13
1000     scheduleEvent(eventNames().seekedEvent);
1001 }
1002 
readyState() const1003 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
1004 {
1005     return m_readyState;
1006 }
1007 
movieLoadType() const1008 MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const
1009 {
1010     return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown;
1011 }
1012 
hasAudio() const1013 bool HTMLMediaElement::hasAudio() const
1014 {
1015     return m_player ? m_player->hasAudio() : false;
1016 }
1017 
seeking() const1018 bool HTMLMediaElement::seeking() const
1019 {
1020     return m_seeking;
1021 }
1022 
1023 // playback state
currentTime() const1024 float HTMLMediaElement::currentTime() const
1025 {
1026     if (!m_player)
1027         return 0;
1028     if (m_seeking)
1029         return m_lastSeekTime;
1030     return m_player->currentTime();
1031 }
1032 
setCurrentTime(float time,ExceptionCode & ec)1033 void HTMLMediaElement::setCurrentTime(float time, ExceptionCode& ec)
1034 {
1035     seek(time, ec);
1036 }
1037 
startTime() const1038 float HTMLMediaElement::startTime() const
1039 {
1040     if (!m_player)
1041         return 0;
1042     return m_player->startTime();
1043 }
1044 
duration() const1045 float HTMLMediaElement::duration() const
1046 {
1047     if (m_readyState >= HAVE_METADATA)
1048         return m_player->duration();
1049 
1050     return numeric_limits<float>::quiet_NaN();
1051 }
1052 
paused() const1053 bool HTMLMediaElement::paused() const
1054 {
1055     return m_paused;
1056 }
1057 
defaultPlaybackRate() const1058 float HTMLMediaElement::defaultPlaybackRate() const
1059 {
1060     return m_defaultPlaybackRate;
1061 }
1062 
setDefaultPlaybackRate(float rate)1063 void HTMLMediaElement::setDefaultPlaybackRate(float rate)
1064 {
1065     if (m_defaultPlaybackRate != rate) {
1066         m_defaultPlaybackRate = rate;
1067         scheduleEvent(eventNames().ratechangeEvent);
1068     }
1069 }
1070 
playbackRate() const1071 float HTMLMediaElement::playbackRate() const
1072 {
1073     return m_player ? m_player->rate() : 0;
1074 }
1075 
setPlaybackRate(float rate)1076 void HTMLMediaElement::setPlaybackRate(float rate)
1077 {
1078     if (m_playbackRate != rate) {
1079         m_playbackRate = rate;
1080         scheduleEvent(eventNames().ratechangeEvent);
1081     }
1082     if (m_player && potentiallyPlaying() && m_player->rate() != rate)
1083         m_player->setRate(rate);
1084 }
1085 
webkitPreservesPitch() const1086 bool HTMLMediaElement::webkitPreservesPitch() const
1087 {
1088     return m_webkitPreservesPitch;
1089 }
1090 
setWebkitPreservesPitch(bool preservesPitch)1091 void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch)
1092 {
1093     m_webkitPreservesPitch = preservesPitch;
1094 
1095     if (!m_player)
1096         return;
1097 
1098     m_player->setPreservesPitch(preservesPitch);
1099 }
1100 
ended() const1101 bool HTMLMediaElement::ended() const
1102 {
1103     // 4.8.10.8 Playing the media resource
1104     // The ended attribute must return true if the media element has ended
1105     // playback and the direction of playback is forwards, and false otherwise.
1106     return endedPlayback() && m_playbackRate > 0;
1107 }
1108 
autoplay() const1109 bool HTMLMediaElement::autoplay() const
1110 {
1111     return hasAttribute(autoplayAttr);
1112 }
1113 
setAutoplay(bool b)1114 void HTMLMediaElement::setAutoplay(bool b)
1115 {
1116     setBooleanAttribute(autoplayAttr, b);
1117 }
1118 
autobuffer() const1119 bool HTMLMediaElement::autobuffer() const
1120 {
1121     return hasAttribute(autobufferAttr);
1122 }
1123 
setAutobuffer(bool b)1124 void HTMLMediaElement::setAutobuffer(bool b)
1125 {
1126     setBooleanAttribute(autobufferAttr, b);
1127 }
1128 
play(bool isUserGesture)1129 void HTMLMediaElement::play(bool isUserGesture)
1130 {
1131     if (m_restrictions & RequireUserGestureForRateChangeRestriction && !isUserGesture)
1132         return;
1133 
1134     playInternal();
1135 }
1136 
playInternal()1137 void HTMLMediaElement::playInternal()
1138 {
1139     // 4.8.10.9. Playing the media resource
1140     if (!m_player || m_networkState == NETWORK_EMPTY)
1141         scheduleLoad();
1142 
1143     if (endedPlayback()) {
1144         ExceptionCode unused;
1145         seek(0, unused);
1146     }
1147 
1148     setPlaybackRate(defaultPlaybackRate());
1149 
1150     if (m_paused) {
1151         m_paused = false;
1152         scheduleEvent(eventNames().playEvent);
1153 
1154         if (m_readyState <= HAVE_CURRENT_DATA)
1155             scheduleEvent(eventNames().waitingEvent);
1156         else if (m_readyState >= HAVE_FUTURE_DATA)
1157             scheduleEvent(eventNames().playingEvent);
1158     }
1159     m_autoplaying = false;
1160 
1161     updatePlayState();
1162 }
1163 
pause(bool isUserGesture)1164 void HTMLMediaElement::pause(bool isUserGesture)
1165 {
1166     if (m_restrictions & RequireUserGestureForRateChangeRestriction && !isUserGesture)
1167         return;
1168 
1169     pauseInternal();
1170 }
1171 
1172 
pauseInternal()1173 void HTMLMediaElement::pauseInternal()
1174 {
1175     // 4.8.10.9. Playing the media resource
1176     if (!m_player || m_networkState == NETWORK_EMPTY)
1177         scheduleLoad();
1178 
1179     m_autoplaying = false;
1180 
1181     if (!m_paused) {
1182         m_paused = true;
1183         scheduleTimeupdateEvent(false);
1184         scheduleEvent(eventNames().pauseEvent);
1185     }
1186 
1187     updatePlayState();
1188 }
1189 
loop() const1190 bool HTMLMediaElement::loop() const
1191 {
1192     return hasAttribute(loopAttr);
1193 }
1194 
setLoop(bool b)1195 void HTMLMediaElement::setLoop(bool b)
1196 {
1197     setBooleanAttribute(loopAttr, b);
1198 }
1199 
controls() const1200 bool HTMLMediaElement::controls() const
1201 {
1202     Frame* frame = document()->frame();
1203 
1204     // always show controls when scripting is disabled
1205     if (frame && !frame->script()->canExecuteScripts())
1206         return true;
1207 
1208     return hasAttribute(controlsAttr);
1209 }
1210 
setControls(bool b)1211 void HTMLMediaElement::setControls(bool b)
1212 {
1213     setBooleanAttribute(controlsAttr, b);
1214 }
1215 
volume() const1216 float HTMLMediaElement::volume() const
1217 {
1218     return m_volume;
1219 }
1220 
setVolume(float vol,ExceptionCode & ec)1221 void HTMLMediaElement::setVolume(float vol, ExceptionCode& ec)
1222 {
1223     if (vol < 0.0f || vol > 1.0f) {
1224         ec = INDEX_SIZE_ERR;
1225         return;
1226     }
1227 
1228     if (m_volume != vol) {
1229         m_volume = vol;
1230         updateVolume();
1231         scheduleEvent(eventNames().volumechangeEvent);
1232     }
1233 }
1234 
muted() const1235 bool HTMLMediaElement::muted() const
1236 {
1237     return m_muted;
1238 }
1239 
setMuted(bool muted)1240 void HTMLMediaElement::setMuted(bool muted)
1241 {
1242     if (m_muted != muted) {
1243         m_muted = muted;
1244         // Avoid recursion when the player reports volume changes.
1245         if (!processingMediaPlayerCallback()) {
1246             if (m_player && m_player->supportsMuting()) {
1247                 m_player->setMuted(m_muted);
1248                 if (renderer())
1249                     renderer()->updateFromElement();
1250             } else
1251                 updateVolume();
1252         }
1253         scheduleEvent(eventNames().volumechangeEvent);
1254     }
1255 }
1256 
togglePlayState()1257 void HTMLMediaElement::togglePlayState()
1258 {
1259     // We can safely call the internal play/pause methods, which don't check restrictions, because
1260     // this method is only called from the built-in media controller
1261     if (canPlay())
1262         playInternal();
1263     else
1264         pauseInternal();
1265 }
1266 
beginScrubbing()1267 void HTMLMediaElement::beginScrubbing()
1268 {
1269     if (!paused()) {
1270         if (ended()) {
1271             // Because a media element stays in non-paused state when it reaches end, playback resumes
1272             // when the slider is dragged from the end to another position unless we pause first. Do
1273             // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes.
1274             pause(processingUserGesture());
1275         } else {
1276             // Not at the end but we still want to pause playback so the media engine doesn't try to
1277             // continue playing during scrubbing. Pause without generating an event as we will
1278             // unpause after scrubbing finishes.
1279             setPausedInternal(true);
1280         }
1281     }
1282 }
1283 
endScrubbing()1284 void HTMLMediaElement::endScrubbing()
1285 {
1286     if (m_pausedInternal)
1287         setPausedInternal(false);
1288 }
1289 
1290 // The spec says to fire periodic timeupdate events (those sent while playing) every
1291 // "15 to 250ms", we choose the slowest frequency
1292 static const double maxTimeupdateEventFrequency = 0.25;
1293 
startPlaybackProgressTimer()1294 void HTMLMediaElement::startPlaybackProgressTimer()
1295 {
1296     if (m_playbackProgressTimer.isActive())
1297         return;
1298 
1299     m_previousProgressTime = WTF::currentTime();
1300     m_previousProgress = 0;
1301     m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency);
1302 }
1303 
playbackProgressTimerFired(Timer<HTMLMediaElement> *)1304 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*)
1305 {
1306     ASSERT(m_player);
1307     if (!m_playbackRate)
1308         return;
1309 
1310     scheduleTimeupdateEvent(true);
1311 
1312     // FIXME: deal with cue ranges here
1313 }
1314 
scheduleTimeupdateEvent(bool periodicEvent)1315 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
1316 {
1317     double now = WTF::currentTime();
1318     double timedelta = now - m_lastTimeUpdateEventWallTime;
1319 
1320     // throttle the periodic events
1321     if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
1322         return;
1323 
1324     // Some media engines make multiple "time changed" callbacks at the same time, but we only want one
1325     // event at a given time so filter here
1326     float movieTime = m_player ? m_player->currentTime() : 0;
1327     if (movieTime != m_lastTimeUpdateEventMovieTime) {
1328         scheduleEvent(eventNames().timeupdateEvent);
1329         m_lastTimeUpdateEventWallTime = now;
1330         m_lastTimeUpdateEventMovieTime = movieTime;
1331     }
1332 }
1333 
canPlay() const1334 bool HTMLMediaElement::canPlay() const
1335 {
1336     return paused() || ended() || m_readyState < HAVE_METADATA;
1337 }
1338 
percentLoaded() const1339 float HTMLMediaElement::percentLoaded() const
1340 {
1341     if (!m_player)
1342         return 0;
1343     float duration = m_player->duration();
1344 
1345     if (!duration || isinf(duration))
1346         return 0;
1347 
1348     float buffered = 0;
1349     RefPtr<TimeRanges> timeRanges = m_player->buffered();
1350     for (unsigned i = 0; i < timeRanges->length(); ++i) {
1351         ExceptionCode ignoredException;
1352         float start = timeRanges->start(i, ignoredException);
1353         float end = timeRanges->end(i, ignoredException);
1354         buffered += end - start;
1355     }
1356     return buffered / duration;
1357 }
1358 
havePotentialSourceChild()1359 bool HTMLMediaElement::havePotentialSourceChild()
1360 {
1361     // Stash the current <source> node so we can restore it after checking
1362     // to see there is another potential
1363     HTMLSourceElement* currentSourceNode = m_currentSourceNode;
1364     KURL nextURL = selectNextSourceChild(0, DoNothing);
1365     m_currentSourceNode = currentSourceNode;
1366 
1367     return nextURL.isValid();
1368 }
1369 
selectNextSourceChild(ContentType * contentType,InvalidSourceAction actionIfInvalid)1370 KURL HTMLMediaElement::selectNextSourceChild(ContentType *contentType, InvalidSourceAction actionIfInvalid)
1371 {
1372     KURL mediaURL;
1373     Node* node;
1374     bool lookingForPreviousNode = m_currentSourceNode;
1375     bool canUse = false;
1376 
1377     for (node = firstChild(); !canUse && node; node = node->nextSibling()) {
1378         if (!node->hasTagName(sourceTag))
1379             continue;
1380 
1381         if (lookingForPreviousNode) {
1382             if (m_currentSourceNode == static_cast<HTMLSourceElement*>(node))
1383                 lookingForPreviousNode = false;
1384             continue;
1385         }
1386 
1387         HTMLSourceElement* source = static_cast<HTMLSourceElement*>(node);
1388         if (!source->hasAttribute(srcAttr))
1389             goto check_again;
1390 
1391         if (source->hasAttribute(mediaAttr)) {
1392             MediaQueryEvaluator screenEval("screen", document()->frame(), renderer() ? renderer()->style() : 0);
1393             RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(source->media());
1394             if (!screenEval.eval(media.get()))
1395                 goto check_again;
1396         }
1397 
1398         if (source->hasAttribute(typeAttr)) {
1399             if (!MediaPlayer::supportsType(ContentType(source->type())))
1400                 goto check_again;
1401         }
1402 
1403         // Is it safe to load this url?
1404         mediaURL = source->src();
1405         if (!mediaURL.isValid() || !isSafeToLoadURL(mediaURL, actionIfInvalid) || !dispatchBeforeLoadEvent(mediaURL.string()))
1406             goto check_again;
1407 
1408         // Making it this far means the <source> looks reasonable
1409         canUse = true;
1410         if (contentType)
1411             *contentType = ContentType(source->type());
1412 
1413 check_again:
1414         if (!canUse && actionIfInvalid == Complain)
1415             source->scheduleErrorEvent();
1416         m_currentSourceNode = static_cast<HTMLSourceElement*>(node);
1417     }
1418 
1419     if (!canUse)
1420         m_currentSourceNode = 0;
1421     return canUse ? mediaURL : KURL();
1422 }
1423 
mediaPlayerTimeChanged(MediaPlayer *)1424 void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*)
1425 {
1426     beginProcessingMediaPlayerCallback();
1427 
1428     // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity,
1429     // it will only queue a 'timeupdate' event if we haven't already posted one at the current
1430     // movie time.
1431     scheduleTimeupdateEvent(false);
1432 
1433     // 4.8.10.10 step 12 & 13.  Needed if no ReadyState change is associated with the seek.
1434     if (m_readyState >= HAVE_CURRENT_DATA && m_seeking)
1435         finishSeek();
1436 
1437     float now = currentTime();
1438     float dur = duration();
1439     if (!isnan(dur) && dur && now >= dur) {
1440         if (loop()) {
1441             ExceptionCode ignoredException;
1442             m_sentEndEvent = false;
1443             seek(0, ignoredException);
1444         } else {
1445             if (!m_sentEndEvent) {
1446                 m_sentEndEvent = true;
1447                 scheduleEvent(eventNames().endedEvent);
1448             }
1449         }
1450     }
1451     else
1452         m_sentEndEvent = false;
1453 
1454     updatePlayState();
1455     endProcessingMediaPlayerCallback();
1456 }
1457 
mediaPlayerVolumeChanged(MediaPlayer *)1458 void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*)
1459 {
1460     beginProcessingMediaPlayerCallback();
1461     if (m_player)
1462         m_volume = m_player->volume();
1463     updateVolume();
1464     endProcessingMediaPlayerCallback();
1465 }
1466 
mediaPlayerMuteChanged(MediaPlayer *)1467 void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*)
1468 {
1469     beginProcessingMediaPlayerCallback();
1470     if (m_player)
1471         setMuted(m_player->muted());
1472     endProcessingMediaPlayerCallback();
1473 }
1474 
mediaPlayerDurationChanged(MediaPlayer *)1475 void HTMLMediaElement::mediaPlayerDurationChanged(MediaPlayer*)
1476 {
1477     beginProcessingMediaPlayerCallback();
1478     scheduleEvent(eventNames().durationchangeEvent);
1479 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
1480     if (renderer()) {
1481         renderer()->updateFromElement();
1482         if (renderer()->isVideo())
1483             toRenderVideo(renderer())->videoSizeChanged();
1484     }
1485 #endif
1486     endProcessingMediaPlayerCallback();
1487 }
1488 
mediaPlayerRateChanged(MediaPlayer *)1489 void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*)
1490 {
1491     beginProcessingMediaPlayerCallback();
1492     // Stash the rate in case the one we tried to set isn't what the engine is
1493     // using (eg. it can't handle the rate we set)
1494     m_playbackRate = m_player->rate();
1495     endProcessingMediaPlayerCallback();
1496 }
1497 
mediaPlayerSawUnsupportedTracks(MediaPlayer *)1498 void HTMLMediaElement::mediaPlayerSawUnsupportedTracks(MediaPlayer*)
1499 {
1500     // The MediaPlayer came across content it cannot completely handle.
1501     // This is normally acceptable except when we are in a standalone
1502     // MediaDocument. If so, tell the document what has happened.
1503     if (ownerDocument()->isMediaDocument()) {
1504         MediaDocument* mediaDocument = static_cast<MediaDocument*>(ownerDocument());
1505         mediaDocument->mediaElementSawUnsupportedTracks();
1506     }
1507 }
1508 
1509 // MediaPlayerPresentation methods
mediaPlayerRepaint(MediaPlayer *)1510 void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*)
1511 {
1512     beginProcessingMediaPlayerCallback();
1513     if (renderer())
1514         renderer()->repaint();
1515     endProcessingMediaPlayerCallback();
1516 }
1517 
mediaPlayerSizeChanged(MediaPlayer *)1518 void HTMLMediaElement::mediaPlayerSizeChanged(MediaPlayer*)
1519 {
1520     beginProcessingMediaPlayerCallback();
1521 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
1522     if (renderer() && renderer()->isVideo())
1523         toRenderVideo(renderer())->videoSizeChanged();
1524 #endif
1525     endProcessingMediaPlayerCallback();
1526 }
1527 
1528 #if USE(ACCELERATED_COMPOSITING)
mediaPlayerRenderingCanBeAccelerated(MediaPlayer *)1529 bool HTMLMediaElement::mediaPlayerRenderingCanBeAccelerated(MediaPlayer*)
1530 {
1531     if (renderer() && renderer()->isVideo()) {
1532         ASSERT(renderer()->view());
1533         return renderer()->view()->compositor()->canAccelerateVideoRendering(toRenderVideo(renderer()));
1534     }
1535     return false;
1536 }
1537 
mediaPlayerGraphicsLayer(MediaPlayer *)1538 GraphicsLayer* HTMLMediaElement::mediaPlayerGraphicsLayer(MediaPlayer*)
1539 {
1540     if (renderer() && renderer()->isVideo())
1541         return toRenderVideo(renderer())->videoGraphicsLayer();
1542     return 0;
1543 }
1544 #endif
1545 
buffered() const1546 PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const
1547 {
1548     if (!m_player)
1549         return TimeRanges::create();
1550     return m_player->buffered();
1551 }
1552 
played()1553 PassRefPtr<TimeRanges> HTMLMediaElement::played()
1554 {
1555     if (m_playing) {
1556         float time = currentTime();
1557         if (time > m_lastSeekTime)
1558             addPlayedRange(m_lastSeekTime, time);
1559     }
1560 
1561     if (!m_playedTimeRanges)
1562         m_playedTimeRanges = TimeRanges::create();
1563 
1564     return m_playedTimeRanges->copy();
1565 }
1566 
seekable() const1567 PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const
1568 {
1569     // FIXME real ranges support
1570     if (!maxTimeSeekable())
1571         return TimeRanges::create();
1572     return TimeRanges::create(minTimeSeekable(), maxTimeSeekable());
1573 }
1574 
potentiallyPlaying() const1575 bool HTMLMediaElement::potentiallyPlaying() const
1576 {
1577     return m_readyState >= HAVE_FUTURE_DATA && couldPlayIfEnoughData();
1578 }
1579 
couldPlayIfEnoughData() const1580 bool HTMLMediaElement::couldPlayIfEnoughData() const
1581 {
1582     return !paused() && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction();
1583 }
1584 
endedPlayback() const1585 bool HTMLMediaElement::endedPlayback() const
1586 {
1587     float dur = duration();
1588     if (!m_player || isnan(dur))
1589         return false;
1590 
1591     // 4.8.10.8 Playing the media resource
1592 
1593     // A media element is said to have ended playback when the element's
1594     // readyState attribute is HAVE_METADATA or greater,
1595     if (m_readyState < HAVE_METADATA)
1596         return false;
1597 
1598     // and the current playback position is the end of the media resource and the direction
1599     // of playback is forwards and the media element does not have a loop attribute specified,
1600     float now = currentTime();
1601     if (m_playbackRate > 0)
1602         return now >= dur && !loop();
1603 
1604     // or the current playback position is the earliest possible position and the direction
1605     // of playback is backwards
1606     if (m_playbackRate < 0)
1607         return now <= 0;
1608 
1609     return false;
1610 }
1611 
stoppedDueToErrors() const1612 bool HTMLMediaElement::stoppedDueToErrors() const
1613 {
1614     if (m_readyState >= HAVE_METADATA && m_error) {
1615         RefPtr<TimeRanges> seekableRanges = seekable();
1616         if (!seekableRanges->contain(currentTime()))
1617             return true;
1618     }
1619 
1620     return false;
1621 }
1622 
pausedForUserInteraction() const1623 bool HTMLMediaElement::pausedForUserInteraction() const
1624 {
1625 //    return !paused() && m_readyState >= HAVE_FUTURE_DATA && [UA requires a decitions from the user]
1626     return false;
1627 }
1628 
minTimeSeekable() const1629 float HTMLMediaElement::minTimeSeekable() const
1630 {
1631     return 0;
1632 }
1633 
maxTimeSeekable() const1634 float HTMLMediaElement::maxTimeSeekable() const
1635 {
1636     return m_player ? m_player->maxTimeSeekable() : 0;
1637 }
1638 
updateVolume()1639 void HTMLMediaElement::updateVolume()
1640 {
1641     if (!m_player)
1642         return;
1643 
1644     // Avoid recursion when the player reports volume changes.
1645     if (!processingMediaPlayerCallback()) {
1646         Page* page = document()->page();
1647         float volumeMultiplier = page ? page->mediaVolume() : 1;
1648 
1649         m_player->setVolume(m_muted ? 0 : m_volume * volumeMultiplier);
1650     }
1651 
1652     if (renderer())
1653         renderer()->updateFromElement();
1654 }
1655 
updatePlayState()1656 void HTMLMediaElement::updatePlayState()
1657 {
1658     if (!m_player)
1659         return;
1660 
1661     if (m_pausedInternal) {
1662         if (!m_player->paused())
1663             m_player->pause();
1664         m_playbackProgressTimer.stop();
1665         return;
1666     }
1667 
1668     bool shouldBePlaying = potentiallyPlaying();
1669     bool playerPaused = m_player->paused();
1670     if (shouldBePlaying && playerPaused) {
1671         // Set rate before calling play in case the rate was set before the media engine wasn't setup.
1672         // The media engine should just stash the rate since it isn't already playing.
1673         m_player->setRate(m_playbackRate);
1674         m_player->play();
1675         startPlaybackProgressTimer();
1676         m_playing = true;
1677     } else if (!shouldBePlaying && !playerPaused) {
1678         m_player->pause();
1679         m_playbackProgressTimer.stop();
1680         m_playing = false;
1681         float time = currentTime();
1682         if (time > m_lastSeekTime)
1683             addPlayedRange(m_lastSeekTime, time);
1684     } else if (couldPlayIfEnoughData() && playerPaused)
1685         m_player->prepareToPlay();
1686 
1687     if (renderer())
1688         renderer()->updateFromElement();
1689 }
1690 
setPausedInternal(bool b)1691 void HTMLMediaElement::setPausedInternal(bool b)
1692 {
1693     m_pausedInternal = b;
1694     updatePlayState();
1695 }
1696 
stopPeriodicTimers()1697 void HTMLMediaElement::stopPeriodicTimers()
1698 {
1699     m_progressEventTimer.stop();
1700     m_playbackProgressTimer.stop();
1701 }
1702 
userCancelledLoad()1703 void HTMLMediaElement::userCancelledLoad()
1704 {
1705     if (m_networkState == NETWORK_EMPTY || m_networkState >= NETWORK_LOADED)
1706         return;
1707 
1708     // If the media data fetching process is aborted by the user:
1709 
1710     // 1 - The user agent should cancel the fetching process.
1711 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
1712     m_player.clear();
1713 #endif
1714     stopPeriodicTimers();
1715 
1716     // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED.
1717     m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED);
1718 
1719     // 3 - Queue a task to fire a progress event called abort at the media element, in the context
1720     // of the fetching process started by this instance of this algorithm.
1721     scheduleEvent(eventNames().abortEvent);
1722 
1723     // 5 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
1724     // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a
1725     // simple event called emptied at the element. Otherwise, set set the element's networkState
1726     // attribute to the NETWORK_IDLE value.
1727     if (m_readyState == HAVE_NOTHING) {
1728         m_networkState = NETWORK_EMPTY;
1729         scheduleEvent(eventNames().emptiedEvent);
1730     }
1731     else
1732         m_networkState = NETWORK_IDLE;
1733 
1734     // 6 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1735     m_delayingTheLoadEvent = false;
1736 
1737     // 7 - Abort the overall resource selection algorithm.
1738     m_currentSourceNode = 0;
1739 }
1740 
documentWillBecomeInactive()1741 void HTMLMediaElement::documentWillBecomeInactive()
1742 {
1743     if (m_isFullscreen)
1744         exitFullscreen();
1745 
1746     m_inActiveDocument = false;
1747     userCancelledLoad();
1748 
1749     // Stop the playback without generating events
1750     setPausedInternal(true);
1751 
1752     if (renderer())
1753         renderer()->updateFromElement();
1754 
1755     stopPeriodicTimers();
1756     cancelPendingEventsAndCallbacks();
1757 }
1758 
documentDidBecomeActive()1759 void HTMLMediaElement::documentDidBecomeActive()
1760 {
1761     m_inActiveDocument = true;
1762     setPausedInternal(false);
1763 
1764     if (m_error && m_error->code() == MediaError::MEDIA_ERR_ABORTED) {
1765         // Restart the load if it was aborted in the middle by moving the document to the page cache.
1766         // m_error is only left at MEDIA_ERR_ABORTED when the document becomes inactive (it is set to
1767         //  MEDIA_ERR_ABORTED while the abortEvent is being sent, but cleared immediately afterwards).
1768         // This behavior is not specified but it seems like a sensible thing to do.
1769         ExceptionCode ec;
1770         load(processingUserGesture(), ec);
1771     }
1772 
1773     if (renderer())
1774         renderer()->updateFromElement();
1775 }
1776 
mediaVolumeDidChange()1777 void HTMLMediaElement::mediaVolumeDidChange()
1778 {
1779     updateVolume();
1780 }
1781 
screenRect()1782 const IntRect HTMLMediaElement::screenRect()
1783 {
1784     IntRect elementRect;
1785     if (renderer())
1786         elementRect = renderer()->view()->frameView()->contentsToScreen(renderer()->absoluteBoundingBoxRect());
1787     return elementRect;
1788 }
1789 
defaultEventHandler(Event * event)1790 void HTMLMediaElement::defaultEventHandler(Event* event)
1791 {
1792 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
1793     RenderObject* r = renderer();
1794     if (!r || !r->isWidget())
1795         return;
1796 
1797     Widget* widget = toRenderWidget(r)->widget();
1798     if (widget)
1799         widget->handleEvent(event);
1800 #else
1801     if (renderer() && renderer()->isMedia())
1802         toRenderMedia(renderer())->forwardEvent(event);
1803     if (event->defaultHandled())
1804         return;
1805     HTMLElement::defaultEventHandler(event);
1806 #endif
1807 }
1808 
processingUserGesture() const1809 bool HTMLMediaElement::processingUserGesture() const
1810 {
1811     Frame* frame = document()->frame();
1812     FrameLoader* loader = frame ? frame->loader() : 0;
1813 
1814     // return 'true' for safety if we don't know the answer
1815     return loader ? loader->isProcessingUserGesture() : true;
1816 }
1817 
1818 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
1819 
deliverNotification(MediaPlayerProxyNotificationType notification)1820 void HTMLMediaElement::deliverNotification(MediaPlayerProxyNotificationType notification)
1821 {
1822     if (notification == MediaPlayerNotificationPlayPauseButtonPressed) {
1823         togglePlayState();
1824         return;
1825     }
1826 
1827     if (m_player)
1828         m_player->deliverNotification(notification);
1829 }
1830 
setMediaPlayerProxy(WebMediaPlayerProxy * proxy)1831 void HTMLMediaElement::setMediaPlayerProxy(WebMediaPlayerProxy* proxy)
1832 {
1833     if (m_player)
1834         m_player->setMediaPlayerProxy(proxy);
1835 }
1836 
initialURL()1837 String HTMLMediaElement::initialURL()
1838 {
1839     KURL initialSrc = document()->completeURL(getAttribute(srcAttr));
1840 
1841     if (!initialSrc.isValid())
1842         initialSrc = selectNextSourceChild(0, DoNothing);
1843 
1844     m_currentSrc = initialSrc.string();
1845 
1846     return initialSrc;
1847 }
1848 
finishParsingChildren()1849 void HTMLMediaElement::finishParsingChildren()
1850 {
1851     HTMLElement::finishParsingChildren();
1852     if (!m_player)
1853         m_player = MediaPlayer::create(this);
1854 
1855     document()->updateStyleIfNeeded();
1856     if (m_needWidgetUpdate && renderer())
1857         toRenderEmbeddedObject(renderer())->updateWidget(true);
1858 }
1859 
1860 #endif
1861 
enterFullscreen()1862 void HTMLMediaElement::enterFullscreen()
1863 {
1864     ASSERT(!m_isFullscreen);
1865     if (document() && document()->page()) {
1866         document()->page()->chrome()->client()->enterFullscreenForNode(this);
1867         scheduleEvent(eventNames().webkitbeginfullscreenEvent);
1868         m_isFullscreen = true;
1869     }
1870 }
1871 
exitFullscreen()1872 void HTMLMediaElement::exitFullscreen()
1873 {
1874     ASSERT(m_isFullscreen);
1875     if (document() && document()->page()) {
1876         document()->page()->chrome()->client()->exitFullscreenForNode(this);
1877         scheduleEvent(eventNames().webkitendfullscreenEvent);
1878     }
1879     m_isFullscreen = false;
1880 }
1881 
platformMedia() const1882 PlatformMedia HTMLMediaElement::platformMedia() const
1883 {
1884     return m_player ? m_player->platformMedia() : NoPlatformMedia;
1885 }
1886 
hasClosedCaptions() const1887 bool HTMLMediaElement::hasClosedCaptions() const
1888 {
1889     return m_player && m_player->hasClosedCaptions();
1890 }
1891 
closedCaptionsVisible() const1892 bool HTMLMediaElement::closedCaptionsVisible() const
1893 {
1894     return m_closedCaptionsVisible;
1895 }
1896 
setClosedCaptionsVisible(bool closedCaptionVisible)1897 void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
1898 {
1899     if (!m_player ||!hasClosedCaptions())
1900         return;
1901 
1902     m_closedCaptionsVisible = closedCaptionVisible;
1903     m_player->setClosedCaptionsVisible(closedCaptionVisible);
1904     if (renderer())
1905         renderer()->updateFromElement();
1906 }
1907 
setWebkitClosedCaptionsVisible(bool visible)1908 void HTMLMediaElement::setWebkitClosedCaptionsVisible(bool visible)
1909 {
1910     setClosedCaptionsVisible(visible);
1911 }
1912 
webkitClosedCaptionsVisible() const1913 bool HTMLMediaElement::webkitClosedCaptionsVisible() const
1914 {
1915     return closedCaptionsVisible();
1916 }
1917 
1918 
webkitHasClosedCaptions() const1919 bool HTMLMediaElement::webkitHasClosedCaptions() const
1920 {
1921     return hasClosedCaptions();
1922 }
1923 
1924 }
1925 
1926 #endif
1927