1/*
2 * Copyright (C) 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
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 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.
17 *
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.
28 */
29
30#include "config.h"
31
32#if ENABLE(VIDEO)
33#include "MediaControlElements.h"
34
35#include "DOMTokenList.h"
36#include "EventHandler.h"
37#include "EventNames.h"
38#include "ExceptionCodePlaceholder.h"
39#include "Frame.h"
40#include "GraphicsContext.h"
41#include "HTMLVideoElement.h"
42#include "ImageBuffer.h"
43#include "Language.h"
44#include "LocalizedStrings.h"
45#include "Logging.h"
46#include "MediaControls.h"
47#include "PageGroup.h"
48#include "RenderLayer.h"
49#include "RenderMediaControlElements.h"
50#include "RenderSlider.h"
51#include "RenderVideo.h"
52#include "RenderView.h"
53#include "Settings.h"
54#include "ShadowRoot.h"
55#if ENABLE(VIDEO_TRACK)
56#include "TextTrackList.h"
57#endif
58
59#if ENABLE(WEBVTT_REGIONS)
60#include "VTTRegionList.h"
61#endif
62
63namespace WebCore {
64
65using namespace HTMLNames;
66
67static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId();
68static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId();
69
70MediaControlPanelElement::MediaControlPanelElement(Document& document)
71    : MediaControlDivElement(document, MediaControlsPanel)
72    , m_canBeDragged(false)
73    , m_isBeingDragged(false)
74    , m_isDisplayed(false)
75    , m_opaque(true)
76    , m_transitionTimer(this, &MediaControlPanelElement::transitionTimerFired)
77{
78    setPseudo(shadowPseudoId());
79}
80
81PassRefPtr<MediaControlPanelElement> MediaControlPanelElement::create(Document& document)
82{
83    return adoptRef(new MediaControlPanelElement(document));
84}
85
86const AtomicString& MediaControlPanelElement::shadowPseudoId() const
87{
88    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-panel", AtomicString::ConstructFromLiteral));
89    return id;
90}
91
92void MediaControlPanelElement::startDrag(const LayoutPoint& eventLocation)
93{
94    if (!m_canBeDragged)
95        return;
96
97    if (m_isBeingDragged)
98        return;
99
100    auto renderer = this->renderer();
101    if (!renderer || !renderer->isBox())
102        return;
103
104    Frame* frame = document().frame();
105    if (!frame)
106        return;
107
108    m_lastDragEventLocation = eventLocation;
109
110    frame->eventHandler().setCapturingMouseEventsElement(this);
111
112    m_isBeingDragged = true;
113}
114
115void MediaControlPanelElement::continueDrag(const LayoutPoint& eventLocation)
116{
117    if (!m_isBeingDragged)
118        return;
119
120    LayoutSize distanceDragged = eventLocation - m_lastDragEventLocation;
121    m_cumulativeDragOffset.move(distanceDragged);
122    m_lastDragEventLocation = eventLocation;
123    setPosition(m_cumulativeDragOffset);
124}
125
126void MediaControlPanelElement::endDrag()
127{
128    if (!m_isBeingDragged)
129        return;
130
131    m_isBeingDragged = false;
132
133    Frame* frame = document().frame();
134    if (!frame)
135        return;
136
137    frame->eventHandler().setCapturingMouseEventsElement(nullptr);
138}
139
140void MediaControlPanelElement::startTimer()
141{
142    stopTimer();
143
144    // The timer is required to set the property display:'none' on the panel,
145    // such that captions are correctly displayed at the bottom of the video
146    // at the end of the fadeout transition.
147    double duration = document().page() ? document().page()->theme().mediaControlsFadeOutDuration() : 0;
148    m_transitionTimer.startOneShot(duration);
149}
150
151void MediaControlPanelElement::stopTimer()
152{
153    if (m_transitionTimer.isActive())
154        m_transitionTimer.stop();
155}
156
157void MediaControlPanelElement::transitionTimerFired(Timer<MediaControlPanelElement>&)
158{
159    if (!m_opaque)
160        hide();
161
162    stopTimer();
163}
164
165void MediaControlPanelElement::setPosition(const LayoutPoint& position)
166{
167    double left = position.x();
168    double top = position.y();
169
170    // Set the left and top to control the panel's position; this depends on it being absolute positioned.
171    // Set the margin to zero since the position passed in will already include the effect of the margin.
172    setInlineStyleProperty(CSSPropertyLeft, left, CSSPrimitiveValue::CSS_PX);
173    setInlineStyleProperty(CSSPropertyTop, top, CSSPrimitiveValue::CSS_PX);
174    setInlineStyleProperty(CSSPropertyMarginLeft, 0.0, CSSPrimitiveValue::CSS_PX);
175    setInlineStyleProperty(CSSPropertyMarginTop, 0.0, CSSPrimitiveValue::CSS_PX);
176
177    classList()->add("dragged", IGNORE_EXCEPTION);
178}
179
180void MediaControlPanelElement::resetPosition()
181{
182    removeInlineStyleProperty(CSSPropertyLeft);
183    removeInlineStyleProperty(CSSPropertyTop);
184    removeInlineStyleProperty(CSSPropertyMarginLeft);
185    removeInlineStyleProperty(CSSPropertyMarginTop);
186
187    classList()->remove("dragged", IGNORE_EXCEPTION);
188
189    m_cumulativeDragOffset.setX(0);
190    m_cumulativeDragOffset.setY(0);
191}
192
193void MediaControlPanelElement::makeOpaque()
194{
195    if (m_opaque)
196        return;
197
198    double duration = document().page() ? document().page()->theme().mediaControlsFadeInDuration() : 0;
199
200    setInlineStyleProperty(CSSPropertyWebkitTransitionProperty, CSSPropertyOpacity);
201    setInlineStyleProperty(CSSPropertyWebkitTransitionDuration, duration, CSSPrimitiveValue::CSS_S);
202    setInlineStyleProperty(CSSPropertyOpacity, 1.0, CSSPrimitiveValue::CSS_NUMBER);
203
204    m_opaque = true;
205
206    if (m_isDisplayed)
207        show();
208}
209
210void MediaControlPanelElement::makeTransparent()
211{
212    if (!m_opaque)
213        return;
214
215    double duration = document().page() ? document().page()->theme().mediaControlsFadeOutDuration() : 0;
216
217    setInlineStyleProperty(CSSPropertyWebkitTransitionProperty, CSSPropertyOpacity);
218    setInlineStyleProperty(CSSPropertyWebkitTransitionDuration, duration, CSSPrimitiveValue::CSS_S);
219    setInlineStyleProperty(CSSPropertyOpacity, 0.0, CSSPrimitiveValue::CSS_NUMBER);
220
221    m_opaque = false;
222    startTimer();
223}
224
225void MediaControlPanelElement::defaultEventHandler(Event* event)
226{
227    MediaControlDivElement::defaultEventHandler(event);
228
229    if (event->isMouseEvent()) {
230        LayoutPoint location = toMouseEvent(event)->absoluteLocation();
231        if (event->type() == eventNames().mousedownEvent && event->target() == this) {
232            startDrag(location);
233            event->setDefaultHandled();
234        } else if (event->type() == eventNames().mousemoveEvent && m_isBeingDragged)
235            continueDrag(location);
236        else if (event->type() == eventNames().mouseupEvent && m_isBeingDragged) {
237            continueDrag(location);
238            endDrag();
239            event->setDefaultHandled();
240        }
241    }
242}
243
244void MediaControlPanelElement::setCanBeDragged(bool canBeDragged)
245{
246    if (m_canBeDragged == canBeDragged)
247        return;
248
249    m_canBeDragged = canBeDragged;
250
251    if (!canBeDragged)
252        endDrag();
253}
254
255void MediaControlPanelElement::setIsDisplayed(bool isDisplayed)
256{
257    m_isDisplayed = isDisplayed;
258}
259
260// ----------------------------
261
262MediaControlPanelEnclosureElement::MediaControlPanelEnclosureElement(Document& document)
263    // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
264    : MediaControlDivElement(document, MediaControlsPanel)
265{
266    setPseudo(shadowPseudoId());
267}
268
269PassRefPtr<MediaControlPanelEnclosureElement> MediaControlPanelEnclosureElement::create(Document& document)
270{
271    return adoptRef(new MediaControlPanelEnclosureElement(document));
272}
273
274const AtomicString& MediaControlPanelEnclosureElement::shadowPseudoId() const
275{
276    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-enclosure", AtomicString::ConstructFromLiteral));
277    return id;
278}
279
280// ----------------------------
281
282MediaControlOverlayEnclosureElement::MediaControlOverlayEnclosureElement(Document& document)
283    // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
284    : MediaControlDivElement(document, MediaControlsPanel)
285{
286}
287
288PassRefPtr<MediaControlOverlayEnclosureElement> MediaControlOverlayEnclosureElement::create(Document& document)
289{
290    return adoptRef(new MediaControlOverlayEnclosureElement(document));
291}
292
293const AtomicString& MediaControlOverlayEnclosureElement::shadowPseudoId() const
294{
295    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-overlay-enclosure", AtomicString::ConstructFromLiteral));
296    return id;
297}
298
299// ----------------------------
300
301MediaControlTimelineContainerElement::MediaControlTimelineContainerElement(Document& document)
302    : MediaControlDivElement(document, MediaTimelineContainer)
303{
304    setPseudo(shadowPseudoId());
305}
306
307PassRefPtr<MediaControlTimelineContainerElement> MediaControlTimelineContainerElement::create(Document& document)
308{
309    RefPtr<MediaControlTimelineContainerElement> element = adoptRef(new MediaControlTimelineContainerElement(document));
310    element->hide();
311    return element.release();
312}
313
314const AtomicString& MediaControlTimelineContainerElement::shadowPseudoId() const
315{
316    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-timeline-container", AtomicString::ConstructFromLiteral));
317    return id;
318}
319
320void MediaControlTimelineContainerElement::setTimeDisplaysHidden(bool hidden)
321{
322    for (unsigned i = 0; i < childNodeCount(); ++i) {
323        Node* child = childNode(i);
324        if (!child || !child->isElementNode())
325            continue;
326        Element* element = toElement(child);
327        if (element->shadowPseudoId() != getMediaControlTimeRemainingDisplayElementShadowPseudoId()
328            && element->shadowPseudoId() != getMediaControlCurrentTimeDisplayElementShadowPseudoId())
329            continue;
330
331        MediaControlTimeDisplayElement* timeDisplay = static_cast<MediaControlTimeDisplayElement*>(element);
332        if (hidden)
333            timeDisplay->hide();
334        else
335            timeDisplay->show();
336    }
337}
338
339RenderPtr<RenderElement> MediaControlTimelineContainerElement::createElementRenderer(PassRef<RenderStyle> style)
340{
341    return createRenderer<RenderMediaControlTimelineContainer>(*this, WTF::move(style));
342}
343
344// ----------------------------
345
346MediaControlVolumeSliderContainerElement::MediaControlVolumeSliderContainerElement(Document& document)
347    : MediaControlDivElement(document, MediaVolumeSliderContainer)
348{
349    setPseudo(shadowPseudoId());
350}
351
352PassRefPtr<MediaControlVolumeSliderContainerElement> MediaControlVolumeSliderContainerElement::create(Document& document)
353{
354    RefPtr<MediaControlVolumeSliderContainerElement> element = adoptRef(new MediaControlVolumeSliderContainerElement(document));
355    element->hide();
356    return element.release();
357}
358
359RenderPtr<RenderElement> MediaControlVolumeSliderContainerElement::createElementRenderer(PassRef<RenderStyle> style)
360{
361    return createRenderer<RenderMediaVolumeSliderContainer>(*this, WTF::move(style));
362}
363
364void MediaControlVolumeSliderContainerElement::defaultEventHandler(Event* event)
365{
366    if (!event->isMouseEvent() || event->type() != eventNames().mouseoutEvent)
367        return;
368
369    // Poor man's mouseleave event detection.
370    MouseEvent* mouseEvent = toMouseEvent(event);
371    EventTarget* relatedTarget = mouseEvent->relatedTarget();
372    if (!relatedTarget || !relatedTarget->toNode())
373        return;
374
375    if (this->containsIncludingShadowDOM(relatedTarget->toNode()))
376        return;
377
378    hide();
379}
380
381const AtomicString& MediaControlVolumeSliderContainerElement::shadowPseudoId() const
382{
383    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-volume-slider-container", AtomicString::ConstructFromLiteral));
384    return id;
385}
386
387// ----------------------------
388
389MediaControlStatusDisplayElement::MediaControlStatusDisplayElement(Document& document)
390    : MediaControlDivElement(document, MediaStatusDisplay)
391    , m_stateBeingDisplayed(Nothing)
392{
393    setPseudo(shadowPseudoId());
394}
395
396PassRefPtr<MediaControlStatusDisplayElement> MediaControlStatusDisplayElement::create(Document& document)
397{
398    RefPtr<MediaControlStatusDisplayElement> element = adoptRef(new MediaControlStatusDisplayElement(document));
399    element->hide();
400    return element.release();
401}
402
403void MediaControlStatusDisplayElement::update()
404{
405    // Get the new state that we'll have to display.
406    StateBeingDisplayed newStateToDisplay = Nothing;
407
408    if (mediaController()->readyState() <= MediaControllerInterface::HAVE_METADATA && mediaController()->hasCurrentSrc())
409        newStateToDisplay = Loading;
410    else if (mediaController()->isLiveStream())
411        newStateToDisplay = LiveBroadcast;
412
413    if (newStateToDisplay == m_stateBeingDisplayed)
414        return;
415
416    if (m_stateBeingDisplayed == Nothing)
417        show();
418    else if (newStateToDisplay == Nothing)
419        hide();
420
421    m_stateBeingDisplayed = newStateToDisplay;
422
423    switch (m_stateBeingDisplayed) {
424    case Nothing:
425        setInnerText("", IGNORE_EXCEPTION);
426        break;
427    case Loading:
428        setInnerText(mediaElementLoadingStateText(), IGNORE_EXCEPTION);
429        break;
430    case LiveBroadcast:
431        setInnerText(mediaElementLiveBroadcastStateText(), IGNORE_EXCEPTION);
432        break;
433    }
434}
435
436const AtomicString& MediaControlStatusDisplayElement::shadowPseudoId() const
437{
438    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-status-display", AtomicString::ConstructFromLiteral));
439    return id;
440}
441
442
443// ----------------------------
444
445MediaControlPanelMuteButtonElement::MediaControlPanelMuteButtonElement(Document& document, MediaControls* controls)
446    : MediaControlMuteButtonElement(document, MediaMuteButton)
447    , m_controls(controls)
448{
449    setPseudo(shadowPseudoId());
450}
451
452PassRefPtr<MediaControlPanelMuteButtonElement> MediaControlPanelMuteButtonElement::create(Document& document, MediaControls* controls)
453{
454    ASSERT(controls);
455
456    RefPtr<MediaControlPanelMuteButtonElement> button = adoptRef(new MediaControlPanelMuteButtonElement(document, controls));
457    button->ensureUserAgentShadowRoot();
458    button->setType("button");
459    return button.release();
460}
461
462void MediaControlPanelMuteButtonElement::defaultEventHandler(Event* event)
463{
464    if (event->type() == eventNames().mouseoverEvent)
465        m_controls->showVolumeSlider();
466
467    MediaControlMuteButtonElement::defaultEventHandler(event);
468}
469
470const AtomicString& MediaControlPanelMuteButtonElement::shadowPseudoId() const
471{
472    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-mute-button", AtomicString::ConstructFromLiteral));
473    return id;
474}
475
476// ----------------------------
477
478MediaControlVolumeSliderMuteButtonElement::MediaControlVolumeSliderMuteButtonElement(Document& document)
479    : MediaControlMuteButtonElement(document, MediaMuteButton)
480{
481    setPseudo(shadowPseudoId());
482}
483
484PassRefPtr<MediaControlVolumeSliderMuteButtonElement> MediaControlVolumeSliderMuteButtonElement::create(Document& document)
485{
486    RefPtr<MediaControlVolumeSliderMuteButtonElement> button = adoptRef(new MediaControlVolumeSliderMuteButtonElement(document));
487    button->ensureUserAgentShadowRoot();
488    button->setType("button");
489    return button.release();
490}
491
492const AtomicString& MediaControlVolumeSliderMuteButtonElement::shadowPseudoId() const
493{
494    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-volume-slider-mute-button", AtomicString::ConstructFromLiteral));
495    return id;
496}
497
498// ----------------------------
499
500MediaControlPlayButtonElement::MediaControlPlayButtonElement(Document& document)
501    : MediaControlInputElement(document, MediaPlayButton)
502{
503    setPseudo(shadowPseudoId());
504}
505
506PassRefPtr<MediaControlPlayButtonElement> MediaControlPlayButtonElement::create(Document& document)
507{
508    RefPtr<MediaControlPlayButtonElement> button = adoptRef(new MediaControlPlayButtonElement(document));
509    button->ensureUserAgentShadowRoot();
510    button->setType("button");
511    return button.release();
512}
513
514void MediaControlPlayButtonElement::defaultEventHandler(Event* event)
515{
516    if (event->type() == eventNames().clickEvent) {
517        if (mediaController()->canPlay())
518            mediaController()->play();
519        else
520            mediaController()->pause();
521        updateDisplayType();
522        event->setDefaultHandled();
523    }
524    HTMLInputElement::defaultEventHandler(event);
525}
526
527void MediaControlPlayButtonElement::updateDisplayType()
528{
529    setDisplayType(mediaController()->canPlay() ? MediaPlayButton : MediaPauseButton);
530}
531
532const AtomicString& MediaControlPlayButtonElement::shadowPseudoId() const
533{
534    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-play-button", AtomicString::ConstructFromLiteral));
535    return id;
536}
537
538// ----------------------------
539
540MediaControlOverlayPlayButtonElement::MediaControlOverlayPlayButtonElement(Document& document)
541    : MediaControlInputElement(document, MediaOverlayPlayButton)
542{
543    setPseudo(shadowPseudoId());
544}
545
546PassRefPtr<MediaControlOverlayPlayButtonElement> MediaControlOverlayPlayButtonElement::create(Document& document)
547{
548    RefPtr<MediaControlOverlayPlayButtonElement> button = adoptRef(new MediaControlOverlayPlayButtonElement(document));
549    button->ensureUserAgentShadowRoot();
550    button->setType("button");
551    return button.release();
552}
553
554void MediaControlOverlayPlayButtonElement::defaultEventHandler(Event* event)
555{
556    if (event->type() == eventNames().clickEvent && mediaController()->canPlay()) {
557        mediaController()->play();
558        updateDisplayType();
559        event->setDefaultHandled();
560    }
561    HTMLInputElement::defaultEventHandler(event);
562}
563
564void MediaControlOverlayPlayButtonElement::updateDisplayType()
565{
566    if (mediaController()->canPlay()) {
567        show();
568    } else
569        hide();
570}
571
572const AtomicString& MediaControlOverlayPlayButtonElement::shadowPseudoId() const
573{
574    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-overlay-play-button", AtomicString::ConstructFromLiteral));
575    return id;
576}
577
578
579// ----------------------------
580
581MediaControlSeekForwardButtonElement::MediaControlSeekForwardButtonElement(Document& document)
582    : MediaControlSeekButtonElement(document, MediaSeekForwardButton)
583{
584    setPseudo(shadowPseudoId());
585}
586
587PassRefPtr<MediaControlSeekForwardButtonElement> MediaControlSeekForwardButtonElement::create(Document& document)
588{
589    RefPtr<MediaControlSeekForwardButtonElement> button = adoptRef(new MediaControlSeekForwardButtonElement(document));
590    button->ensureUserAgentShadowRoot();
591    button->setType("button");
592    return button.release();
593}
594
595const AtomicString& MediaControlSeekForwardButtonElement::shadowPseudoId() const
596{
597    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-seek-forward-button", AtomicString::ConstructFromLiteral));
598    return id;
599}
600
601// ----------------------------
602
603MediaControlSeekBackButtonElement::MediaControlSeekBackButtonElement(Document& document)
604    : MediaControlSeekButtonElement(document, MediaSeekBackButton)
605{
606    setPseudo(shadowPseudoId());
607}
608
609PassRefPtr<MediaControlSeekBackButtonElement> MediaControlSeekBackButtonElement::create(Document& document)
610{
611    RefPtr<MediaControlSeekBackButtonElement> button = adoptRef(new MediaControlSeekBackButtonElement(document));
612    button->ensureUserAgentShadowRoot();
613    button->setType("button");
614    return button.release();
615}
616
617const AtomicString& MediaControlSeekBackButtonElement::shadowPseudoId() const
618{
619    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-seek-back-button", AtomicString::ConstructFromLiteral));
620    return id;
621}
622
623// ----------------------------
624
625MediaControlRewindButtonElement::MediaControlRewindButtonElement(Document& document)
626    : MediaControlInputElement(document, MediaRewindButton)
627{
628    setPseudo(shadowPseudoId());
629}
630
631PassRefPtr<MediaControlRewindButtonElement> MediaControlRewindButtonElement::create(Document& document)
632{
633    RefPtr<MediaControlRewindButtonElement> button = adoptRef(new MediaControlRewindButtonElement(document));
634    button->ensureUserAgentShadowRoot();
635    button->setType("button");
636    return button.release();
637}
638
639void MediaControlRewindButtonElement::defaultEventHandler(Event* event)
640{
641    if (event->type() == eventNames().clickEvent) {
642        mediaController()->setCurrentTime(std::max<double>(0, mediaController()->currentTime() - 30));
643        event->setDefaultHandled();
644    }
645    HTMLInputElement::defaultEventHandler(event);
646}
647
648const AtomicString& MediaControlRewindButtonElement::shadowPseudoId() const
649{
650    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-rewind-button", AtomicString::ConstructFromLiteral));
651    return id;
652}
653
654// ----------------------------
655
656MediaControlReturnToRealtimeButtonElement::MediaControlReturnToRealtimeButtonElement(Document& document)
657    : MediaControlInputElement(document, MediaReturnToRealtimeButton)
658{
659    setPseudo(shadowPseudoId());
660}
661
662PassRefPtr<MediaControlReturnToRealtimeButtonElement> MediaControlReturnToRealtimeButtonElement::create(Document& document)
663{
664    RefPtr<MediaControlReturnToRealtimeButtonElement> button = adoptRef(new MediaControlReturnToRealtimeButtonElement(document));
665    button->ensureUserAgentShadowRoot();
666    button->setType("button");
667    button->hide();
668    return button.release();
669}
670
671void MediaControlReturnToRealtimeButtonElement::defaultEventHandler(Event* event)
672{
673    if (event->type() == eventNames().clickEvent) {
674        mediaController()->returnToRealtime();
675        event->setDefaultHandled();
676    }
677    HTMLInputElement::defaultEventHandler(event);
678}
679
680const AtomicString& MediaControlReturnToRealtimeButtonElement::shadowPseudoId() const
681{
682    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-return-to-realtime-button", AtomicString::ConstructFromLiteral));
683    return id;
684}
685
686// ----------------------------
687
688MediaControlToggleClosedCaptionsButtonElement::MediaControlToggleClosedCaptionsButtonElement(Document& document, MediaControls* controls)
689    : MediaControlInputElement(document, MediaShowClosedCaptionsButton)
690#if PLATFORM(COCOA) || PLATFORM(WIN) || PLATFORM(GTK)
691    , m_controls(controls)
692#endif
693{
694#if !PLATFORM(COCOA) && !PLATFORM(WIN) || !PLATFORM(GTK)
695    UNUSED_PARAM(controls);
696#endif
697    setPseudo(shadowPseudoId());
698}
699
700PassRefPtr<MediaControlToggleClosedCaptionsButtonElement> MediaControlToggleClosedCaptionsButtonElement::create(Document& document, MediaControls* controls)
701{
702    ASSERT(controls);
703
704    RefPtr<MediaControlToggleClosedCaptionsButtonElement> button = adoptRef(new MediaControlToggleClosedCaptionsButtonElement(document, controls));
705    button->ensureUserAgentShadowRoot();
706    button->setType("button");
707    button->hide();
708    return button.release();
709}
710
711void MediaControlToggleClosedCaptionsButtonElement::updateDisplayType()
712{
713    bool captionsVisible = mediaController()->closedCaptionsVisible();
714    setDisplayType(captionsVisible ? MediaHideClosedCaptionsButton : MediaShowClosedCaptionsButton);
715    setChecked(captionsVisible);
716}
717
718void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event* event)
719{
720    if (event->type() == eventNames().clickEvent) {
721        // FIXME: It's not great that the shared code is dictating behavior of platform-specific
722        // UI. Not all ports may want the closed captions button to toggle a list of tracks, so
723        // we have to use #if.
724        // https://bugs.webkit.org/show_bug.cgi?id=101877
725#if !PLATFORM(COCOA) && !PLATFORM(WIN) && !PLATFORM(GTK)
726        mediaController()->setClosedCaptionsVisible(!mediaController()->closedCaptionsVisible());
727        setChecked(mediaController()->closedCaptionsVisible());
728        updateDisplayType();
729#else
730        m_controls->toggleClosedCaptionTrackList();
731#endif
732        event->setDefaultHandled();
733    }
734
735    HTMLInputElement::defaultEventHandler(event);
736}
737
738const AtomicString& MediaControlToggleClosedCaptionsButtonElement::shadowPseudoId() const
739{
740    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-toggle-closed-captions-button", AtomicString::ConstructFromLiteral));
741    return id;
742}
743
744// ----------------------------
745
746MediaControlClosedCaptionsContainerElement::MediaControlClosedCaptionsContainerElement(Document& document)
747    : MediaControlDivElement(document, MediaClosedCaptionsContainer)
748{
749    setPseudo(shadowPseudoId());
750}
751
752PassRefPtr<MediaControlClosedCaptionsContainerElement> MediaControlClosedCaptionsContainerElement::create(Document& document)
753{
754    RefPtr<MediaControlClosedCaptionsContainerElement> element = adoptRef(new MediaControlClosedCaptionsContainerElement(document));
755    element->setAttribute(dirAttr, "auto");
756    element->hide();
757    return element.release();
758}
759
760const AtomicString& MediaControlClosedCaptionsContainerElement::shadowPseudoId() const
761{
762    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-closed-captions-container", AtomicString::ConstructFromLiteral));
763    return id;
764}
765
766// ----------------------------
767
768MediaControlClosedCaptionsTrackListElement::MediaControlClosedCaptionsTrackListElement(Document& document, MediaControls* controls)
769    : MediaControlDivElement(document, MediaClosedCaptionsTrackList)
770#if ENABLE(VIDEO_TRACK)
771    , m_controls(controls)
772#endif
773{
774#if !ENABLE(VIDEO_TRACK)
775    UNUSED_PARAM(controls);
776#endif
777    setPseudo(shadowPseudoId());
778}
779
780PassRefPtr<MediaControlClosedCaptionsTrackListElement> MediaControlClosedCaptionsTrackListElement::create(Document& document, MediaControls* controls)
781{
782    ASSERT(controls);
783    RefPtr<MediaControlClosedCaptionsTrackListElement> element = adoptRef(new MediaControlClosedCaptionsTrackListElement(document, controls));
784    return element.release();
785}
786
787void MediaControlClosedCaptionsTrackListElement::defaultEventHandler(Event* event)
788{
789#if ENABLE(VIDEO_TRACK)
790    if (event->type() == eventNames().clickEvent) {
791        Node* target = event->target()->toNode();
792        if (!target || !target->isElementNode())
793            return;
794
795        // When we created the elements in the track list, we gave them a custom
796        // attribute representing the index in the HTMLMediaElement's list of tracks.
797        // Check if the event target has such a custom element and, if so,
798        // tell the HTMLMediaElement to enable that track.
799
800        RefPtr<TextTrack> textTrack;
801        MenuItemToTrackMap::iterator iter = m_menuToTrackMap.find(toElement(target));
802        if (iter != m_menuToTrackMap.end())
803            textTrack = iter->value;
804        m_menuToTrackMap.clear();
805        m_controls->toggleClosedCaptionTrackList();
806        if (!textTrack)
807            return;
808
809        HTMLMediaElement* mediaElement = parentMediaElement(this);
810        if (!mediaElement)
811            return;
812
813        mediaElement->setSelectedTextTrack(textTrack.get());
814
815        updateDisplay();
816    }
817
818    MediaControlDivElement::defaultEventHandler(event);
819#else
820    UNUSED_PARAM(event);
821#endif
822}
823
824const AtomicString& MediaControlClosedCaptionsTrackListElement::shadowPseudoId() const
825{
826    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-closed-captions-track-list", AtomicString::ConstructFromLiteral));
827    return id;
828}
829
830void MediaControlClosedCaptionsTrackListElement::updateDisplay()
831{
832#if ENABLE(VIDEO_TRACK)
833    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, selectedClassValue, ("selected", AtomicString::ConstructFromLiteral));
834
835    if (!mediaController()->hasClosedCaptions())
836        return;
837
838    if (!document().page())
839        return;
840    CaptionUserPreferences::CaptionDisplayMode displayMode = document().page()->group().captionPreferences()->captionDisplayMode();
841
842    HTMLMediaElement* mediaElement = parentMediaElement(this);
843    if (!mediaElement)
844        return;
845
846    TextTrackList* trackList = mediaElement->textTracks();
847    if (!trackList || !trackList->length())
848        return;
849
850    rebuildTrackListMenu();
851
852    RefPtr<Element> offMenuItem;
853    bool trackMenuItemSelected = false;
854
855    for (unsigned i = 0, length = m_menuItems.size(); i < length; ++i) {
856        RefPtr<Element> trackItem = m_menuItems[i];
857
858        RefPtr<TextTrack> textTrack;
859        MenuItemToTrackMap::iterator iter = m_menuToTrackMap.find(trackItem.get());
860        if (iter == m_menuToTrackMap.end())
861            continue;
862        textTrack = iter->value;
863        if (!textTrack)
864            continue;
865
866        if (textTrack == TextTrack::captionMenuOffItem()) {
867            offMenuItem = trackItem;
868            continue;
869        }
870
871        if (textTrack == TextTrack::captionMenuAutomaticItem()) {
872            if (displayMode == CaptionUserPreferences::Automatic)
873                trackItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION);
874            else
875                trackItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION);
876            continue;
877        }
878
879        if (displayMode != CaptionUserPreferences::Automatic && textTrack->mode() == TextTrack::showingKeyword()) {
880            trackMenuItemSelected = true;
881            trackItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION);
882        } else
883            trackItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION);
884    }
885
886    if (offMenuItem) {
887        if (displayMode == CaptionUserPreferences::ForcedOnly && !trackMenuItemSelected)
888            offMenuItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION);
889        else
890            offMenuItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION);
891    }
892#endif
893}
894
895void MediaControlClosedCaptionsTrackListElement::rebuildTrackListMenu()
896{
897#if ENABLE(VIDEO_TRACK)
898    // Remove any existing content.
899    removeChildren();
900    m_menuItems.clear();
901    m_menuToTrackMap.clear();
902
903    if (!mediaController()->hasClosedCaptions())
904        return;
905
906    HTMLMediaElement* mediaElement = parentMediaElement(this);
907    if (!mediaElement)
908        return;
909
910    TextTrackList* trackList = mediaElement->textTracks();
911    if (!trackList || !trackList->length())
912        return;
913
914    if (!document().page())
915        return;
916    CaptionUserPreferences* captionPreferences = document().page()->group().captionPreferences();
917    Vector<RefPtr<TextTrack>> tracksForMenu = captionPreferences->sortedTrackListForMenu(trackList);
918
919    RefPtr<Element> captionsHeader = document().createElement(h3Tag, ASSERT_NO_EXCEPTION);
920    captionsHeader->appendChild(document().createTextNode(textTrackSubtitlesText()));
921    appendChild(captionsHeader);
922    RefPtr<Element> captionsMenuList = document().createElement(ulTag, ASSERT_NO_EXCEPTION);
923
924    for (unsigned i = 0, length = tracksForMenu.size(); i < length; ++i) {
925        RefPtr<TextTrack> textTrack = tracksForMenu[i];
926        RefPtr<Element> menuItem = document().createElement(liTag, ASSERT_NO_EXCEPTION);
927        menuItem->appendChild(document().createTextNode(captionPreferences->displayNameForTrack(textTrack.get())));
928        captionsMenuList->appendChild(menuItem);
929        m_menuItems.append(menuItem);
930        m_menuToTrackMap.add(menuItem, textTrack);
931    }
932
933    appendChild(captionsMenuList);
934#endif
935}
936
937// ----------------------------
938
939MediaControlTimelineElement::MediaControlTimelineElement(Document& document, MediaControls* controls)
940    : MediaControlInputElement(document, MediaSlider)
941    , m_controls(controls)
942{
943    setPseudo(shadowPseudoId());
944}
945
946PassRefPtr<MediaControlTimelineElement> MediaControlTimelineElement::create(Document& document, MediaControls* controls)
947{
948    ASSERT(controls);
949
950    RefPtr<MediaControlTimelineElement> timeline = adoptRef(new MediaControlTimelineElement(document, controls));
951    timeline->ensureUserAgentShadowRoot();
952    timeline->setType("range");
953    timeline->setAttribute(precisionAttr, "float");
954    return timeline.release();
955}
956
957void MediaControlTimelineElement::defaultEventHandler(Event* event)
958{
959    // Left button is 0. Rejects mouse events not from left button.
960    if (event->isMouseEvent() && toMouseEvent(event)->button())
961        return;
962
963    if (!renderer())
964        return;
965
966    if (event->type() == eventNames().mousedownEvent)
967        mediaController()->beginScrubbing();
968
969    if (event->type() == eventNames().mouseupEvent)
970        mediaController()->endScrubbing();
971
972    MediaControlInputElement::defaultEventHandler(event);
973
974    if (event->type() == eventNames().mouseoverEvent || event->type() == eventNames().mouseoutEvent || event->type() == eventNames().mousemoveEvent)
975        return;
976
977    double time = value().toDouble();
978    if (event->type() == eventNames().inputEvent && time != mediaController()->currentTime())
979        mediaController()->setCurrentTime(time);
980
981    RenderSlider* slider = toRenderSlider(renderer());
982    if (slider && slider->inDragMode())
983        m_controls->updateCurrentTimeDisplay();
984}
985
986#if !PLATFORM(IOS)
987bool MediaControlTimelineElement::willRespondToMouseClickEvents()
988{
989    if (!renderer())
990        return false;
991
992    return true;
993}
994#endif // !PLATFORM(IOS)
995
996void MediaControlTimelineElement::setPosition(double currentTime)
997{
998    setValue(String::number(currentTime));
999}
1000
1001void MediaControlTimelineElement::setDuration(double duration)
1002{
1003    setAttribute(maxAttr, AtomicString::number(std::isfinite(duration) ? duration : 0));
1004}
1005
1006
1007const AtomicString& MediaControlTimelineElement::shadowPseudoId() const
1008{
1009    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-timeline", AtomicString::ConstructFromLiteral));
1010    return id;
1011}
1012
1013// ----------------------------
1014
1015MediaControlPanelVolumeSliderElement::MediaControlPanelVolumeSliderElement(Document& document)
1016    : MediaControlVolumeSliderElement(document)
1017{
1018    setPseudo(shadowPseudoId());
1019}
1020
1021PassRefPtr<MediaControlPanelVolumeSliderElement> MediaControlPanelVolumeSliderElement::create(Document& document)
1022{
1023    RefPtr<MediaControlPanelVolumeSliderElement> slider = adoptRef(new MediaControlPanelVolumeSliderElement(document));
1024    slider->ensureUserAgentShadowRoot();
1025    slider->setType("range");
1026    slider->setAttribute(precisionAttr, "float");
1027    slider->setAttribute(maxAttr, "1");
1028    return slider.release();
1029}
1030
1031const AtomicString& MediaControlPanelVolumeSliderElement::shadowPseudoId() const
1032{
1033    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-volume-slider", AtomicString::ConstructFromLiteral));
1034    return id;
1035}
1036
1037// ----------------------------
1038
1039MediaControlFullscreenVolumeSliderElement::MediaControlFullscreenVolumeSliderElement(Document& document)
1040    : MediaControlVolumeSliderElement(document)
1041{
1042    setPseudo(shadowPseudoId());
1043}
1044
1045PassRefPtr<MediaControlFullscreenVolumeSliderElement> MediaControlFullscreenVolumeSliderElement::create(Document& document)
1046{
1047    RefPtr<MediaControlFullscreenVolumeSliderElement> slider = adoptRef(new MediaControlFullscreenVolumeSliderElement(document));
1048    slider->ensureUserAgentShadowRoot();
1049    slider->setType("range");
1050    slider->setAttribute(precisionAttr, "float");
1051    slider->setAttribute(maxAttr, "1");
1052    return slider.release();
1053}
1054
1055const AtomicString& MediaControlFullscreenVolumeSliderElement::shadowPseudoId() const
1056{
1057    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-volume-slider", AtomicString::ConstructFromLiteral));
1058    return id;
1059}
1060
1061// ----------------------------
1062
1063MediaControlFullscreenButtonElement::MediaControlFullscreenButtonElement(Document& document)
1064    : MediaControlInputElement(document, MediaEnterFullscreenButton)
1065{
1066    setPseudo(shadowPseudoId());
1067}
1068
1069PassRefPtr<MediaControlFullscreenButtonElement> MediaControlFullscreenButtonElement::create(Document& document)
1070{
1071    RefPtr<MediaControlFullscreenButtonElement> button = adoptRef(new MediaControlFullscreenButtonElement(document));
1072    button->ensureUserAgentShadowRoot();
1073    button->setType("button");
1074    button->hide();
1075    return button.release();
1076}
1077
1078void MediaControlFullscreenButtonElement::defaultEventHandler(Event* event)
1079{
1080    if (event->type() == eventNames().clickEvent) {
1081#if ENABLE(FULLSCREEN_API)
1082        // Only use the new full screen API if the fullScreenEnabled setting has
1083        // been explicitly enabled. Otherwise, use the old fullscreen API. This
1084        // allows apps which embed a WebView to retain the existing full screen
1085        // video implementation without requiring them to implement their own full
1086        // screen behavior.
1087        if (document().settings() && document().settings()->fullScreenEnabled()) {
1088            if (document().webkitIsFullScreen() && document().webkitCurrentFullScreenElement() == parentMediaElement(this))
1089                document().webkitCancelFullScreen();
1090            else
1091                document().requestFullScreenForElement(parentMediaElement(this), 0, Document::ExemptIFrameAllowFullScreenRequirement);
1092        } else
1093#endif
1094            mediaController()->enterFullscreen();
1095        event->setDefaultHandled();
1096    }
1097    HTMLInputElement::defaultEventHandler(event);
1098}
1099
1100const AtomicString& MediaControlFullscreenButtonElement::shadowPseudoId() const
1101{
1102    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-button", AtomicString::ConstructFromLiteral));
1103    return id;
1104}
1105
1106void MediaControlFullscreenButtonElement::setIsFullscreen(bool isFullscreen)
1107{
1108    setDisplayType(isFullscreen ? MediaExitFullscreenButton : MediaEnterFullscreenButton);
1109}
1110
1111// ----------------------------
1112
1113MediaControlFullscreenVolumeMinButtonElement::MediaControlFullscreenVolumeMinButtonElement(Document& document)
1114    : MediaControlInputElement(document, MediaUnMuteButton)
1115{
1116    setPseudo(shadowPseudoId());
1117}
1118
1119PassRefPtr<MediaControlFullscreenVolumeMinButtonElement> MediaControlFullscreenVolumeMinButtonElement::create(Document& document)
1120{
1121    RefPtr<MediaControlFullscreenVolumeMinButtonElement> button = adoptRef(new MediaControlFullscreenVolumeMinButtonElement(document));
1122    button->ensureUserAgentShadowRoot();
1123    button->setType("button");
1124    return button.release();
1125}
1126
1127void MediaControlFullscreenVolumeMinButtonElement::defaultEventHandler(Event* event)
1128{
1129    if (event->type() == eventNames().clickEvent) {
1130        ExceptionCode code = 0;
1131        mediaController()->setVolume(0, code);
1132        event->setDefaultHandled();
1133    }
1134    HTMLInputElement::defaultEventHandler(event);
1135}
1136
1137const AtomicString& MediaControlFullscreenVolumeMinButtonElement::shadowPseudoId() const
1138{
1139    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-volume-min-button", AtomicString::ConstructFromLiteral));
1140    return id;
1141}
1142
1143// ----------------------------
1144
1145MediaControlFullscreenVolumeMaxButtonElement::MediaControlFullscreenVolumeMaxButtonElement(Document& document)
1146: MediaControlInputElement(document, MediaMuteButton)
1147{
1148    setPseudo(shadowPseudoId());
1149}
1150
1151PassRefPtr<MediaControlFullscreenVolumeMaxButtonElement> MediaControlFullscreenVolumeMaxButtonElement::create(Document& document)
1152{
1153    RefPtr<MediaControlFullscreenVolumeMaxButtonElement> button = adoptRef(new MediaControlFullscreenVolumeMaxButtonElement(document));
1154    button->ensureUserAgentShadowRoot();
1155    button->setType("button");
1156    return button.release();
1157}
1158
1159void MediaControlFullscreenVolumeMaxButtonElement::defaultEventHandler(Event* event)
1160{
1161    if (event->type() == eventNames().clickEvent) {
1162        ExceptionCode code = 0;
1163        mediaController()->setVolume(1, code);
1164        event->setDefaultHandled();
1165    }
1166    HTMLInputElement::defaultEventHandler(event);
1167}
1168
1169const AtomicString& MediaControlFullscreenVolumeMaxButtonElement::shadowPseudoId() const
1170{
1171    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-volume-max-button", AtomicString::ConstructFromLiteral));
1172    return id;
1173}
1174
1175// ----------------------------
1176
1177MediaControlTimeRemainingDisplayElement::MediaControlTimeRemainingDisplayElement(Document& document)
1178    : MediaControlTimeDisplayElement(document, MediaTimeRemainingDisplay)
1179{
1180    setPseudo(shadowPseudoId());
1181}
1182
1183PassRefPtr<MediaControlTimeRemainingDisplayElement> MediaControlTimeRemainingDisplayElement::create(Document& document)
1184{
1185    return adoptRef(new MediaControlTimeRemainingDisplayElement(document));
1186}
1187
1188static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId()
1189{
1190    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-time-remaining-display", AtomicString::ConstructFromLiteral));
1191    return id;
1192}
1193
1194const AtomicString& MediaControlTimeRemainingDisplayElement::shadowPseudoId() const
1195{
1196    return getMediaControlTimeRemainingDisplayElementShadowPseudoId();
1197}
1198
1199// ----------------------------
1200
1201MediaControlCurrentTimeDisplayElement::MediaControlCurrentTimeDisplayElement(Document& document)
1202    : MediaControlTimeDisplayElement(document, MediaCurrentTimeDisplay)
1203{
1204    setPseudo(shadowPseudoId());
1205}
1206
1207PassRefPtr<MediaControlCurrentTimeDisplayElement> MediaControlCurrentTimeDisplayElement::create(Document& document)
1208{
1209    return adoptRef(new MediaControlCurrentTimeDisplayElement(document));
1210}
1211
1212static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId()
1213{
1214    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-current-time-display", AtomicString::ConstructFromLiteral));
1215    return id;
1216}
1217
1218const AtomicString& MediaControlCurrentTimeDisplayElement::shadowPseudoId() const
1219{
1220    return getMediaControlCurrentTimeDisplayElementShadowPseudoId();
1221}
1222
1223// ----------------------------
1224
1225#if ENABLE(VIDEO_TRACK)
1226
1227MediaControlTextTrackContainerElement::MediaControlTextTrackContainerElement(Document& document)
1228    : MediaControlDivElement(document, MediaTextTrackDisplayContainer)
1229    , m_updateTimer(this, &MediaControlTextTrackContainerElement::updateTimerFired)
1230    , m_fontSize(0)
1231    , m_fontSizeIsImportant(false)
1232    , m_updateTextTrackRepresentationStyle(false)
1233{
1234    setPseudo(shadowPseudoId());
1235}
1236
1237PassRefPtr<MediaControlTextTrackContainerElement> MediaControlTextTrackContainerElement::create(Document& document)
1238{
1239    RefPtr<MediaControlTextTrackContainerElement> element = adoptRef(new MediaControlTextTrackContainerElement(document));
1240    element->hide();
1241    return element.release();
1242}
1243
1244RenderPtr<RenderElement> MediaControlTextTrackContainerElement::createElementRenderer(PassRef<RenderStyle> style)
1245{
1246    return createRenderer<RenderTextTrackContainerElement>(*this, WTF::move(style));
1247}
1248
1249const AtomicString& MediaControlTextTrackContainerElement::textTrackContainerElementShadowPseudoId()
1250{
1251    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-text-track-container", AtomicString::ConstructFromLiteral));
1252    return id;
1253}
1254
1255const AtomicString& MediaControlTextTrackContainerElement::shadowPseudoId() const
1256{
1257    return textTrackContainerElementShadowPseudoId();
1258}
1259
1260static bool compareCueIntervalForDisplay(const CueInterval& one, const CueInterval& two)
1261{
1262    return one.data()->isPositionedAbove(two.data());
1263};
1264
1265void MediaControlTextTrackContainerElement::updateDisplay()
1266{
1267    if (!mediaController()->closedCaptionsVisible())
1268        removeChildren();
1269
1270    HTMLMediaElement* mediaElement = parentMediaElement(this);
1271    // 1. If the media element is an audio element, or is another playback
1272    // mechanism with no rendering area, abort these steps. There is nothing to
1273    // render.
1274    if (!mediaElement || !mediaElement->isVideo())
1275        return;
1276
1277    // 2. Let video be the media element or other playback mechanism.
1278    HTMLVideoElement* video = toHTMLVideoElement(mediaElement);
1279
1280    // 3. Let output be an empty list of absolutely positioned CSS block boxes.
1281    Vector<RefPtr<HTMLDivElement>> output;
1282
1283    // 4. If the user agent is exposing a user interface for video, add to
1284    // output one or more completely transparent positioned CSS block boxes that
1285    // cover the same region as the user interface.
1286
1287    // 5. If the last time these rules were run, the user agent was not exposing
1288    // a user interface for video, but now it is, let reset be true. Otherwise,
1289    // let reset be false.
1290
1291    // There is nothing to be done explicitly for 4th and 5th steps, as
1292    // everything is handled through CSS. The caption box is on top of the
1293    // controls box, in a container set with the -webkit-box display property.
1294
1295    // 6. Let tracks be the subset of video's list of text tracks that have as
1296    // their rules for updating the text track rendering these rules for
1297    // updating the display of WebVTT text tracks, and whose text track mode is
1298    // showing or showing by default.
1299    // 7. Let cues be an empty list of text track cues.
1300    // 8. For each track track in tracks, append to cues all the cues from
1301    // track's list of cues that have their text track cue active flag set.
1302    CueList activeCues = video->currentlyActiveCues();
1303
1304    // 9. If reset is false, then, for each text track cue cue in cues: if cue's
1305    // text track cue display state has a set of CSS boxes, then add those boxes
1306    // to output, and remove cue from cues.
1307
1308    // There is nothing explicitly to be done here, as all the caching occurs
1309    // within the TextTrackCue instance itself. If parameters of the cue change,
1310    // the display tree is cleared.
1311
1312    // If the number of CSS boxes in the output is less than the number of cues
1313    // we wish to render (e.g., we are adding another cue in a set of roll-up
1314    // cues), remove all the existing CSS boxes representing the cues and re-add
1315    // them so that the new cue is at the bottom.
1316    if (childNodeCount() < activeCues.size())
1317        removeChildren();
1318
1319    // Sort the active cues for the appropriate display order. For example, for roll-up
1320    // or paint-on captions, we need to add the cues in reverse chronological order,
1321    // so that the newest captions appear at the bottom.
1322    std::sort(activeCues.begin(), activeCues.end(), &compareCueIntervalForDisplay);
1323
1324    // 10. For each text track cue cue in cues that has not yet had
1325    // corresponding CSS boxes added to output, in text track cue order, run the
1326    // following substeps:
1327    for (size_t i = 0; i < activeCues.size(); ++i) {
1328        if (!mediaController()->closedCaptionsVisible())
1329            continue;
1330
1331        TextTrackCue* textTrackCue = activeCues[i].data();
1332        if (!textTrackCue->isRenderable())
1333            continue;
1334
1335        VTTCue* cue = toVTTCue(textTrackCue);
1336
1337        ASSERT(cue->isActive());
1338        if (!cue->track() || !cue->track()->isRendered() || !cue->isActive() || cue->text().isEmpty())
1339            continue;
1340
1341        LOG(Media, "MediaControlTextTrackContainerElement::updateDisplay(%p) - adding and positioning cue #%zu: \"%s\", start=%.2f, end=%.2f, line=%.2f", this, i, cue->text().utf8().data(), cue->startTime(), cue->endTime(), cue->line());
1342
1343        RefPtr<VTTCueBox> displayBox = cue->getDisplayTree(m_videoDisplaySize.size(), m_fontSize);
1344#if ENABLE(WEBVTT_REGIONS)
1345        if (cue->track()->mode() == TextTrack::disabledKeyword())
1346            continue;
1347
1348        VTTRegion* region = cue->track()->regions()->getRegionById(cue->regionId());
1349        if (!region) {
1350            // If cue has an empty text track cue region identifier or there is no
1351            // WebVTT region whose region identifier is identical to cue's text
1352            // track cue region identifier, run the following substeps:
1353#endif
1354            if (displayBox->hasChildNodes() && !contains(displayBox.get())) {
1355                // Note: the display tree of a cue is removed when the active flag of the cue is unset.
1356                appendChild(displayBox, ASSERT_NO_EXCEPTION);
1357                cue->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant);
1358            }
1359#if ENABLE(WEBVTT_REGIONS)
1360        } else {
1361            // Let region be the WebVTT region whose region identifier
1362            // matches the text track cue region identifier of cue.
1363            RefPtr<HTMLDivElement> regionNode = region->getDisplayTree();
1364
1365            // Append the region to the viewport, if it was not already.
1366            if (!contains(regionNode.get()))
1367                appendChild(region->getDisplayTree());
1368
1369            region->appendTextTrackCueBox(displayBox);
1370        }
1371#endif
1372    }
1373
1374    // 11. Return output.
1375    if (hasChildNodes()) {
1376        show();
1377        updateTextTrackRepresentation();
1378    } else {
1379        hide();
1380        clearTextTrackRepresentation();
1381    }
1382}
1383
1384void MediaControlTextTrackContainerElement::updateActiveCuesFontSize()
1385{
1386    if (!document().page())
1387        return;
1388
1389    HTMLMediaElement* mediaElement = parentMediaElement(this);
1390    if (!mediaElement)
1391        return;
1392
1393    float smallestDimension = std::min(m_videoDisplaySize.size().height(), m_videoDisplaySize.size().width());
1394    float fontScale = document().page()->group().captionPreferences()->captionFontSizeScaleAndImportance(m_fontSizeIsImportant);
1395    m_fontSize = lroundf(smallestDimension * fontScale);
1396
1397    CueList activeCues = mediaElement->currentlyActiveCues();
1398    for (size_t i = 0; i < activeCues.size(); ++i) {
1399        TextTrackCue* cue = activeCues[i].data();
1400        if (!cue->isRenderable())
1401            continue;
1402
1403        toVTTCue(cue)->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant);
1404    }
1405
1406}
1407
1408void MediaControlTextTrackContainerElement::updateTimerFired(Timer<MediaControlTextTrackContainerElement>&)
1409{
1410    if (!document().page())
1411        return;
1412
1413    if (m_textTrackRepresentation)
1414        updateStyleForTextTrackRepresentation();
1415
1416    updateActiveCuesFontSize();
1417    updateDisplay();
1418}
1419
1420void MediaControlTextTrackContainerElement::updateTextTrackRepresentation()
1421{
1422    HTMLMediaElement* mediaElement = parentMediaElement(this);
1423    if (!mediaElement)
1424        return;
1425
1426    if (!mediaElement->requiresTextTrackRepresentation())
1427        return;
1428
1429    if (!m_textTrackRepresentation) {
1430        m_textTrackRepresentation = TextTrackRepresentation::create(this);
1431        m_updateTextTrackRepresentationStyle = true;
1432        mediaElement->setTextTrackRepresentation(m_textTrackRepresentation.get());
1433    }
1434
1435    m_textTrackRepresentation->update();
1436    updateStyleForTextTrackRepresentation();
1437}
1438
1439void MediaControlTextTrackContainerElement::clearTextTrackRepresentation()
1440{
1441    if (!m_textTrackRepresentation)
1442        return;
1443
1444    m_textTrackRepresentation = nullptr;
1445    m_updateTextTrackRepresentationStyle = true;
1446    if (HTMLMediaElement* mediaElement = parentMediaElement(this))
1447        mediaElement->setTextTrackRepresentation(nullptr);
1448    updateStyleForTextTrackRepresentation();
1449    updateActiveCuesFontSize();
1450}
1451
1452void MediaControlTextTrackContainerElement::updateStyleForTextTrackRepresentation()
1453{
1454    if (!m_updateTextTrackRepresentationStyle)
1455        return;
1456    m_updateTextTrackRepresentationStyle = false;
1457
1458    if (m_textTrackRepresentation) {
1459        setInlineStyleProperty(CSSPropertyWidth, m_videoDisplaySize.size().width(), CSSPrimitiveValue::CSS_PX);
1460        setInlineStyleProperty(CSSPropertyHeight, m_videoDisplaySize.size().height(), CSSPrimitiveValue::CSS_PX);
1461        setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
1462        setInlineStyleProperty(CSSPropertyLeft, 0, CSSPrimitiveValue::CSS_PX);
1463        setInlineStyleProperty(CSSPropertyTop, 0, CSSPrimitiveValue::CSS_PX);
1464        return;
1465    }
1466
1467    removeInlineStyleProperty(CSSPropertyPosition);
1468    removeInlineStyleProperty(CSSPropertyWidth);
1469    removeInlineStyleProperty(CSSPropertyHeight);
1470    removeInlineStyleProperty(CSSPropertyLeft);
1471    removeInlineStyleProperty(CSSPropertyTop);
1472}
1473
1474void MediaControlTextTrackContainerElement::enteredFullscreen()
1475{
1476    if (hasChildNodes())
1477        updateTextTrackRepresentation();
1478    updateSizes(true);
1479}
1480
1481void MediaControlTextTrackContainerElement::exitedFullscreen()
1482{
1483    clearTextTrackRepresentation();
1484    updateSizes(true);
1485}
1486
1487void MediaControlTextTrackContainerElement::updateSizes(bool forceUpdate)
1488{
1489    HTMLMediaElement* mediaElement = parentMediaElement(this);
1490    if (!mediaElement)
1491        return;
1492
1493    if (!document().page())
1494        return;
1495
1496    mediaElement->syncTextTrackBounds();
1497
1498    IntRect videoBox;
1499    if (m_textTrackRepresentation)
1500        videoBox = m_textTrackRepresentation->bounds();
1501    else {
1502        if (!mediaElement->renderer() || !mediaElement->renderer()->isVideo())
1503            return;
1504        videoBox = toRenderVideo(*mediaElement->renderer()).videoBox();
1505    }
1506
1507    if (!forceUpdate && m_videoDisplaySize == videoBox)
1508        return;
1509
1510    m_videoDisplaySize = videoBox;
1511    m_updateTextTrackRepresentationStyle = true;
1512
1513    // FIXME (121170): This function is called during layout, and should lay out the text tracks immediately.
1514    m_updateTimer.startOneShot(0);
1515}
1516
1517PassRefPtr<Image> MediaControlTextTrackContainerElement::createTextTrackRepresentationImage()
1518{
1519    if (!hasChildNodes())
1520        return nullptr;
1521
1522    Frame* frame = document().frame();
1523    if (!frame)
1524        return nullptr;
1525
1526    document().updateLayout();
1527
1528    auto renderer = this->renderer();
1529    if (!renderer)
1530        return nullptr;
1531
1532    if (!renderer->hasLayer())
1533        return nullptr;
1534
1535    RenderLayer* layer = toRenderLayerModelObject(renderer)->layer();
1536
1537    float deviceScaleFactor = 1;
1538    if (Page* page = document().page())
1539        deviceScaleFactor = page->deviceScaleFactor();
1540
1541    IntRect paintingRect = IntRect(IntPoint(), layer->size());
1542
1543    std::unique_ptr<ImageBuffer> buffer(ImageBuffer::create(paintingRect.size(), deviceScaleFactor, ColorSpaceDeviceRGB));
1544    if (!buffer)
1545        return nullptr;
1546
1547    layer->paint(buffer->context(), paintingRect, PaintBehaviorFlattenCompositingLayers, nullptr, RenderLayer::PaintLayerPaintingCompositingAllPhases);
1548
1549    return buffer->copyImage();
1550}
1551
1552void MediaControlTextTrackContainerElement::textTrackRepresentationBoundsChanged(const IntRect&)
1553{
1554    if (hasChildNodes())
1555        updateTextTrackRepresentation();
1556    updateSizes();
1557}
1558
1559#endif // ENABLE(VIDEO_TRACK)
1560
1561// ----------------------------
1562
1563} // namespace WebCore
1564
1565#endif // ENABLE(VIDEO)
1566