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