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