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