2 * Copyright (C) 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "core/html/shadow/MediaControlElements.h"
33 #include "RuntimeEnabledFeatures.h"
34 #include "bindings/v8/ExceptionStatePlaceholder.h"
35 #include "core/dom/DOMTokenList.h"
36 #include "core/dom/FullscreenElementStack.h"
37 #include "core/dom/shadow/ShadowRoot.h"
38 #include "core/events/MouseEvent.h"
39 #include "core/frame/LocalFrame.h"
40 #include "core/html/HTMLVideoElement.h"
41 #include "core/html/shadow/MediaControls.h"
42 #include "core/html/track/TextTrack.h"
43 #include "core/html/track/vtt/VTTRegionList.h"
44 #include "core/page/EventHandler.h"
45 #include "core/rendering/RenderMediaControlElements.h"
46 #include "core/rendering/RenderSlider.h"
47 #include "core/rendering/RenderTheme.h"
48 #include "core/rendering/RenderVideo.h"
52 using namespace HTMLNames;
54 static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId();
55 static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId();
57 static const double fadeInDuration = 0.1;
58 static const double fadeOutDuration = 0.3;
60 MediaControlPanelElement::MediaControlPanelElement(MediaControls& mediaControls)
61 : MediaControlDivElement(mediaControls, MediaControlsPanel)
62 , m_isDisplayed(false)
64 , m_transitionTimer(this, &MediaControlPanelElement::transitionTimerFired)
68 PassRefPtr<MediaControlPanelElement> MediaControlPanelElement::create(MediaControls& mediaControls)
70 return adoptRef(new MediaControlPanelElement(mediaControls));
73 const AtomicString& MediaControlPanelElement::shadowPseudoId() const
75 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-panel", AtomicString::ConstructFromLiteral));
79 void MediaControlPanelElement::defaultEventHandler(Event* event)
81 // Suppress the media element activation behavior (toggle play/pause) when
82 // any part of the control panel is clicked.
83 if (event->type() == EventTypeNames::click) {
84 event->setDefaultHandled();
87 HTMLDivElement::defaultEventHandler(event);
90 void MediaControlPanelElement::startTimer()
94 // The timer is required to set the property display:'none' on the panel,
95 // such that captions are correctly displayed at the bottom of the video
96 // at the end of the fadeout transition.
97 // FIXME: Racing a transition with a setTimeout like this is wrong.
98 m_transitionTimer.startOneShot(fadeOutDuration, FROM_HERE);
101 void MediaControlPanelElement::stopTimer()
103 if (m_transitionTimer.isActive())
104 m_transitionTimer.stop();
107 void MediaControlPanelElement::transitionTimerFired(Timer<MediaControlPanelElement>*)
115 void MediaControlPanelElement::makeOpaque()
120 setInlineStyleProperty(CSSPropertyTransitionProperty, CSSPropertyOpacity);
121 setInlineStyleProperty(CSSPropertyTransitionDuration, fadeInDuration, CSSPrimitiveValue::CSS_S);
122 setInlineStyleProperty(CSSPropertyOpacity, 1.0, CSSPrimitiveValue::CSS_NUMBER);
130 void MediaControlPanelElement::makeTransparent()
135 setInlineStyleProperty(CSSPropertyTransitionProperty, CSSPropertyOpacity);
136 setInlineStyleProperty(CSSPropertyTransitionDuration, fadeOutDuration, CSSPrimitiveValue::CSS_S);
137 setInlineStyleProperty(CSSPropertyOpacity, 0.0, CSSPrimitiveValue::CSS_NUMBER);
143 void MediaControlPanelElement::setIsDisplayed(bool isDisplayed)
145 m_isDisplayed = isDisplayed;
148 // ----------------------------
150 MediaControlPanelEnclosureElement::MediaControlPanelEnclosureElement(MediaControls& mediaControls)
151 // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
152 : MediaControlDivElement(mediaControls, MediaControlsPanel)
156 PassRefPtr<MediaControlPanelEnclosureElement> MediaControlPanelEnclosureElement::create(MediaControls& mediaControls)
158 return adoptRef(new MediaControlPanelEnclosureElement(mediaControls));
161 const AtomicString& MediaControlPanelEnclosureElement::shadowPseudoId() const
163 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-enclosure", AtomicString::ConstructFromLiteral));
167 // ----------------------------
169 MediaControlOverlayEnclosureElement::MediaControlOverlayEnclosureElement(MediaControls& mediaControls)
170 // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
171 : MediaControlDivElement(mediaControls, MediaControlsPanel)
175 PassRefPtr<MediaControlOverlayEnclosureElement> MediaControlOverlayEnclosureElement::create(MediaControls& mediaControls)
177 return adoptRef(new MediaControlOverlayEnclosureElement(mediaControls));
180 const AtomicString& MediaControlOverlayEnclosureElement::shadowPseudoId() const
182 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-overlay-enclosure", AtomicString::ConstructFromLiteral));
186 // ----------------------------
188 MediaControlMuteButtonElement::MediaControlMuteButtonElement(MediaControls& mediaControls)
189 : MediaControlInputElement(mediaControls, MediaMuteButton)
193 PassRefPtr<MediaControlMuteButtonElement> MediaControlMuteButtonElement::create(MediaControls& mediaControls)
195 RefPtr<MediaControlMuteButtonElement> button = adoptRef(new MediaControlMuteButtonElement(mediaControls));
196 button->ensureUserAgentShadowRoot();
197 button->setType("button");
198 return button.release();
201 void MediaControlMuteButtonElement::defaultEventHandler(Event* event)
203 if (event->type() == EventTypeNames::click) {
204 mediaControllerInterface().setMuted(!mediaControllerInterface().muted());
205 event->setDefaultHandled();
208 HTMLInputElement::defaultEventHandler(event);
211 void MediaControlMuteButtonElement::updateDisplayType()
213 setDisplayType(mediaControllerInterface().muted() ? MediaUnMuteButton : MediaMuteButton);
216 const AtomicString& MediaControlMuteButtonElement::shadowPseudoId() const
218 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-mute-button", AtomicString::ConstructFromLiteral));
222 // ----------------------------
224 MediaControlPlayButtonElement::MediaControlPlayButtonElement(MediaControls& mediaControls)
225 : MediaControlInputElement(mediaControls, MediaPlayButton)
229 PassRefPtr<MediaControlPlayButtonElement> MediaControlPlayButtonElement::create(MediaControls& mediaControls)
231 RefPtr<MediaControlPlayButtonElement> button = adoptRef(new MediaControlPlayButtonElement(mediaControls));
232 button->ensureUserAgentShadowRoot();
233 button->setType("button");
234 return button.release();
237 void MediaControlPlayButtonElement::defaultEventHandler(Event* event)
239 if (event->type() == EventTypeNames::click) {
240 mediaElement().togglePlayState();
242 event->setDefaultHandled();
244 HTMLInputElement::defaultEventHandler(event);
247 void MediaControlPlayButtonElement::updateDisplayType()
249 setDisplayType(mediaElement().togglePlayStateWillPlay() ? MediaPlayButton : MediaPauseButton);
252 const AtomicString& MediaControlPlayButtonElement::shadowPseudoId() const
254 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-play-button", AtomicString::ConstructFromLiteral));
258 // ----------------------------
260 MediaControlOverlayPlayButtonElement::MediaControlOverlayPlayButtonElement(MediaControls& mediaControls)
261 : MediaControlInputElement(mediaControls, MediaOverlayPlayButton)
265 PassRefPtr<MediaControlOverlayPlayButtonElement> MediaControlOverlayPlayButtonElement::create(MediaControls& mediaControls)
267 RefPtr<MediaControlOverlayPlayButtonElement> button = adoptRef(new MediaControlOverlayPlayButtonElement(mediaControls));
268 button->ensureUserAgentShadowRoot();
269 button->setType("button");
270 return button.release();
273 void MediaControlOverlayPlayButtonElement::defaultEventHandler(Event* event)
275 if (event->type() == EventTypeNames::click && mediaElement().togglePlayStateWillPlay()) {
276 mediaElement().togglePlayState();
278 event->setDefaultHandled();
280 HTMLInputElement::defaultEventHandler(event);
283 void MediaControlOverlayPlayButtonElement::updateDisplayType()
285 if (mediaElement().togglePlayStateWillPlay()) {
291 const AtomicString& MediaControlOverlayPlayButtonElement::shadowPseudoId() const
293 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-overlay-play-button", AtomicString::ConstructFromLiteral));
298 // ----------------------------
300 MediaControlToggleClosedCaptionsButtonElement::MediaControlToggleClosedCaptionsButtonElement(MediaControls& mediaControls)
301 : MediaControlInputElement(mediaControls, MediaShowClosedCaptionsButton)
305 PassRefPtr<MediaControlToggleClosedCaptionsButtonElement> MediaControlToggleClosedCaptionsButtonElement::create(MediaControls& mediaControls)
307 RefPtr<MediaControlToggleClosedCaptionsButtonElement> button = adoptRef(new MediaControlToggleClosedCaptionsButtonElement(mediaControls));
308 button->ensureUserAgentShadowRoot();
309 button->setType("button");
311 return button.release();
314 void MediaControlToggleClosedCaptionsButtonElement::updateDisplayType()
316 bool captionsVisible = mediaElement().closedCaptionsVisible();
317 setDisplayType(captionsVisible ? MediaHideClosedCaptionsButton : MediaShowClosedCaptionsButton);
318 setChecked(captionsVisible);
321 void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event* event)
323 if (event->type() == EventTypeNames::click) {
324 mediaElement().setClosedCaptionsVisible(!mediaElement().closedCaptionsVisible());
325 setChecked(mediaElement().closedCaptionsVisible());
327 event->setDefaultHandled();
330 HTMLInputElement::defaultEventHandler(event);
333 const AtomicString& MediaControlToggleClosedCaptionsButtonElement::shadowPseudoId() const
335 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-toggle-closed-captions-button", AtomicString::ConstructFromLiteral));
339 // ----------------------------
341 MediaControlTimelineElement::MediaControlTimelineElement(MediaControls& mediaControls)
342 : MediaControlInputElement(mediaControls, MediaSlider)
346 PassRefPtr<MediaControlTimelineElement> MediaControlTimelineElement::create(MediaControls& mediaControls)
348 RefPtr<MediaControlTimelineElement> timeline = adoptRef(new MediaControlTimelineElement(mediaControls));
349 timeline->ensureUserAgentShadowRoot();
350 timeline->setType("range");
351 timeline->setAttribute(stepAttr, "any");
352 return timeline.release();
355 void MediaControlTimelineElement::defaultEventHandler(Event* event)
357 if (event->isMouseEvent() && toMouseEvent(event)->button() != LeftButton)
360 if (!inDocument() || !document().isActive())
363 if (event->type() == EventTypeNames::mousedown)
364 mediaControls().beginScrubbing();
366 if (event->type() == EventTypeNames::mouseup)
367 mediaControls().endScrubbing();
369 MediaControlInputElement::defaultEventHandler(event);
371 if (event->type() == EventTypeNames::mouseover || event->type() == EventTypeNames::mouseout || event->type() == EventTypeNames::mousemove)
374 double time = value().toDouble();
375 if (event->type() == EventTypeNames::input && time != mediaControllerInterface().currentTime())
376 mediaControllerInterface().setCurrentTime(time, IGNORE_EXCEPTION);
378 RenderSlider* slider = toRenderSlider(renderer());
379 if (slider && slider->inDragMode())
380 mediaControls().updateCurrentTimeDisplay();
383 bool MediaControlTimelineElement::willRespondToMouseClickEvents()
385 return inDocument() && document().isActive();
388 void MediaControlTimelineElement::setPosition(double currentTime)
390 setValue(String::number(currentTime));
393 void MediaControlTimelineElement::setDuration(double duration)
395 setFloatingPointAttribute(maxAttr, std::isfinite(duration) ? duration : 0);
399 const AtomicString& MediaControlTimelineElement::shadowPseudoId() const
401 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-timeline", AtomicString::ConstructFromLiteral));
405 // ----------------------------
407 MediaControlVolumeSliderElement::MediaControlVolumeSliderElement(MediaControls& mediaControls)
408 : MediaControlInputElement(mediaControls, MediaVolumeSlider)
412 PassRefPtr<MediaControlVolumeSliderElement> MediaControlVolumeSliderElement::create(MediaControls& mediaControls)
414 RefPtr<MediaControlVolumeSliderElement> slider = adoptRef(new MediaControlVolumeSliderElement(mediaControls));
415 slider->ensureUserAgentShadowRoot();
416 slider->setType("range");
417 slider->setAttribute(stepAttr, "any");
418 slider->setAttribute(maxAttr, "1");
419 return slider.release();
422 void MediaControlVolumeSliderElement::defaultEventHandler(Event* event)
424 if (event->isMouseEvent() && toMouseEvent(event)->button() != LeftButton)
427 if (!inDocument() || !document().isActive())
430 MediaControlInputElement::defaultEventHandler(event);
432 if (event->type() == EventTypeNames::mouseover || event->type() == EventTypeNames::mouseout || event->type() == EventTypeNames::mousemove)
435 double volume = value().toDouble();
436 mediaControllerInterface().setVolume(volume, ASSERT_NO_EXCEPTION);
437 mediaControllerInterface().setMuted(false);
440 bool MediaControlVolumeSliderElement::willRespondToMouseMoveEvents()
442 if (!inDocument() || !document().isActive())
445 return MediaControlInputElement::willRespondToMouseMoveEvents();
448 bool MediaControlVolumeSliderElement::willRespondToMouseClickEvents()
450 if (!inDocument() || !document().isActive())
453 return MediaControlInputElement::willRespondToMouseClickEvents();
456 void MediaControlVolumeSliderElement::setVolume(double volume)
458 if (value().toDouble() != volume)
459 setValue(String::number(volume));
462 const AtomicString& MediaControlVolumeSliderElement::shadowPseudoId() const
464 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-volume-slider", AtomicString::ConstructFromLiteral));
468 // ----------------------------
470 MediaControlFullscreenButtonElement::MediaControlFullscreenButtonElement(MediaControls& mediaControls)
471 : MediaControlInputElement(mediaControls, MediaEnterFullscreenButton)
475 PassRefPtr<MediaControlFullscreenButtonElement> MediaControlFullscreenButtonElement::create(MediaControls& mediaControls)
477 RefPtr<MediaControlFullscreenButtonElement> button = adoptRef(new MediaControlFullscreenButtonElement(mediaControls));
478 button->ensureUserAgentShadowRoot();
479 button->setType("button");
481 return button.release();
484 void MediaControlFullscreenButtonElement::defaultEventHandler(Event* event)
486 if (event->type() == EventTypeNames::click) {
487 if (FullscreenElementStack::isActiveFullScreenElement(&mediaElement()))
488 FullscreenElementStack::from(document()).webkitCancelFullScreen();
490 FullscreenElementStack::from(document()).requestFullScreenForElement(&mediaElement(), 0, FullscreenElementStack::ExemptIFrameAllowFullScreenRequirement);
491 event->setDefaultHandled();
493 HTMLInputElement::defaultEventHandler(event);
496 const AtomicString& MediaControlFullscreenButtonElement::shadowPseudoId() const
498 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-button", AtomicString::ConstructFromLiteral));
502 void MediaControlFullscreenButtonElement::setIsFullscreen(bool isFullscreen)
504 setDisplayType(isFullscreen ? MediaExitFullscreenButton : MediaEnterFullscreenButton);
507 // ----------------------------
509 MediaControlTimeRemainingDisplayElement::MediaControlTimeRemainingDisplayElement(MediaControls& mediaControls)
510 : MediaControlTimeDisplayElement(mediaControls, MediaTimeRemainingDisplay)
514 PassRefPtr<MediaControlTimeRemainingDisplayElement> MediaControlTimeRemainingDisplayElement::create(MediaControls& mediaControls)
516 return adoptRef(new MediaControlTimeRemainingDisplayElement(mediaControls));
519 static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId()
521 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-time-remaining-display", AtomicString::ConstructFromLiteral));
525 const AtomicString& MediaControlTimeRemainingDisplayElement::shadowPseudoId() const
527 return getMediaControlTimeRemainingDisplayElementShadowPseudoId();
530 // ----------------------------
532 MediaControlCurrentTimeDisplayElement::MediaControlCurrentTimeDisplayElement(MediaControls& mediaControls)
533 : MediaControlTimeDisplayElement(mediaControls, MediaCurrentTimeDisplay)
537 PassRefPtr<MediaControlCurrentTimeDisplayElement> MediaControlCurrentTimeDisplayElement::create(MediaControls& mediaControls)
539 return adoptRef(new MediaControlCurrentTimeDisplayElement(mediaControls));
542 static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId()
544 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-current-time-display", AtomicString::ConstructFromLiteral));
548 const AtomicString& MediaControlCurrentTimeDisplayElement::shadowPseudoId() const
550 return getMediaControlCurrentTimeDisplayElementShadowPseudoId();
553 // ----------------------------
555 MediaControlTextTrackContainerElement::MediaControlTextTrackContainerElement(MediaControls& mediaControls)
556 : MediaControlDivElement(mediaControls, MediaTextTrackDisplayContainer)
561 PassRefPtr<MediaControlTextTrackContainerElement> MediaControlTextTrackContainerElement::create(MediaControls& mediaControls)
563 RefPtr<MediaControlTextTrackContainerElement> element = adoptRef(new MediaControlTextTrackContainerElement(mediaControls));
565 return element.release();
568 RenderObject* MediaControlTextTrackContainerElement::createRenderer(RenderStyle*)
570 return new RenderTextTrackContainerElement(this);
573 const AtomicString& MediaControlTextTrackContainerElement::textTrackContainerElementShadowPseudoId()
575 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-text-track-container", AtomicString::ConstructFromLiteral));
579 const AtomicString& MediaControlTextTrackContainerElement::shadowPseudoId() const
581 return textTrackContainerElementShadowPseudoId();
584 void MediaControlTextTrackContainerElement::updateDisplay()
586 if (!mediaElement().closedCaptionsVisible()) {
591 // 1. If the media element is an audio element, or is another playback
592 // mechanism with no rendering area, abort these steps. There is nothing to
594 if (isHTMLAudioElement(mediaElement()))
597 // 2. Let video be the media element or other playback mechanism.
598 HTMLVideoElement& video = toHTMLVideoElement(mediaElement());
600 // 3. Let output be an empty list of absolutely positioned CSS block boxes.
601 Vector<RefPtr<HTMLDivElement> > output;
603 // 4. If the user agent is exposing a user interface for video, add to
604 // output one or more completely transparent positioned CSS block boxes that
605 // cover the same region as the user interface.
607 // 5. If the last time these rules were run, the user agent was not exposing
608 // a user interface for video, but now it is, let reset be true. Otherwise,
609 // let reset be false.
611 // There is nothing to be done explicitly for 4th and 5th steps, as
612 // everything is handled through CSS. The caption box is on top of the
613 // controls box, in a container set with the -webkit-box display property.
615 // 6. Let tracks be the subset of video's list of text tracks that have as
616 // their rules for updating the text track rendering these rules for
617 // updating the display of WebVTT text tracks, and whose text track mode is
618 // showing or showing by default.
619 // 7. Let cues be an empty list of text track cues.
620 // 8. For each track track in tracks, append to cues all the cues from
621 // track's list of cues that have their text track cue active flag set.
622 CueList activeCues = video.currentlyActiveCues();
624 // 9. If reset is false, then, for each text track cue cue in cues: if cue's
625 // text track cue display state has a set of CSS boxes, then add those boxes
626 // to output, and remove cue from cues.
628 // There is nothing explicitly to be done here, as all the caching occurs
629 // within the TextTrackCue instance itself. If parameters of the cue change,
630 // the display tree is cleared.
632 // 10. For each text track cue cue in cues that has not yet had
633 // corresponding CSS boxes added to output, in text track cue order, run the
634 // following substeps:
635 for (size_t i = 0; i < activeCues.size(); ++i) {
636 TextTrackCue* cue = activeCues[i].data();
638 ASSERT(cue->isActive());
639 if (!cue->track() || !cue->track()->isRendered() || !cue->isActive())
642 cue->updateDisplay(m_videoDisplaySize.size(), *this);
645 // 11. Return output.
652 void MediaControlTextTrackContainerElement::updateSizes()
654 if (!document().isActive())
659 if (!mediaElement().renderer() || !mediaElement().renderer()->isVideo())
661 videoBox = toRenderVideo(mediaElement().renderer())->videoBox();
663 if (m_videoDisplaySize == videoBox)
665 m_videoDisplaySize = videoBox;
667 float smallestDimension = std::min(m_videoDisplaySize.size().height(), m_videoDisplaySize.size().width());
669 float fontSize = smallestDimension * 0.05f;
670 if (fontSize != m_fontSize) {
671 m_fontSize = fontSize;
672 setInlineStyleProperty(CSSPropertyFontSize, fontSize, CSSPrimitiveValue::CSS_PX);
676 // ----------------------------
678 } // namespace WebCore