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