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