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 "bindings/v8/ExceptionStatePlaceholder.h"
34 #include "core/dom/DOMTokenList.h"
35 #include "core/dom/FullscreenElementStack.h"
36 #include "core/dom/shadow/ShadowRoot.h"
37 #include "core/events/MouseEvent.h"
38 #include "core/frame/LocalFrame.h"
39 #include "core/html/HTMLVideoElement.h"
40 #include "core/html/MediaController.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"
49 #include "platform/RuntimeEnabledFeatures.h"
53 using namespace HTMLNames;
55 static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId();
56 static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId();
58 // This is the duration from mediaControls.css
59 static const double fadeOutDuration = 0.3;
61 MediaControlPanelElement::MediaControlPanelElement(MediaControls& mediaControls)
62 : MediaControlDivElement(mediaControls, MediaControlsPanel)
63 , m_isDisplayed(false)
65 , m_transitionTimer(this, &MediaControlPanelElement::transitionTimerFired)
69 PassRefPtrWillBeRawPtr<MediaControlPanelElement> MediaControlPanelElement::create(MediaControls& mediaControls)
71 return adoptRefWillBeNoop(new MediaControlPanelElement(mediaControls));
74 const AtomicString& MediaControlPanelElement::shadowPseudoId() const
76 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-panel", AtomicString::ConstructFromLiteral));
80 void MediaControlPanelElement::defaultEventHandler(Event* event)
82 // Suppress the media element activation behavior (toggle play/pause) when
83 // any part of the control panel is clicked.
84 if (event->type() == EventTypeNames::click) {
85 event->setDefaultHandled();
88 HTMLDivElement::defaultEventHandler(event);
91 void MediaControlPanelElement::startTimer()
95 // The timer is required to set the property display:'none' on the panel,
96 // such that captions are correctly displayed at the bottom of the video
97 // at the end of the fadeout transition.
98 // FIXME: Racing a transition with a setTimeout like this is wrong.
99 m_transitionTimer.startOneShot(fadeOutDuration, FROM_HERE);
102 void MediaControlPanelElement::stopTimer()
104 if (m_transitionTimer.isActive())
105 m_transitionTimer.stop();
108 void MediaControlPanelElement::transitionTimerFired(Timer<MediaControlPanelElement>*)
116 void MediaControlPanelElement::makeOpaque()
121 setInlineStyleProperty(CSSPropertyOpacity, 1.0, CSSPrimitiveValue::CSS_NUMBER);
128 void MediaControlPanelElement::makeTransparent()
133 setInlineStyleProperty(CSSPropertyOpacity, 0.0, CSSPrimitiveValue::CSS_NUMBER);
139 void MediaControlPanelElement::setIsDisplayed(bool isDisplayed)
141 m_isDisplayed = isDisplayed;
144 // ----------------------------
146 MediaControlPanelEnclosureElement::MediaControlPanelEnclosureElement(MediaControls& mediaControls)
147 // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
148 : MediaControlDivElement(mediaControls, MediaControlsPanel)
152 PassRefPtrWillBeRawPtr<MediaControlPanelEnclosureElement> MediaControlPanelEnclosureElement::create(MediaControls& mediaControls)
154 return adoptRefWillBeNoop(new MediaControlPanelEnclosureElement(mediaControls));
157 const AtomicString& MediaControlPanelEnclosureElement::shadowPseudoId() const
159 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-enclosure", AtomicString::ConstructFromLiteral));
163 // ----------------------------
165 MediaControlOverlayEnclosureElement::MediaControlOverlayEnclosureElement(MediaControls& mediaControls)
166 // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
167 : MediaControlDivElement(mediaControls, MediaControlsPanel)
171 PassRefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> MediaControlOverlayEnclosureElement::create(MediaControls& mediaControls)
173 return adoptRefWillBeNoop(new MediaControlOverlayEnclosureElement(mediaControls));
176 const AtomicString& MediaControlOverlayEnclosureElement::shadowPseudoId() const
178 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-overlay-enclosure", AtomicString::ConstructFromLiteral));
182 // ----------------------------
184 MediaControlMuteButtonElement::MediaControlMuteButtonElement(MediaControls& mediaControls)
185 : MediaControlInputElement(mediaControls, MediaMuteButton)
189 PassRefPtrWillBeRawPtr<MediaControlMuteButtonElement> MediaControlMuteButtonElement::create(MediaControls& mediaControls)
191 RefPtrWillBeRawPtr<MediaControlMuteButtonElement> button = adoptRefWillBeNoop(new MediaControlMuteButtonElement(mediaControls));
192 button->ensureUserAgentShadowRoot();
193 button->setType("button");
194 return button.release();
197 void MediaControlMuteButtonElement::defaultEventHandler(Event* event)
199 if (event->type() == EventTypeNames::click) {
200 mediaElement().setMuted(!mediaElement().muted());
201 event->setDefaultHandled();
204 HTMLInputElement::defaultEventHandler(event);
207 void MediaControlMuteButtonElement::updateDisplayType()
209 setDisplayType(mediaElement().muted() ? MediaUnMuteButton : MediaMuteButton);
212 const AtomicString& MediaControlMuteButtonElement::shadowPseudoId() const
214 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-mute-button", AtomicString::ConstructFromLiteral));
218 // ----------------------------
220 MediaControlPlayButtonElement::MediaControlPlayButtonElement(MediaControls& mediaControls)
221 : MediaControlInputElement(mediaControls, MediaPlayButton)
225 PassRefPtrWillBeRawPtr<MediaControlPlayButtonElement> MediaControlPlayButtonElement::create(MediaControls& mediaControls)
227 RefPtrWillBeRawPtr<MediaControlPlayButtonElement> button = adoptRefWillBeNoop(new MediaControlPlayButtonElement(mediaControls));
228 button->ensureUserAgentShadowRoot();
229 button->setType("button");
230 return button.release();
233 void MediaControlPlayButtonElement::defaultEventHandler(Event* event)
235 if (event->type() == EventTypeNames::click) {
236 mediaElement().togglePlayState();
238 event->setDefaultHandled();
240 HTMLInputElement::defaultEventHandler(event);
243 void MediaControlPlayButtonElement::updateDisplayType()
245 setDisplayType(mediaElement().togglePlayStateWillPlay() ? MediaPlayButton : MediaPauseButton);
248 const AtomicString& MediaControlPlayButtonElement::shadowPseudoId() const
250 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-play-button", AtomicString::ConstructFromLiteral));
254 // ----------------------------
256 MediaControlOverlayPlayButtonElement::MediaControlOverlayPlayButtonElement(MediaControls& mediaControls)
257 : MediaControlInputElement(mediaControls, MediaOverlayPlayButton)
261 PassRefPtrWillBeRawPtr<MediaControlOverlayPlayButtonElement> MediaControlOverlayPlayButtonElement::create(MediaControls& mediaControls)
263 RefPtrWillBeRawPtr<MediaControlOverlayPlayButtonElement> button = adoptRefWillBeNoop(new MediaControlOverlayPlayButtonElement(mediaControls));
264 button->ensureUserAgentShadowRoot();
265 button->setType("button");
266 return button.release();
269 void MediaControlOverlayPlayButtonElement::defaultEventHandler(Event* event)
271 if (event->type() == EventTypeNames::click && mediaElement().togglePlayStateWillPlay()) {
272 mediaElement().togglePlayState();
274 event->setDefaultHandled();
278 void MediaControlOverlayPlayButtonElement::updateDisplayType()
280 if (mediaElement().togglePlayStateWillPlay()) {
286 const AtomicString& MediaControlOverlayPlayButtonElement::shadowPseudoId() const
288 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-overlay-play-button", AtomicString::ConstructFromLiteral));
293 // ----------------------------
295 MediaControlToggleClosedCaptionsButtonElement::MediaControlToggleClosedCaptionsButtonElement(MediaControls& mediaControls)
296 : MediaControlInputElement(mediaControls, MediaShowClosedCaptionsButton)
300 PassRefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> MediaControlToggleClosedCaptionsButtonElement::create(MediaControls& mediaControls)
302 RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> button = adoptRefWillBeNoop(new MediaControlToggleClosedCaptionsButtonElement(mediaControls));
303 button->ensureUserAgentShadowRoot();
304 button->setType("button");
306 return button.release();
309 void MediaControlToggleClosedCaptionsButtonElement::updateDisplayType()
311 bool captionsVisible = mediaElement().closedCaptionsVisible();
312 setDisplayType(captionsVisible ? MediaHideClosedCaptionsButton : MediaShowClosedCaptionsButton);
313 setChecked(captionsVisible);
316 void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event* event)
318 if (event->type() == EventTypeNames::click) {
319 mediaElement().setClosedCaptionsVisible(!mediaElement().closedCaptionsVisible());
320 setChecked(mediaElement().closedCaptionsVisible());
322 event->setDefaultHandled();
325 HTMLInputElement::defaultEventHandler(event);
328 const AtomicString& MediaControlToggleClosedCaptionsButtonElement::shadowPseudoId() const
330 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-toggle-closed-captions-button", AtomicString::ConstructFromLiteral));
334 // ----------------------------
336 MediaControlTimelineElement::MediaControlTimelineElement(MediaControls& mediaControls)
337 : MediaControlInputElement(mediaControls, MediaSlider)
341 PassRefPtrWillBeRawPtr<MediaControlTimelineElement> MediaControlTimelineElement::create(MediaControls& mediaControls)
343 RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = adoptRefWillBeNoop(new MediaControlTimelineElement(mediaControls));
344 timeline->ensureUserAgentShadowRoot();
345 timeline->setType("range");
346 timeline->setAttribute(stepAttr, "any");
347 return timeline.release();
350 void MediaControlTimelineElement::defaultEventHandler(Event* event)
352 if (event->isMouseEvent() && toMouseEvent(event)->button() != LeftButton)
355 if (!inDocument() || !document().isActive())
358 if (event->type() == EventTypeNames::mousedown)
359 mediaControls().beginScrubbing();
361 if (event->type() == EventTypeNames::mouseup)
362 mediaControls().endScrubbing();
364 MediaControlInputElement::defaultEventHandler(event);
366 if (event->type() == EventTypeNames::mouseover || event->type() == EventTypeNames::mouseout || event->type() == EventTypeNames::mousemove)
369 double time = value().toDouble();
370 if (event->type() == EventTypeNames::input) {
371 // FIXME: This will need to take the timeline offset into consideration
372 // once that concept is supported, see https://crbug.com/312699
373 if (mediaElement().controller())
374 mediaElement().controller()->setCurrentTime(time, IGNORE_EXCEPTION);
376 mediaElement().setCurrentTime(time, IGNORE_EXCEPTION);
379 RenderSlider* slider = toRenderSlider(renderer());
380 if (slider && slider->inDragMode())
381 mediaControls().updateCurrentTimeDisplay();
384 bool MediaControlTimelineElement::willRespondToMouseClickEvents()
386 return inDocument() && document().isActive();
389 void MediaControlTimelineElement::setPosition(double currentTime)
391 setValue(String::number(currentTime));
394 void MediaControlTimelineElement::setDuration(double duration)
396 setFloatingPointAttribute(maxAttr, std::isfinite(duration) ? duration : 0);
400 const AtomicString& MediaControlTimelineElement::shadowPseudoId() const
402 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-timeline", AtomicString::ConstructFromLiteral));
406 // ----------------------------
408 MediaControlVolumeSliderElement::MediaControlVolumeSliderElement(MediaControls& mediaControls)
409 : MediaControlInputElement(mediaControls, MediaVolumeSlider)
413 PassRefPtrWillBeRawPtr<MediaControlVolumeSliderElement> MediaControlVolumeSliderElement::create(MediaControls& mediaControls)
415 RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = adoptRefWillBeNoop(new MediaControlVolumeSliderElement(mediaControls));
416 slider->ensureUserAgentShadowRoot();
417 slider->setType("range");
418 slider->setAttribute(stepAttr, "any");
419 slider->setAttribute(maxAttr, "1");
420 return slider.release();
423 void MediaControlVolumeSliderElement::defaultEventHandler(Event* event)
425 if (event->isMouseEvent() && toMouseEvent(event)->button() != LeftButton)
428 if (!inDocument() || !document().isActive())
431 MediaControlInputElement::defaultEventHandler(event);
433 if (event->type() == EventTypeNames::mouseover || event->type() == EventTypeNames::mouseout || event->type() == EventTypeNames::mousemove)
436 double volume = value().toDouble();
437 mediaElement().setVolume(volume, ASSERT_NO_EXCEPTION);
438 mediaElement().setMuted(false);
441 bool MediaControlVolumeSliderElement::willRespondToMouseMoveEvents()
443 if (!inDocument() || !document().isActive())
446 return MediaControlInputElement::willRespondToMouseMoveEvents();
449 bool MediaControlVolumeSliderElement::willRespondToMouseClickEvents()
451 if (!inDocument() || !document().isActive())
454 return MediaControlInputElement::willRespondToMouseClickEvents();
457 void MediaControlVolumeSliderElement::setVolume(double volume)
459 if (value().toDouble() != volume)
460 setValue(String::number(volume));
463 const AtomicString& MediaControlVolumeSliderElement::shadowPseudoId() const
465 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-volume-slider", AtomicString::ConstructFromLiteral));
469 // ----------------------------
471 MediaControlFullscreenButtonElement::MediaControlFullscreenButtonElement(MediaControls& mediaControls)
472 : MediaControlInputElement(mediaControls, MediaEnterFullscreenButton)
476 PassRefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> MediaControlFullscreenButtonElement::create(MediaControls& mediaControls)
478 RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> button = adoptRefWillBeNoop(new MediaControlFullscreenButtonElement(mediaControls));
479 button->ensureUserAgentShadowRoot();
480 button->setType("button");
482 return button.release();
485 void MediaControlFullscreenButtonElement::defaultEventHandler(Event* event)
487 if (event->type() == EventTypeNames::click) {
488 if (FullscreenElementStack::isActiveFullScreenElement(&mediaElement()))
489 FullscreenElementStack::from(document()).webkitCancelFullScreen();
491 FullscreenElementStack::from(document()).requestFullScreenForElement(&mediaElement(), 0, FullscreenElementStack::ExemptIFrameAllowFullScreenRequirement);
492 event->setDefaultHandled();
494 HTMLInputElement::defaultEventHandler(event);
497 const AtomicString& MediaControlFullscreenButtonElement::shadowPseudoId() const
499 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-button", AtomicString::ConstructFromLiteral));
503 void MediaControlFullscreenButtonElement::setIsFullscreen(bool isFullscreen)
505 setDisplayType(isFullscreen ? MediaExitFullscreenButton : MediaEnterFullscreenButton);
508 // ----------------------------
510 MediaControlTimeRemainingDisplayElement::MediaControlTimeRemainingDisplayElement(MediaControls& mediaControls)
511 : MediaControlTimeDisplayElement(mediaControls, MediaTimeRemainingDisplay)
515 PassRefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> MediaControlTimeRemainingDisplayElement::create(MediaControls& mediaControls)
517 return adoptRefWillBeNoop(new MediaControlTimeRemainingDisplayElement(mediaControls));
520 static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId()
522 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-time-remaining-display", AtomicString::ConstructFromLiteral));
526 const AtomicString& MediaControlTimeRemainingDisplayElement::shadowPseudoId() const
528 return getMediaControlTimeRemainingDisplayElementShadowPseudoId();
531 // ----------------------------
533 MediaControlCurrentTimeDisplayElement::MediaControlCurrentTimeDisplayElement(MediaControls& mediaControls)
534 : MediaControlTimeDisplayElement(mediaControls, MediaCurrentTimeDisplay)
538 PassRefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> MediaControlCurrentTimeDisplayElement::create(MediaControls& mediaControls)
540 return adoptRefWillBeNoop(new MediaControlCurrentTimeDisplayElement(mediaControls));
543 static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId()
545 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-current-time-display", AtomicString::ConstructFromLiteral));
549 const AtomicString& MediaControlCurrentTimeDisplayElement::shadowPseudoId() const
551 return getMediaControlCurrentTimeDisplayElementShadowPseudoId();
554 // ----------------------------
556 MediaControlTextTrackContainerElement::MediaControlTextTrackContainerElement(MediaControls& mediaControls)
557 : MediaControlDivElement(mediaControls, MediaTextTrackDisplayContainer)
562 PassRefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> MediaControlTextTrackContainerElement::create(MediaControls& mediaControls)
564 RefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> element = adoptRefWillBeNoop(new MediaControlTextTrackContainerElement(mediaControls));
566 return element.release();
569 RenderObject* MediaControlTextTrackContainerElement::createRenderer(RenderStyle*)
571 return new RenderTextTrackContainerElement(this);
574 const AtomicString& MediaControlTextTrackContainerElement::textTrackContainerElementShadowPseudoId()
576 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-text-track-container", AtomicString::ConstructFromLiteral));
580 const AtomicString& MediaControlTextTrackContainerElement::shadowPseudoId() const
582 return textTrackContainerElementShadowPseudoId();
585 void MediaControlTextTrackContainerElement::updateDisplay()
587 if (!mediaElement().closedCaptionsVisible()) {
592 // 1. If the media element is an audio element, or is another playback
593 // mechanism with no rendering area, abort these steps. There is nothing to
595 if (isHTMLAudioElement(mediaElement()))
598 // 2. Let video be the media element or other playback mechanism.
599 HTMLVideoElement& video = toHTMLVideoElement(mediaElement());
601 // 3. Let output be an empty list of absolutely positioned CSS block boxes.
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