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