Upstream version 11.39.250.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / shadow / MediaControls.cpp
index 45559d9..778f1d1 100644 (file)
 #include "config.h"
 #include "core/html/shadow/MediaControls.h"
 
-#include "bindings/v8/ExceptionStatePlaceholder.h"
-
-namespace WebCore {
-
-static const double timeWithoutMouseMovementBeforeHidingFullscreenControls = 3;
-
-MediaControls::MediaControls(Document& document)
-    : HTMLDivElement(HTMLNames::divTag, document)
-    , m_mediaController(0)
-    , m_panel(0)
-    , m_textDisplayContainer(0)
-    , m_playButton(0)
-    , m_currentTimeDisplay(0)
-    , m_timeline(0)
-    , m_panelMuteButton(0)
-    , m_volumeSlider(0)
-    , m_toggleClosedCaptionsButton(0)
-    , m_fullScreenButton(0)
-    , m_hideFullscreenControlsTimer(this, &MediaControls::hideFullscreenControlsTimerFired)
-    , m_isFullscreen(false)
+#include "bindings/core/v8/ExceptionStatePlaceholder.h"
+#include "core/dom/ClientRect.h"
+#include "core/events/MouseEvent.h"
+#include "core/frame/Settings.h"
+#include "core/html/HTMLMediaElement.h"
+#include "core/html/MediaController.h"
+#include "core/rendering/RenderTheme.h"
+
+namespace blink {
+
+// If you change this value, then also update the corresponding value in
+// LayoutTests/media/media-controls.js.
+static const double timeWithoutMouseMovementBeforeHidingMediaControls = 3;
+
+static bool fullscreenIsSupported(const Document& document)
+{
+    return !document.settings() || document.settings()->fullscreenSupported();
+}
+
+MediaControls::MediaControls(HTMLMediaElement& mediaElement)
+    : HTMLDivElement(mediaElement.document())
+    , m_mediaElement(&mediaElement)
+    , m_panel(nullptr)
+    , m_textDisplayContainer(nullptr)
+    , m_overlayPlayButton(nullptr)
+    , m_overlayEnclosure(nullptr)
+    , m_playButton(nullptr)
+    , m_currentTimeDisplay(nullptr)
+    , m_timeline(nullptr)
+    , m_muteButton(nullptr)
+    , m_volumeSlider(nullptr)
+    , m_toggleClosedCaptionsButton(nullptr)
+    , m_fullScreenButton(nullptr)
+    , m_castButton(nullptr)
+    , m_overlayCastButton(nullptr)
+    , m_durationDisplay(nullptr)
+    , m_enclosure(nullptr)
+    , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
     , m_isMouseOverControls(false)
+    , m_isPausedForScrubbing(false)
+    , m_wasLastEventTouch(false)
 {
 }
 
-void MediaControls::setMediaController(MediaControllerInterface* controller)
+PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& mediaElement)
 {
-    if (m_mediaController == controller)
-        return;
-    m_mediaController = controller;
+    RefPtrWillBeRawPtr<MediaControls> controls = adoptRefWillBeNoop(new MediaControls(mediaElement));
 
-    if (m_panel)
-        m_panel->setMediaController(controller);
-    if (m_textDisplayContainer)
-        m_textDisplayContainer->setMediaController(controller);
-    if (m_playButton)
-        m_playButton->setMediaController(controller);
-    if (m_currentTimeDisplay)
-        m_currentTimeDisplay->setMediaController(controller);
-    if (m_timeline)
-        m_timeline->setMediaController(controller);
-    if (m_panelMuteButton)
-        m_panelMuteButton->setMediaController(controller);
-    if (m_volumeSlider)
-        m_volumeSlider->setMediaController(controller);
-    if (m_toggleClosedCaptionsButton)
-        m_toggleClosedCaptionsButton->setMediaController(controller);
-    if (m_fullScreenButton)
-        m_fullScreenButton->setMediaController(controller);
+    if (controls->initializeControls())
+        return controls.release();
+
+    return nullptr;
 }
 
-void MediaControls::reset()
+bool MediaControls::initializeControls()
 {
-    Page* page = document().page();
-    if (!page)
-        return;
-
-    m_playButton->updateDisplayType();
+    TrackExceptionState exceptionState;
 
-    updateCurrentTimeDisplay();
+    RefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> overlayEnclosure = MediaControlOverlayEnclosureElement::create(*this);
 
-    double duration = m_mediaController->duration();
-    if (std::isfinite(duration) || RenderTheme::theme().hasOwnDisabledStateHandlingFor(MediaSliderPart)) {
-        m_timeline->setDuration(duration);
-        m_timeline->setPosition(m_mediaController->currentTime());
+    if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
+        RefPtrWillBeRawPtr<MediaControlOverlayPlayButtonElement> overlayPlayButton = MediaControlOverlayPlayButtonElement::create(*this);
+        m_overlayPlayButton = overlayPlayButton.get();
+        overlayEnclosure->appendChild(overlayPlayButton.release(), exceptionState);
+        if (exceptionState.hadException())
+            return false;
     }
 
-    if (m_mediaController->hasAudio() || RenderTheme::theme().hasOwnDisabledStateHandlingFor(MediaMuteButtonPart))
-        m_panelMuteButton->show();
-    else
-        m_panelMuteButton->hide();
-
-    if (m_volumeSlider) {
-        if (!m_mediaController->hasAudio())
-            m_volumeSlider->hide();
-        else {
-            m_volumeSlider->show();
-            m_volumeSlider->setVolume(m_mediaController->volume());
-        }
-    }
+    RefPtrWillBeRawPtr<MediaControlCastButtonElement> overlayCastButton = MediaControlCastButtonElement::create(*this, true);
+    m_overlayCastButton = overlayCastButton.get();
+    overlayEnclosure->appendChild(overlayCastButton.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
 
-    refreshClosedCaptionsButtonVisibility();
+    m_overlayEnclosure = overlayEnclosure.get();
+    appendChild(overlayEnclosure.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
 
-    if (m_fullScreenButton) {
-        if (m_mediaController->supportsFullscreen() && m_mediaController->hasVideo())
-            m_fullScreenButton->show();
-        else
-            m_fullScreenButton->hide();
-    }
+    // Create an enclosing element for the panel so we can visually offset the controls correctly.
+    RefPtrWillBeRawPtr<MediaControlPanelEnclosureElement> enclosure = MediaControlPanelEnclosureElement::create(*this);
 
-    makeOpaque();
+    RefPtrWillBeRawPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(*this);
+
+    RefPtrWillBeRawPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(*this);
+    m_playButton = playButton.get();
+    panel->appendChild(playButton.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this);
+    m_timeline = timeline.get();
+    panel->appendChild(timeline.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    RefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(*this);
+    m_currentTimeDisplay = currentTimeDisplay.get();
+    m_currentTimeDisplay->hide();
+    panel->appendChild(currentTimeDisplay.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    RefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(*this);
+    m_durationDisplay = durationDisplay.get();
+    panel->appendChild(durationDisplay.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    RefPtrWillBeRawPtr<MediaControlMuteButtonElement> muteButton = MediaControlMuteButtonElement::create(*this);
+    m_muteButton = muteButton.get();
+    panel->appendChild(muteButton.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this);
+    m_volumeSlider = slider.get();
+    panel->appendChild(slider.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this);
+    m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
+    panel->appendChild(toggleClosedCaptionsButton.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    RefPtrWillBeRawPtr<MediaControlCastButtonElement> castButton = MediaControlCastButtonElement::create(*this, false);
+    m_castButton = castButton.get();
+    panel->appendChild(castButton.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(*this);
+    m_fullScreenButton = fullscreenButton.get();
+    panel->appendChild(fullscreenButton.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    m_panel = panel.get();
+    enclosure->appendChild(panel.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    m_enclosure = enclosure.get();
+    appendChild(enclosure.release(), exceptionState);
+    if (exceptionState.hadException())
+        return false;
+
+    return true;
 }
 
-void MediaControls::reportedError()
+void MediaControls::reset()
 {
-    Page* page = document().page();
-    if (!page)
-        return;
+    double duration = mediaElement().duration();
+    m_durationDisplay->setInnerText(RenderTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION);
+    m_durationDisplay->setCurrentValue(duration);
+
+    updatePlayState();
 
-    if (!RenderTheme::theme().hasOwnDisabledStateHandlingFor(MediaMuteButtonPart)) {
-        m_panelMuteButton->hide();
+    updateCurrentTimeDisplay();
+
+    m_timeline->setDuration(duration);
+    m_timeline->setPosition(mediaElement().currentTime());
+
+    if (!mediaElement().hasAudio())
         m_volumeSlider->hide();
-    }
+    else
+        m_volumeSlider->show();
+    updateVolume();
 
-    if (m_toggleClosedCaptionsButton && !RenderTheme::theme().hasOwnDisabledStateHandlingFor(MediaToggleClosedCaptionsButtonPart))
-        m_toggleClosedCaptionsButton->hide();
+    refreshClosedCaptionsButtonVisibility();
 
-    if (m_fullScreenButton && !RenderTheme::theme().hasOwnDisabledStateHandlingFor(MediaEnterFullscreenButtonPart))
+    if (mediaElement().hasVideo() && fullscreenIsSupported(document()))
+        m_fullScreenButton->show();
+    else
         m_fullScreenButton->hide();
-}
 
-void MediaControls::loadedMetadata()
-{
-    reset();
+    refreshCastButtonVisibility();
+    makeOpaque();
 }
 
 void MediaControls::show()
@@ -147,12 +214,22 @@ void MediaControls::show()
     makeOpaque();
     m_panel->setIsDisplayed(true);
     m_panel->show();
+    if (m_overlayPlayButton)
+        m_overlayPlayButton->updateDisplayType();
+}
+
+void MediaControls::mediaElementFocused()
+{
+    show();
+    resetHideMediaControlsTimer();
 }
 
 void MediaControls::hide()
 {
     m_panel->setIsDisplayed(false);
     m_panel->hide();
+    if (m_overlayPlayButton)
+        m_overlayPlayButton->hide();
 }
 
 void MediaControls::makeOpaque()
@@ -165,116 +242,202 @@ void MediaControls::makeTransparent()
     m_panel->makeTransparent();
 }
 
-bool MediaControls::shouldHideControls()
-{
-    return !m_panel->hovered();
-}
-
-void MediaControls::bufferingProgressed()
+bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
 {
-    // We only need to update buffering progress when paused, during normal
-    // playback playbackProgressed() will take care of it.
-    if (m_mediaController->paused())
-        m_timeline->setPosition(m_mediaController->currentTime());
+    // Never hide for a media element without visual representation.
+    if (!mediaElement().hasVideo() || mediaElement().isPlayingRemotely())
+        return false;
+    // Don't hide if the mouse is over the controls.
+    const bool ignoreControlsHover = behaviorFlags & IgnoreControlsHover;
+    if (!ignoreControlsHover && m_panel->hovered())
+        return false;
+    // Don't hide if the mouse is over the video area.
+    const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover;
+    if (!ignoreVideoHover && m_isMouseOverControls)
+        return false;
+    // Don't hide if focus is on the HTMLMediaElement or within the
+    // controls/shadow tree. (Perform the checks separately to avoid going
+    // through all the potential ancestor hosts for the focused element.)
+    const bool ignoreFocus = behaviorFlags & IgnoreFocus;
+    if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement())))
+        return false;
+    return true;
 }
 
 void MediaControls::playbackStarted()
 {
-    m_playButton->updateDisplayType();
-    m_timeline->setPosition(m_mediaController->currentTime());
+    m_currentTimeDisplay->show();
+    m_durationDisplay->hide();
+
+    updatePlayState();
+    m_timeline->setPosition(mediaElement().currentTime());
     updateCurrentTimeDisplay();
 
-    if (m_isFullscreen)
-        startHideFullscreenControlsTimer();
+    startHideMediaControlsTimer();
 }
 
 void MediaControls::playbackProgressed()
 {
-    m_timeline->setPosition(m_mediaController->currentTime());
+    m_timeline->setPosition(mediaElement().currentTime());
     updateCurrentTimeDisplay();
 
-    if (!m_isMouseOverControls && m_mediaController->hasVideo())
+    if (shouldHideMediaControls())
         makeTransparent();
 }
 
 void MediaControls::playbackStopped()
 {
-    m_playButton->updateDisplayType();
-    m_timeline->setPosition(m_mediaController->currentTime());
+    updatePlayState();
+    m_timeline->setPosition(mediaElement().currentTime());
     updateCurrentTimeDisplay();
     makeOpaque();
 
-    stopHideFullscreenControlsTimer();
+    stopHideMediaControlsTimer();
 }
 
-void MediaControls::showVolumeSlider()
+void MediaControls::updatePlayState()
 {
-    if (!m_mediaController->hasAudio())
+    if (m_isPausedForScrubbing)
         return;
 
-    m_volumeSlider->show();
+    if (m_overlayPlayButton)
+        m_overlayPlayButton->updateDisplayType();
+    m_playButton->updateDisplayType();
+}
+
+void MediaControls::beginScrubbing()
+{
+    if (!mediaElement().togglePlayStateWillPlay()) {
+        m_isPausedForScrubbing = true;
+        mediaElement().togglePlayState();
+    }
 }
 
-void MediaControls::changedMute()
+void MediaControls::endScrubbing()
 {
-    m_panelMuteButton->changedMute();
+    if (m_isPausedForScrubbing) {
+        m_isPausedForScrubbing = false;
+        if (mediaElement().togglePlayStateWillPlay())
+            mediaElement().togglePlayState();
+    }
+}
+
+void MediaControls::updateCurrentTimeDisplay()
+{
+    double now = mediaElement().currentTime();
+    double duration = mediaElement().duration();
+
+    // After seek, hide duration display and show current time.
+    if (now > 0) {
+        m_currentTimeDisplay->show();
+        m_durationDisplay->hide();
+    }
+
+    // Allow the theme to format the time.
+    m_currentTimeDisplay->setInnerText(RenderTheme::theme().formatMediaControlsCurrentTime(now, duration), IGNORE_EXCEPTION);
+    m_currentTimeDisplay->setCurrentValue(now);
 }
 
-void MediaControls::changedVolume()
+void MediaControls::updateVolume()
 {
-    if (m_volumeSlider)
-        m_volumeSlider->setVolume(m_mediaController->volume());
-    if (m_panelMuteButton && m_panelMuteButton->renderer())
-        m_panelMuteButton->renderer()->repaint();
+    m_muteButton->updateDisplayType();
+    if (m_muteButton->renderer())
+        m_muteButton->renderer()->setShouldDoFullPaintInvalidation(true);
+
+    if (mediaElement().muted())
+        m_volumeSlider->setVolume(0);
+    else
+        m_volumeSlider->setVolume(mediaElement().volume());
 }
 
 void MediaControls::changedClosedCaptionsVisibility()
 {
-    if (m_toggleClosedCaptionsButton)
-        m_toggleClosedCaptionsButton->updateDisplayType();
+    m_toggleClosedCaptionsButton->updateDisplayType();
 }
 
 void MediaControls::refreshClosedCaptionsButtonVisibility()
 {
-    if (!m_toggleClosedCaptionsButton)
-        return;
-
-    if (m_mediaController->hasClosedCaptions())
+    if (mediaElement().hasClosedCaptions())
         m_toggleClosedCaptionsButton->show();
     else
         m_toggleClosedCaptionsButton->hide();
 }
 
-void MediaControls::closedCaptionTracksChanged()
+void MediaControls::textTracksChanged()
 {
     refreshClosedCaptionsButtonVisibility();
 }
 
+void MediaControls::refreshCastButtonVisibility()
+{
+    if (mediaElement().hasRemoteRoutes()) {
+        // The reason for the autoplay test is that some pages (e.g. vimeo.com) have an autoplay background video, which
+        // doesn't autoplay on Chrome for Android (we prevent it) so starts paused. In such cases we don't want to automatically
+        // show the cast button, since it looks strange and is unlikely to correspond with anything the user wants to do.
+        // If a user does want to cast a paused autoplay video then they can still do so by touching or clicking on the
+        // video, which will cause the cast button to appear.
+        if (!mediaElement().shouldShowControls() && !mediaElement().autoplay() && mediaElement().paused()) {
+            showOverlayCastButton();
+        } else if (mediaElement().shouldShowControls()) {
+            m_overlayCastButton->hide();
+            m_castButton->show();
+            // Check that the cast button actually fits on the bar.
+            if (m_fullScreenButton->getBoundingClientRect()->right() > m_panel->getBoundingClientRect()->right()) {
+                m_castButton->hide();
+                m_overlayCastButton->show();
+            }
+        }
+    } else {
+        m_castButton->hide();
+        m_overlayCastButton->hide();
+    }
+}
+
+void MediaControls::showOverlayCastButton()
+{
+    m_overlayCastButton->show();
+    resetHideMediaControlsTimer();
+}
+
 void MediaControls::enteredFullscreen()
 {
-    m_isFullscreen = true;
     m_fullScreenButton->setIsFullscreen(true);
-    startHideFullscreenControlsTimer();
+    stopHideMediaControlsTimer();
+    startHideMediaControlsTimer();
 }
 
 void MediaControls::exitedFullscreen()
 {
-    m_isFullscreen = false;
     m_fullScreenButton->setIsFullscreen(false);
-    stopHideFullscreenControlsTimer();
+    stopHideMediaControlsTimer();
+    startHideMediaControlsTimer();
+}
+
+void MediaControls::startedCasting()
+{
+    m_castButton->setIsPlayingRemotely(true);
+    m_overlayCastButton->setIsPlayingRemotely(true);
+}
+
+void MediaControls::stoppedCasting()
+{
+    m_castButton->setIsPlayingRemotely(false);
+    m_overlayCastButton->setIsPlayingRemotely(false);
 }
 
 void MediaControls::defaultEventHandler(Event* event)
 {
     HTMLDivElement::defaultEventHandler(event);
+    m_wasLastEventTouch = event->isTouchEvent() || event->isGestureEvent()
+        || (event->isMouseEvent() && toMouseEvent(event)->fromTouch());
 
     if (event->type() == EventTypeNames::mouseover) {
         if (!containsRelatedTarget(event)) {
             m_isMouseOverControls = true;
-            if (!m_mediaController->canPlay()) {
+            if (!mediaElement().togglePlayStateWillPlay()) {
                 makeOpaque();
-                if (shouldHideControls())
-                    startHideFullscreenControlsTimer();
+                if (shouldHideMediaControls())
+                    startHideMediaControlsTimer();
             }
         }
         return;
@@ -283,57 +446,59 @@ void MediaControls::defaultEventHandler(Event* event)
     if (event->type() == EventTypeNames::mouseout) {
         if (!containsRelatedTarget(event)) {
             m_isMouseOverControls = false;
-            stopHideFullscreenControlsTimer();
+            stopHideMediaControlsTimer();
         }
         return;
     }
 
     if (event->type() == EventTypeNames::mousemove) {
-        if (m_isFullscreen) {
-            // When we get a mouse move in fullscreen mode, show the media controls, and start a timer
-            // that will hide the media controls after a 3 seconds without a mouse move.
-            makeOpaque();
-            if (shouldHideControls())
-                startHideFullscreenControlsTimer();
-        }
+        // When we get a mouse move, show the media controls, and start a timer
+        // that will hide the media controls after a 3 seconds without a mouse move.
+        makeOpaque();
+        refreshCastButtonVisibility();
+        if (shouldHideMediaControls(IgnoreVideoHover))
+            startHideMediaControlsTimer();
         return;
     }
 }
 
-void MediaControls::hideFullscreenControlsTimerFired(Timer<MediaControls>*)
+void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*)
 {
-    if (m_mediaController->paused())
+    if (mediaElement().togglePlayStateWillPlay())
         return;
 
-    if (!m_isFullscreen)
-        return;
-
-    if (!shouldHideControls())
+    unsigned behaviorFlags = IgnoreFocus | IgnoreVideoHover;
+    if (m_wasLastEventTouch) {
+        behaviorFlags |= IgnoreControlsHover;
+    }
+    if (!shouldHideMediaControls(behaviorFlags))
         return;
 
     makeTransparent();
+    m_overlayCastButton->hide();
 }
 
-void MediaControls::startHideFullscreenControlsTimer()
+void MediaControls::startHideMediaControlsTimer()
 {
-    if (!m_isFullscreen)
-        return;
-
-    Page* page = document().page();
-    if (!page)
-        return;
+    m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
+}
 
-    m_hideFullscreenControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingFullscreenControls);
+void MediaControls::stopHideMediaControlsTimer()
+{
+    m_hideMediaControlsTimer.stop();
 }
 
-void MediaControls::stopHideFullscreenControlsTimer()
+void MediaControls::resetHideMediaControlsTimer()
 {
-    m_hideFullscreenControlsTimer.stop();
+    stopHideMediaControlsTimer();
+    if (!mediaElement().paused())
+        startHideMediaControlsTimer();
 }
 
-const AtomicString& MediaControls::part() const
+
+const AtomicString& MediaControls::shadowPseudoId() const
 {
-    DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls"));
+    DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls", AtomicString::ConstructFromLiteral));
     return id;
 }
 
@@ -352,14 +517,14 @@ void MediaControls::createTextTrackDisplay()
     if (m_textDisplayContainer)
         return;
 
-    RefPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(document());
+    RefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(*this);
     m_textDisplayContainer = textDisplayContainer.get();
 
-    if (m_mediaController)
-        m_textDisplayContainer->setMediaController(m_mediaController);
-
-    // Insert it before the first controller element so it always displays behind the controls.
-    insertBefore(textDisplayContainer.release(), m_panel, IGNORE_EXCEPTION);
+    // Insert it before (behind) all other control elements.
+    if (m_overlayPlayButton)
+        m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayPlayButton);
+    else
+        m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayCastButton);
 }
 
 void MediaControls::showTextTrackDisplay()
@@ -384,4 +549,25 @@ void MediaControls::updateTextTrackDisplay()
     m_textDisplayContainer->updateDisplay();
 }
 
+void MediaControls::trace(Visitor* visitor)
+{
+    visitor->trace(m_mediaElement);
+    visitor->trace(m_panel);
+    visitor->trace(m_textDisplayContainer);
+    visitor->trace(m_overlayPlayButton);
+    visitor->trace(m_overlayEnclosure);
+    visitor->trace(m_playButton);
+    visitor->trace(m_currentTimeDisplay);
+    visitor->trace(m_timeline);
+    visitor->trace(m_muteButton);
+    visitor->trace(m_volumeSlider);
+    visitor->trace(m_toggleClosedCaptionsButton);
+    visitor->trace(m_fullScreenButton);
+    visitor->trace(m_durationDisplay);
+    visitor->trace(m_enclosure);
+    visitor->trace(m_castButton);
+    visitor->trace(m_overlayCastButton);
+    HTMLDivElement::trace(visitor);
+}
+
 }