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/MediaController.h"
42 #include "core/html/shadow/MediaControls.h"
43 #include "core/html/track/TextTrack.h"
44 #include "core/html/track/vtt/VTTRegionList.h"
45 #include "core/page/EventHandler.h"
46 #include "core/rendering/RenderMediaControlElements.h"
47 #include "core/rendering/RenderSlider.h"
48 #include "core/rendering/RenderTheme.h"
49 #include "core/rendering/RenderVideo.h"
53 using namespace HTMLNames;
55 static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId();
56 static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId();
58 static const double fadeInDuration = 0.1;
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 PassRefPtr<MediaControlPanelElement> MediaControlPanelElement::create(MediaControls& mediaControls)
71 return adoptRef(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(CSSPropertyTransitionProperty, CSSPropertyOpacity);
122 setInlineStyleProperty(CSSPropertyTransitionDuration, fadeInDuration, CSSPrimitiveValue::CSS_S);
123 setInlineStyleProperty(CSSPropertyOpacity, 1.0, CSSPrimitiveValue::CSS_NUMBER);
131 void MediaControlPanelElement::makeTransparent()
136 setInlineStyleProperty(CSSPropertyTransitionProperty, CSSPropertyOpacity);
137 setInlineStyleProperty(CSSPropertyTransitionDuration, fadeOutDuration, CSSPrimitiveValue::CSS_S);
138 setInlineStyleProperty(CSSPropertyOpacity, 0.0, CSSPrimitiveValue::CSS_NUMBER);
144 void MediaControlPanelElement::setIsDisplayed(bool isDisplayed)
146 m_isDisplayed = isDisplayed;
149 // ----------------------------
151 MediaControlPanelEnclosureElement::MediaControlPanelEnclosureElement(MediaControls& mediaControls)
152 // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
153 : MediaControlDivElement(mediaControls, MediaControlsPanel)
157 PassRefPtr<MediaControlPanelEnclosureElement> MediaControlPanelEnclosureElement::create(MediaControls& mediaControls)
159 return adoptRef(new MediaControlPanelEnclosureElement(mediaControls));
162 const AtomicString& MediaControlPanelEnclosureElement::shadowPseudoId() const
164 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-enclosure", AtomicString::ConstructFromLiteral));
168 // ----------------------------
170 MediaControlOverlayEnclosureElement::MediaControlOverlayEnclosureElement(MediaControls& mediaControls)
171 // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
172 : MediaControlDivElement(mediaControls, MediaControlsPanel)
176 PassRefPtr<MediaControlOverlayEnclosureElement> MediaControlOverlayEnclosureElement::create(MediaControls& mediaControls)
178 return adoptRef(new MediaControlOverlayEnclosureElement(mediaControls));
181 const AtomicString& MediaControlOverlayEnclosureElement::shadowPseudoId() const
183 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-overlay-enclosure", AtomicString::ConstructFromLiteral));
187 // ----------------------------
189 MediaControlMuteButtonElement::MediaControlMuteButtonElement(MediaControls& mediaControls)
190 : MediaControlInputElement(mediaControls, MediaMuteButton)
194 PassRefPtr<MediaControlMuteButtonElement> MediaControlMuteButtonElement::create(MediaControls& mediaControls)
196 RefPtr<MediaControlMuteButtonElement> button = adoptRef(new MediaControlMuteButtonElement(mediaControls));
197 button->ensureUserAgentShadowRoot();
198 button->setType("button");
199 return button.release();
202 void MediaControlMuteButtonElement::defaultEventHandler(Event* event)
204 if (event->type() == EventTypeNames::click) {
205 mediaElement().setMuted(!mediaElement().muted());
206 event->setDefaultHandled();
209 HTMLInputElement::defaultEventHandler(event);
212 void MediaControlMuteButtonElement::updateDisplayType()
214 setDisplayType(mediaElement().muted() ? MediaUnMuteButton : MediaMuteButton);
217 const AtomicString& MediaControlMuteButtonElement::shadowPseudoId() const
219 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-mute-button", AtomicString::ConstructFromLiteral));
223 // ----------------------------
225 MediaControlPlayButtonElement::MediaControlPlayButtonElement(MediaControls& mediaControls)
226 : MediaControlInputElement(mediaControls, MediaPlayButton)
230 PassRefPtr<MediaControlPlayButtonElement> MediaControlPlayButtonElement::create(MediaControls& mediaControls)
232 RefPtr<MediaControlPlayButtonElement> button = adoptRef(new MediaControlPlayButtonElement(mediaControls));
233 button->ensureUserAgentShadowRoot();
234 button->setType("button");
235 return button.release();
238 void MediaControlPlayButtonElement::defaultEventHandler(Event* event)
240 if (event->type() == EventTypeNames::click) {
241 mediaElement().togglePlayState();
243 event->setDefaultHandled();
245 HTMLInputElement::defaultEventHandler(event);
248 void MediaControlPlayButtonElement::updateDisplayType()
250 setDisplayType(mediaElement().togglePlayStateWillPlay() ? MediaPlayButton : MediaPauseButton);
253 const AtomicString& MediaControlPlayButtonElement::shadowPseudoId() const
255 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-play-button", AtomicString::ConstructFromLiteral));
259 // ----------------------------
261 MediaControlOverlayPlayButtonElement::MediaControlOverlayPlayButtonElement(MediaControls& mediaControls)
262 : MediaControlInputElement(mediaControls, MediaOverlayPlayButton)
266 PassRefPtr<MediaControlOverlayPlayButtonElement> MediaControlOverlayPlayButtonElement::create(MediaControls& mediaControls)
268 RefPtr<MediaControlOverlayPlayButtonElement> button = adoptRef(new MediaControlOverlayPlayButtonElement(mediaControls));
269 button->ensureUserAgentShadowRoot();
270 button->setType("button");
271 return button.release();
274 void MediaControlOverlayPlayButtonElement::defaultEventHandler(Event* event)
276 if (event->type() == EventTypeNames::click && mediaElement().togglePlayStateWillPlay()) {
277 mediaElement().togglePlayState();
279 event->setDefaultHandled();
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) {
376 // FIXME: This will need to take the timeline offset into consideration
377 // once that concept is supported, see https://crbug.com/312699
378 if (mediaElement().controller())
379 mediaElement().controller()->setCurrentTime(time, IGNORE_EXCEPTION);
381 mediaElement().setCurrentTime(time, IGNORE_EXCEPTION);
384 RenderSlider* slider = toRenderSlider(renderer());
385 if (slider && slider->inDragMode())
386 mediaControls().updateCurrentTimeDisplay();
389 bool MediaControlTimelineElement::willRespondToMouseClickEvents()
391 return inDocument() && document().isActive();
394 void MediaControlTimelineElement::setPosition(double currentTime)
396 setValue(String::number(currentTime));
399 void MediaControlTimelineElement::setDuration(double duration)
401 setFloatingPointAttribute(maxAttr, std::isfinite(duration) ? duration : 0);
405 const AtomicString& MediaControlTimelineElement::shadowPseudoId() const
407 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-timeline", AtomicString::ConstructFromLiteral));
411 // ----------------------------
413 MediaControlVolumeSliderElement::MediaControlVolumeSliderElement(MediaControls& mediaControls)
414 : MediaControlInputElement(mediaControls, MediaVolumeSlider)
418 PassRefPtr<MediaControlVolumeSliderElement> MediaControlVolumeSliderElement::create(MediaControls& mediaControls)
420 RefPtr<MediaControlVolumeSliderElement> slider = adoptRef(new MediaControlVolumeSliderElement(mediaControls));
421 slider->ensureUserAgentShadowRoot();
422 slider->setType("range");
423 slider->setAttribute(stepAttr, "any");
424 slider->setAttribute(maxAttr, "1");
425 return slider.release();
428 void MediaControlVolumeSliderElement::defaultEventHandler(Event* event)
430 if (event->isMouseEvent() && toMouseEvent(event)->button() != LeftButton)
433 if (!inDocument() || !document().isActive())
436 MediaControlInputElement::defaultEventHandler(event);
438 if (event->type() == EventTypeNames::mouseover || event->type() == EventTypeNames::mouseout || event->type() == EventTypeNames::mousemove)
441 double volume = value().toDouble();
442 mediaElement().setVolume(volume, ASSERT_NO_EXCEPTION);
443 mediaElement().setMuted(false);
446 bool MediaControlVolumeSliderElement::willRespondToMouseMoveEvents()
448 if (!inDocument() || !document().isActive())
451 return MediaControlInputElement::willRespondToMouseMoveEvents();
454 bool MediaControlVolumeSliderElement::willRespondToMouseClickEvents()
456 if (!inDocument() || !document().isActive())
459 return MediaControlInputElement::willRespondToMouseClickEvents();
462 void MediaControlVolumeSliderElement::setVolume(double volume)
464 if (value().toDouble() != volume)
465 setValue(String::number(volume));
468 const AtomicString& MediaControlVolumeSliderElement::shadowPseudoId() const
470 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-volume-slider", AtomicString::ConstructFromLiteral));
474 // ----------------------------
476 MediaControlFullscreenButtonElement::MediaControlFullscreenButtonElement(MediaControls& mediaControls)
477 : MediaControlInputElement(mediaControls, MediaEnterFullscreenButton)
481 PassRefPtr<MediaControlFullscreenButtonElement> MediaControlFullscreenButtonElement::create(MediaControls& mediaControls)
483 RefPtr<MediaControlFullscreenButtonElement> button = adoptRef(new MediaControlFullscreenButtonElement(mediaControls));
484 button->ensureUserAgentShadowRoot();
485 button->setType("button");
487 return button.release();
490 void MediaControlFullscreenButtonElement::defaultEventHandler(Event* event)
492 if (event->type() == EventTypeNames::click) {
493 if (FullscreenElementStack::isActiveFullScreenElement(&mediaElement()))
494 FullscreenElementStack::from(document()).webkitCancelFullScreen();
496 FullscreenElementStack::from(document()).requestFullScreenForElement(&mediaElement(), 0, FullscreenElementStack::ExemptIFrameAllowFullScreenRequirement);
497 event->setDefaultHandled();
499 HTMLInputElement::defaultEventHandler(event);
502 const AtomicString& MediaControlFullscreenButtonElement::shadowPseudoId() const
504 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-button", AtomicString::ConstructFromLiteral));
508 void MediaControlFullscreenButtonElement::setIsFullscreen(bool isFullscreen)
510 setDisplayType(isFullscreen ? MediaExitFullscreenButton : MediaEnterFullscreenButton);
513 // ----------------------------
515 MediaControlTimeRemainingDisplayElement::MediaControlTimeRemainingDisplayElement(MediaControls& mediaControls)
516 : MediaControlTimeDisplayElement(mediaControls, MediaTimeRemainingDisplay)
520 PassRefPtr<MediaControlTimeRemainingDisplayElement> MediaControlTimeRemainingDisplayElement::create(MediaControls& mediaControls)
522 return adoptRef(new MediaControlTimeRemainingDisplayElement(mediaControls));
525 static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId()
527 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-time-remaining-display", AtomicString::ConstructFromLiteral));
531 const AtomicString& MediaControlTimeRemainingDisplayElement::shadowPseudoId() const
533 return getMediaControlTimeRemainingDisplayElementShadowPseudoId();
536 // ----------------------------
538 MediaControlCurrentTimeDisplayElement::MediaControlCurrentTimeDisplayElement(MediaControls& mediaControls)
539 : MediaControlTimeDisplayElement(mediaControls, MediaCurrentTimeDisplay)
543 PassRefPtr<MediaControlCurrentTimeDisplayElement> MediaControlCurrentTimeDisplayElement::create(MediaControls& mediaControls)
545 return adoptRef(new MediaControlCurrentTimeDisplayElement(mediaControls));
548 static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId()
550 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-current-time-display", AtomicString::ConstructFromLiteral));
554 const AtomicString& MediaControlCurrentTimeDisplayElement::shadowPseudoId() const
556 return getMediaControlCurrentTimeDisplayElementShadowPseudoId();
559 // ----------------------------
561 MediaControlTextTrackContainerElement::MediaControlTextTrackContainerElement(MediaControls& mediaControls)
562 : MediaControlDivElement(mediaControls, MediaTextTrackDisplayContainer)
567 PassRefPtr<MediaControlTextTrackContainerElement> MediaControlTextTrackContainerElement::create(MediaControls& mediaControls)
569 RefPtr<MediaControlTextTrackContainerElement> element = adoptRef(new MediaControlTextTrackContainerElement(mediaControls));
571 return element.release();
574 RenderObject* MediaControlTextTrackContainerElement::createRenderer(RenderStyle*)
576 return new RenderTextTrackContainerElement(this);
579 const AtomicString& MediaControlTextTrackContainerElement::textTrackContainerElementShadowPseudoId()
581 DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-text-track-container", AtomicString::ConstructFromLiteral));
585 const AtomicString& MediaControlTextTrackContainerElement::shadowPseudoId() const
587 return textTrackContainerElementShadowPseudoId();
590 void MediaControlTextTrackContainerElement::updateDisplay()
592 if (!mediaElement().closedCaptionsVisible()) {
597 // 1. If the media element is an audio element, or is another playback
598 // mechanism with no rendering area, abort these steps. There is nothing to
600 if (isHTMLAudioElement(mediaElement()))
603 // 2. Let video be the media element or other playback mechanism.
604 HTMLVideoElement& video = toHTMLVideoElement(mediaElement());
606 // 3. Let output be an empty list of absolutely positioned CSS block boxes.
607 Vector<RefPtr<HTMLDivElement> > output;
609 // 4. If the user agent is exposing a user interface for video, add to
610 // output one or more completely transparent positioned CSS block boxes that
611 // cover the same region as the user interface.
613 // 5. If the last time these rules were run, the user agent was not exposing
614 // a user interface for video, but now it is, let reset be true. Otherwise,
615 // let reset be false.
617 // There is nothing to be done explicitly for 4th and 5th steps, as
618 // everything is handled through CSS. The caption box is on top of the
619 // controls box, in a container set with the -webkit-box display property.
621 // 6. Let tracks be the subset of video's list of text tracks that have as
622 // their rules for updating the text track rendering these rules for
623 // updating the display of WebVTT text tracks, and whose text track mode is
624 // showing or showing by default.
625 // 7. Let cues be an empty list of text track cues.
626 // 8. For each track track in tracks, append to cues all the cues from
627 // track's list of cues that have their text track cue active flag set.
628 CueList activeCues = video.currentlyActiveCues();
630 // 9. If reset is false, then, for each text track cue cue in cues: if cue's
631 // text track cue display state has a set of CSS boxes, then add those boxes
632 // to output, and remove cue from cues.
634 // There is nothing explicitly to be done here, as all the caching occurs
635 // within the TextTrackCue instance itself. If parameters of the cue change,
636 // the display tree is cleared.
638 // 10. For each text track cue cue in cues that has not yet had
639 // corresponding CSS boxes added to output, in text track cue order, run the
640 // following substeps:
641 for (size_t i = 0; i < activeCues.size(); ++i) {
642 TextTrackCue* cue = activeCues[i].data();
644 ASSERT(cue->isActive());
645 if (!cue->track() || !cue->track()->isRendered() || !cue->isActive())
648 cue->updateDisplay(m_videoDisplaySize.size(), *this);
651 // 11. Return output.
658 void MediaControlTextTrackContainerElement::updateSizes()
660 if (!document().isActive())
665 if (!mediaElement().renderer() || !mediaElement().renderer()->isVideo())
667 videoBox = toRenderVideo(mediaElement().renderer())->videoBox();
669 if (m_videoDisplaySize == videoBox)
671 m_videoDisplaySize = videoBox;
673 float smallestDimension = std::min(m_videoDisplaySize.size().height(), m_videoDisplaySize.size().width());
675 float fontSize = smallestDimension * 0.05f;
676 if (fontSize != m_fontSize) {
677 m_fontSize = fontSize;
678 setInlineStyleProperty(CSSPropertyFontSize, fontSize, CSSPrimitiveValue::CSS_PX);
682 // ----------------------------
684 } // namespace WebCore