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