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