Move HTML canvas and tracks from ExceptionCode to Exception
[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
32 #if ENABLE(VIDEO)
33 #include "MediaControlElements.h"
34
35 #include "DOMTokenList.h"
36 #include "ElementChildIterator.h"
37 #include "EventHandler.h"
38 #include "EventNames.h"
39 #include "ExceptionCodePlaceholder.h"
40 #include "Frame.h"
41 #include "GraphicsContext.h"
42 #include "HTMLVideoElement.h"
43 #include "ImageBuffer.h"
44 #include "Language.h"
45 #include "LocalizedStrings.h"
46 #include "Logging.h"
47 #include "MediaControls.h"
48 #include "Page.h"
49 #include "PageGroup.h"
50 #include "RenderLayer.h"
51 #include "RenderMediaControlElements.h"
52 #include "RenderSlider.h"
53 #include "RenderVideo.h"
54 #include "RenderView.h"
55 #include "Settings.h"
56 #include "ShadowRoot.h"
57 #if ENABLE(VIDEO_TRACK)
58 #include "TextTrackList.h"
59 #endif
60 #include "VTTRegionList.h"
61
62 namespace WebCore {
63
64 using namespace HTMLNames;
65
66 static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId();
67 static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId();
68
69 MediaControlPanelElement::MediaControlPanelElement(Document& document)
70     : MediaControlDivElement(document, MediaControlsPanel)
71     , m_canBeDragged(false)
72     , m_isBeingDragged(false)
73     , m_isDisplayed(false)
74     , m_opaque(true)
75     , m_transitionTimer(*this, &MediaControlPanelElement::transitionTimerFired)
76 {
77     setPseudo(AtomicString("-webkit-media-controls-panel", AtomicString::ConstructFromLiteral));
78 }
79
80 Ref<MediaControlPanelElement> MediaControlPanelElement::create(Document& document)
81 {
82     return adoptRef(*new MediaControlPanelElement(document));
83 }
84
85 void MediaControlPanelElement::startDrag(const LayoutPoint& eventLocation)
86 {
87     if (!m_canBeDragged)
88         return;
89
90     if (m_isBeingDragged)
91         return;
92
93     auto renderer = this->renderer();
94     if (!renderer || !renderer->isBox())
95         return;
96
97     Frame* frame = document().frame();
98     if (!frame)
99         return;
100
101     m_lastDragEventLocation = eventLocation;
102
103     frame->eventHandler().setCapturingMouseEventsElement(this);
104
105     m_isBeingDragged = true;
106 }
107
108 void MediaControlPanelElement::continueDrag(const LayoutPoint& eventLocation)
109 {
110     if (!m_isBeingDragged)
111         return;
112
113     LayoutSize distanceDragged = eventLocation - m_lastDragEventLocation;
114     m_cumulativeDragOffset.move(distanceDragged);
115     m_lastDragEventLocation = eventLocation;
116     setPosition(m_cumulativeDragOffset);
117 }
118
119 void MediaControlPanelElement::endDrag()
120 {
121     if (!m_isBeingDragged)
122         return;
123
124     m_isBeingDragged = false;
125
126     Frame* frame = document().frame();
127     if (!frame)
128         return;
129
130     frame->eventHandler().setCapturingMouseEventsElement(nullptr);
131 }
132
133 void MediaControlPanelElement::startTimer()
134 {
135     stopTimer();
136
137     // The timer is required to set the property display:'none' on the panel,
138     // such that captions are correctly displayed at the bottom of the video
139     // at the end of the fadeout transition.
140     double duration = document().page() ? document().page()->theme().mediaControlsFadeOutDuration() : 0;
141     m_transitionTimer.startOneShot(duration);
142 }
143
144 void MediaControlPanelElement::stopTimer()
145 {
146     if (m_transitionTimer.isActive())
147         m_transitionTimer.stop();
148 }
149
150 void MediaControlPanelElement::transitionTimerFired()
151 {
152     if (!m_opaque)
153         hide();
154
155     stopTimer();
156 }
157
158 void MediaControlPanelElement::setPosition(const LayoutPoint& position)
159 {
160     double left = position.x();
161     double top = position.y();
162
163     // Set the left and top to control the panel's position; this depends on it being absolute positioned.
164     // Set the margin to zero since the position passed in will already include the effect of the margin.
165     setInlineStyleProperty(CSSPropertyLeft, left, CSSPrimitiveValue::CSS_PX);
166     setInlineStyleProperty(CSSPropertyTop, top, CSSPrimitiveValue::CSS_PX);
167     setInlineStyleProperty(CSSPropertyMarginLeft, 0.0, CSSPrimitiveValue::CSS_PX);
168     setInlineStyleProperty(CSSPropertyMarginTop, 0.0, CSSPrimitiveValue::CSS_PX);
169
170     classList().add("dragged", IGNORE_EXCEPTION);
171 }
172
173 void MediaControlPanelElement::resetPosition()
174 {
175     removeInlineStyleProperty(CSSPropertyLeft);
176     removeInlineStyleProperty(CSSPropertyTop);
177     removeInlineStyleProperty(CSSPropertyMarginLeft);
178     removeInlineStyleProperty(CSSPropertyMarginTop);
179
180     classList().remove("dragged", IGNORE_EXCEPTION);
181
182     m_cumulativeDragOffset.setX(0);
183     m_cumulativeDragOffset.setY(0);
184 }
185
186 void MediaControlPanelElement::makeOpaque()
187 {
188     if (m_opaque)
189         return;
190
191     double duration = document().page() ? document().page()->theme().mediaControlsFadeInDuration() : 0;
192
193     setInlineStyleProperty(CSSPropertyTransitionProperty, CSSPropertyOpacity);
194     setInlineStyleProperty(CSSPropertyTransitionDuration, duration, CSSPrimitiveValue::CSS_S);
195     setInlineStyleProperty(CSSPropertyOpacity, 1.0, CSSPrimitiveValue::CSS_NUMBER);
196
197     m_opaque = true;
198
199     if (m_isDisplayed)
200         show();
201 }
202
203 void MediaControlPanelElement::makeTransparent()
204 {
205     if (!m_opaque)
206         return;
207
208     double duration = document().page() ? document().page()->theme().mediaControlsFadeOutDuration() : 0;
209
210     setInlineStyleProperty(CSSPropertyTransitionProperty, CSSPropertyOpacity);
211     setInlineStyleProperty(CSSPropertyTransitionDuration, duration, CSSPrimitiveValue::CSS_S);
212     setInlineStyleProperty(CSSPropertyOpacity, 0.0, CSSPrimitiveValue::CSS_NUMBER);
213
214     m_opaque = false;
215     startTimer();
216 }
217
218 void MediaControlPanelElement::defaultEventHandler(Event& event)
219 {
220     MediaControlDivElement::defaultEventHandler(event);
221
222     if (is<MouseEvent>(event)) {
223         LayoutPoint location = downcast<MouseEvent>(event).absoluteLocation();
224         if (event.type() == eventNames().mousedownEvent && event.target() == this) {
225             startDrag(location);
226             event.setDefaultHandled();
227         } else if (event.type() == eventNames().mousemoveEvent && m_isBeingDragged)
228             continueDrag(location);
229         else if (event.type() == eventNames().mouseupEvent && m_isBeingDragged) {
230             continueDrag(location);
231             endDrag();
232             event.setDefaultHandled();
233         }
234     }
235 }
236
237 void MediaControlPanelElement::setCanBeDragged(bool canBeDragged)
238 {
239     if (m_canBeDragged == canBeDragged)
240         return;
241
242     m_canBeDragged = canBeDragged;
243
244     if (!canBeDragged)
245         endDrag();
246 }
247
248 void MediaControlPanelElement::setIsDisplayed(bool isDisplayed)
249 {
250     m_isDisplayed = isDisplayed;
251 }
252
253 // ----------------------------
254
255 MediaControlPanelEnclosureElement::MediaControlPanelEnclosureElement(Document& document)
256     // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
257     : MediaControlDivElement(document, MediaControlsPanel)
258 {
259     setPseudo(AtomicString("-webkit-media-controls-enclosure", AtomicString::ConstructFromLiteral));
260 }
261
262 Ref<MediaControlPanelEnclosureElement> MediaControlPanelEnclosureElement::create(Document& document)
263 {
264     return adoptRef(*new MediaControlPanelEnclosureElement(document));
265 }
266
267 // ----------------------------
268
269 MediaControlOverlayEnclosureElement::MediaControlOverlayEnclosureElement(Document& document)
270     // Mapping onto same MediaControlElementType as panel element, since it has similar properties.
271     : MediaControlDivElement(document, MediaControlsPanel)
272 {
273     setPseudo(AtomicString("-webkit-media-controls-overlay-enclosure", AtomicString::ConstructFromLiteral));
274 }
275
276 Ref<MediaControlOverlayEnclosureElement> MediaControlOverlayEnclosureElement::create(Document& document)
277 {
278     return adoptRef(*new MediaControlOverlayEnclosureElement(document));
279 }
280
281 // ----------------------------
282
283 MediaControlTimelineContainerElement::MediaControlTimelineContainerElement(Document& document)
284     : MediaControlDivElement(document, MediaTimelineContainer)
285 {
286     setPseudo(AtomicString("-webkit-media-controls-timeline-container", AtomicString::ConstructFromLiteral));
287 }
288
289 Ref<MediaControlTimelineContainerElement> MediaControlTimelineContainerElement::create(Document& document)
290 {
291     Ref<MediaControlTimelineContainerElement> element = adoptRef(*new MediaControlTimelineContainerElement(document));
292     element->hide();
293     return element;
294 }
295
296 void MediaControlTimelineContainerElement::setTimeDisplaysHidden(bool hidden)
297 {
298     for (auto& element : childrenOfType<Element>(*this)) {
299         if (element.shadowPseudoId() != getMediaControlTimeRemainingDisplayElementShadowPseudoId()
300             && element.shadowPseudoId() != getMediaControlCurrentTimeDisplayElementShadowPseudoId())
301             continue;
302
303         MediaControlTimeDisplayElement& timeDisplay = static_cast<MediaControlTimeDisplayElement&>(element);
304         if (hidden)
305             timeDisplay.hide();
306         else
307             timeDisplay.show();
308     }
309 }
310
311 RenderPtr<RenderElement> MediaControlTimelineContainerElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
312 {
313     return createRenderer<RenderMediaControlTimelineContainer>(*this, WTFMove(style));
314 }
315
316 // ----------------------------
317
318 MediaControlVolumeSliderContainerElement::MediaControlVolumeSliderContainerElement(Document& document)
319     : MediaControlDivElement(document, MediaVolumeSliderContainer)
320 {
321     setPseudo(AtomicString("-webkit-media-controls-volume-slider-container", AtomicString::ConstructFromLiteral));
322 }
323
324 Ref<MediaControlVolumeSliderContainerElement> MediaControlVolumeSliderContainerElement::create(Document& document)
325 {
326     Ref<MediaControlVolumeSliderContainerElement> element = adoptRef(*new MediaControlVolumeSliderContainerElement(document));
327     element->hide();
328     return element;
329 }
330
331 RenderPtr<RenderElement> MediaControlVolumeSliderContainerElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
332 {
333     return createRenderer<RenderMediaVolumeSliderContainer>(*this, WTFMove(style));
334 }
335
336 void MediaControlVolumeSliderContainerElement::defaultEventHandler(Event& event)
337 {
338     if (!is<MouseEvent>(event) || event.type() != eventNames().mouseoutEvent)
339         return;
340
341     // Poor man's mouseleave event detection.
342     MouseEvent& mouseEvent = downcast<MouseEvent>(event);
343     EventTarget* relatedTarget = mouseEvent.relatedTarget();
344     if (!relatedTarget || !relatedTarget->toNode())
345         return;
346
347     if (this->containsIncludingShadowDOM(relatedTarget->toNode()))
348         return;
349
350     hide();
351 }
352
353 // ----------------------------
354
355 MediaControlStatusDisplayElement::MediaControlStatusDisplayElement(Document& document)
356     : MediaControlDivElement(document, MediaStatusDisplay)
357     , m_stateBeingDisplayed(Nothing)
358 {
359     setPseudo(AtomicString("-webkit-media-controls-status-display", AtomicString::ConstructFromLiteral));
360 }
361
362 Ref<MediaControlStatusDisplayElement> MediaControlStatusDisplayElement::create(Document& document)
363 {
364     Ref<MediaControlStatusDisplayElement> element = adoptRef(*new MediaControlStatusDisplayElement(document));
365     element->hide();
366     return element;
367 }
368
369 void MediaControlStatusDisplayElement::update()
370 {
371     // Get the new state that we'll have to display.
372     StateBeingDisplayed newStateToDisplay = Nothing;
373
374     if (mediaController()->readyState() <= MediaControllerInterface::HAVE_METADATA && mediaController()->hasCurrentSrc())
375         newStateToDisplay = Loading;
376     else if (mediaController()->isLiveStream())
377         newStateToDisplay = LiveBroadcast;
378
379     if (newStateToDisplay == m_stateBeingDisplayed)
380         return;
381
382     if (m_stateBeingDisplayed == Nothing)
383         show();
384     else if (newStateToDisplay == Nothing)
385         hide();
386
387     m_stateBeingDisplayed = newStateToDisplay;
388
389     switch (m_stateBeingDisplayed) {
390     case Nothing:
391         setInnerText(emptyString(), IGNORE_EXCEPTION);
392         break;
393     case Loading:
394         setInnerText(mediaElementLoadingStateText(), IGNORE_EXCEPTION);
395         break;
396     case LiveBroadcast:
397         setInnerText(mediaElementLiveBroadcastStateText(), IGNORE_EXCEPTION);
398         break;
399     }
400 }
401
402 // ----------------------------
403
404 MediaControlPanelMuteButtonElement::MediaControlPanelMuteButtonElement(Document& document, MediaControls* controls)
405     : MediaControlMuteButtonElement(document, MediaMuteButton)
406     , m_controls(controls)
407 {
408     setPseudo(AtomicString("-webkit-media-controls-mute-button", AtomicString::ConstructFromLiteral));
409 }
410
411 Ref<MediaControlPanelMuteButtonElement> MediaControlPanelMuteButtonElement::create(Document& document, MediaControls* controls)
412 {
413     ASSERT(controls);
414
415     Ref<MediaControlPanelMuteButtonElement> button = adoptRef(*new MediaControlPanelMuteButtonElement(document, controls));
416     button->ensureUserAgentShadowRoot();
417     button->setType("button");
418     return button;
419 }
420
421 void MediaControlPanelMuteButtonElement::defaultEventHandler(Event& event)
422 {
423     if (event.type() == eventNames().mouseoverEvent)
424         m_controls->showVolumeSlider();
425
426     MediaControlMuteButtonElement::defaultEventHandler(event);
427 }
428
429 // ----------------------------
430
431 MediaControlVolumeSliderMuteButtonElement::MediaControlVolumeSliderMuteButtonElement(Document& document)
432     : MediaControlMuteButtonElement(document, MediaMuteButton)
433 {
434     setPseudo(AtomicString("-webkit-media-controls-volume-slider-mute-button", AtomicString::ConstructFromLiteral));
435 }
436
437 Ref<MediaControlVolumeSliderMuteButtonElement> MediaControlVolumeSliderMuteButtonElement::create(Document& document)
438 {
439     Ref<MediaControlVolumeSliderMuteButtonElement> button = adoptRef(*new MediaControlVolumeSliderMuteButtonElement(document));
440     button->ensureUserAgentShadowRoot();
441     button->setType("button");
442     return button;
443 }
444
445 // ----------------------------
446
447 MediaControlPlayButtonElement::MediaControlPlayButtonElement(Document& document)
448     : MediaControlInputElement(document, MediaPlayButton)
449 {
450     setPseudo(AtomicString("-webkit-media-controls-play-button", AtomicString::ConstructFromLiteral));
451 }
452
453 Ref<MediaControlPlayButtonElement> MediaControlPlayButtonElement::create(Document& document)
454 {
455     Ref<MediaControlPlayButtonElement> button = adoptRef(*new MediaControlPlayButtonElement(document));
456     button->ensureUserAgentShadowRoot();
457     button->setType("button");
458     return button;
459 }
460
461 void MediaControlPlayButtonElement::defaultEventHandler(Event& event)
462 {
463     if (event.type() == eventNames().clickEvent) {
464         if (mediaController()->canPlay())
465             mediaController()->play();
466         else
467             mediaController()->pause();
468         updateDisplayType();
469         event.setDefaultHandled();
470     }
471     HTMLInputElement::defaultEventHandler(event);
472 }
473
474 void MediaControlPlayButtonElement::updateDisplayType()
475 {
476     setDisplayType(mediaController()->canPlay() ? MediaPlayButton : MediaPauseButton);
477 }
478
479 // ----------------------------
480
481 MediaControlOverlayPlayButtonElement::MediaControlOverlayPlayButtonElement(Document& document)
482     : MediaControlInputElement(document, MediaOverlayPlayButton)
483 {
484     setPseudo(AtomicString("-webkit-media-controls-overlay-play-button", AtomicString::ConstructFromLiteral));
485 }
486
487 Ref<MediaControlOverlayPlayButtonElement> MediaControlOverlayPlayButtonElement::create(Document& document)
488 {
489     Ref<MediaControlOverlayPlayButtonElement> button = adoptRef(*new MediaControlOverlayPlayButtonElement(document));
490     button->ensureUserAgentShadowRoot();
491     button->setType("button");
492     return button;
493 }
494
495 void MediaControlOverlayPlayButtonElement::defaultEventHandler(Event& event)
496 {
497     if (event.type() == eventNames().clickEvent && mediaController()->canPlay()) {
498         mediaController()->play();
499         updateDisplayType();
500         event.setDefaultHandled();
501     }
502     HTMLInputElement::defaultEventHandler(event);
503 }
504
505 void MediaControlOverlayPlayButtonElement::updateDisplayType()
506 {
507     if (mediaController()->canPlay()) {
508         show();
509     } else
510         hide();
511 }
512
513 // ----------------------------
514
515 MediaControlSeekForwardButtonElement::MediaControlSeekForwardButtonElement(Document& document)
516     : MediaControlSeekButtonElement(document, MediaSeekForwardButton)
517 {
518     setPseudo(AtomicString("-webkit-media-controls-seek-forward-button", AtomicString::ConstructFromLiteral));
519 }
520
521 Ref<MediaControlSeekForwardButtonElement> MediaControlSeekForwardButtonElement::create(Document& document)
522 {
523     Ref<MediaControlSeekForwardButtonElement> button = adoptRef(*new MediaControlSeekForwardButtonElement(document));
524     button->ensureUserAgentShadowRoot();
525     button->setType("button");
526     return button;
527 }
528
529 // ----------------------------
530
531 MediaControlSeekBackButtonElement::MediaControlSeekBackButtonElement(Document& document)
532     : MediaControlSeekButtonElement(document, MediaSeekBackButton)
533 {
534     setPseudo(AtomicString("-webkit-media-controls-seek-back-button", AtomicString::ConstructFromLiteral));
535 }
536
537 Ref<MediaControlSeekBackButtonElement> MediaControlSeekBackButtonElement::create(Document& document)
538 {
539     Ref<MediaControlSeekBackButtonElement> button = adoptRef(*new MediaControlSeekBackButtonElement(document));
540     button->ensureUserAgentShadowRoot();
541     button->setType("button");
542     return button;
543 }
544
545 // ----------------------------
546
547 MediaControlRewindButtonElement::MediaControlRewindButtonElement(Document& document)
548     : MediaControlInputElement(document, MediaRewindButton)
549 {
550     setPseudo(AtomicString("-webkit-media-controls-rewind-button", AtomicString::ConstructFromLiteral));
551 }
552
553 Ref<MediaControlRewindButtonElement> MediaControlRewindButtonElement::create(Document& document)
554 {
555     Ref<MediaControlRewindButtonElement> button = adoptRef(*new MediaControlRewindButtonElement(document));
556     button->ensureUserAgentShadowRoot();
557     button->setType("button");
558     return button;
559 }
560
561 void MediaControlRewindButtonElement::defaultEventHandler(Event& event)
562 {
563     if (event.type() == eventNames().clickEvent) {
564         mediaController()->setCurrentTime(std::max<double>(0, mediaController()->currentTime() - 30));
565         event.setDefaultHandled();
566     }
567     HTMLInputElement::defaultEventHandler(event);
568 }
569
570 // ----------------------------
571
572 MediaControlReturnToRealtimeButtonElement::MediaControlReturnToRealtimeButtonElement(Document& document)
573     : MediaControlInputElement(document, MediaReturnToRealtimeButton)
574 {
575     setPseudo(AtomicString("-webkit-media-controls-return-to-realtime-button", AtomicString::ConstructFromLiteral));
576 }
577
578 Ref<MediaControlReturnToRealtimeButtonElement> MediaControlReturnToRealtimeButtonElement::create(Document& document)
579 {
580     Ref<MediaControlReturnToRealtimeButtonElement> button = adoptRef(*new MediaControlReturnToRealtimeButtonElement(document));
581     button->ensureUserAgentShadowRoot();
582     button->setType("button");
583     button->hide();
584     return button;
585 }
586
587 void MediaControlReturnToRealtimeButtonElement::defaultEventHandler(Event& event)
588 {
589     if (event.type() == eventNames().clickEvent) {
590         mediaController()->returnToRealtime();
591         event.setDefaultHandled();
592     }
593     HTMLInputElement::defaultEventHandler(event);
594 }
595
596 // ----------------------------
597
598 MediaControlToggleClosedCaptionsButtonElement::MediaControlToggleClosedCaptionsButtonElement(Document& document, MediaControls* controls)
599     : MediaControlInputElement(document, MediaShowClosedCaptionsButton)
600 #if PLATFORM(COCOA) || PLATFORM(WIN) || PLATFORM(GTK)
601     , m_controls(controls)
602 #endif
603 {
604 #if !PLATFORM(COCOA) && !PLATFORM(WIN) || !PLATFORM(GTK)
605     UNUSED_PARAM(controls);
606 #endif
607     setPseudo(AtomicString("-webkit-media-controls-toggle-closed-captions-button", AtomicString::ConstructFromLiteral));
608 }
609
610 Ref<MediaControlToggleClosedCaptionsButtonElement> MediaControlToggleClosedCaptionsButtonElement::create(Document& document, MediaControls* controls)
611 {
612     ASSERT(controls);
613
614     Ref<MediaControlToggleClosedCaptionsButtonElement> button = adoptRef(*new MediaControlToggleClosedCaptionsButtonElement(document, controls));
615     button->ensureUserAgentShadowRoot();
616     button->setType("button");
617     button->hide();
618     return button;
619 }
620
621 void MediaControlToggleClosedCaptionsButtonElement::updateDisplayType()
622 {
623     bool captionsVisible = mediaController()->closedCaptionsVisible();
624     setDisplayType(captionsVisible ? MediaHideClosedCaptionsButton : MediaShowClosedCaptionsButton);
625     setChecked(captionsVisible);
626 }
627
628 void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event& event)
629 {
630     if (event.type() == eventNames().clickEvent) {
631         // FIXME: It's not great that the shared code is dictating behavior of platform-specific
632         // UI. Not all ports may want the closed captions button to toggle a list of tracks, so
633         // we have to use #if.
634         // https://bugs.webkit.org/show_bug.cgi?id=101877
635 #if !PLATFORM(COCOA) && !PLATFORM(WIN) && !PLATFORM(GTK)
636         mediaController()->setClosedCaptionsVisible(!mediaController()->closedCaptionsVisible());
637         setChecked(mediaController()->closedCaptionsVisible());
638         updateDisplayType();
639 #else
640         m_controls->toggleClosedCaptionTrackList();
641 #endif
642         event.setDefaultHandled();
643     }
644
645     HTMLInputElement::defaultEventHandler(event);
646 }
647
648 // ----------------------------
649
650 MediaControlClosedCaptionsContainerElement::MediaControlClosedCaptionsContainerElement(Document& document)
651     : MediaControlDivElement(document, MediaClosedCaptionsContainer)
652 {
653     setPseudo(AtomicString("-webkit-media-controls-closed-captions-container", AtomicString::ConstructFromLiteral));
654 }
655
656 Ref<MediaControlClosedCaptionsContainerElement> MediaControlClosedCaptionsContainerElement::create(Document& document)
657 {
658     Ref<MediaControlClosedCaptionsContainerElement> element = adoptRef(*new MediaControlClosedCaptionsContainerElement(document));
659     element->setAttributeWithoutSynchronization(dirAttr, AtomicString("auto", AtomicString::ConstructFromLiteral));
660     element->hide();
661     return element;
662 }
663
664 // ----------------------------
665
666 MediaControlClosedCaptionsTrackListElement::MediaControlClosedCaptionsTrackListElement(Document& document, MediaControls* controls)
667     : MediaControlDivElement(document, MediaClosedCaptionsTrackList)
668 #if ENABLE(VIDEO_TRACK)
669     , m_controls(controls)
670 #endif
671 {
672 #if !ENABLE(VIDEO_TRACK)
673     UNUSED_PARAM(controls);
674 #endif
675     setPseudo(AtomicString("-webkit-media-controls-closed-captions-track-list", AtomicString::ConstructFromLiteral));
676 }
677
678 Ref<MediaControlClosedCaptionsTrackListElement> MediaControlClosedCaptionsTrackListElement::create(Document& document, MediaControls* controls)
679 {
680     ASSERT(controls);
681     Ref<MediaControlClosedCaptionsTrackListElement> element = adoptRef(*new MediaControlClosedCaptionsTrackListElement(document, controls));
682     return element;
683 }
684
685 void MediaControlClosedCaptionsTrackListElement::defaultEventHandler(Event& event)
686 {
687 #if ENABLE(VIDEO_TRACK)
688     if (event.type() == eventNames().clickEvent) {
689         Node* target = event.target()->toNode();
690         if (!is<Element>(target))
691             return;
692
693         // When we created the elements in the track list, we gave them a custom
694         // attribute representing the index in the HTMLMediaElement's list of tracks.
695         // Check if the event target has such a custom element and, if so,
696         // tell the HTMLMediaElement to enable that track.
697
698         RefPtr<TextTrack> textTrack;
699         MenuItemToTrackMap::iterator iter = m_menuToTrackMap.find(downcast<Element>(target));
700         if (iter != m_menuToTrackMap.end())
701             textTrack = iter->value;
702         m_menuToTrackMap.clear();
703         m_controls->toggleClosedCaptionTrackList();
704         if (!textTrack)
705             return;
706
707         HTMLMediaElement* mediaElement = parentMediaElement(this);
708         if (!mediaElement)
709             return;
710
711         mediaElement->setSelectedTextTrack(textTrack.get());
712
713         updateDisplay();
714     }
715
716     MediaControlDivElement::defaultEventHandler(event);
717 #else
718     UNUSED_PARAM(event);
719 #endif
720 }
721
722 void MediaControlClosedCaptionsTrackListElement::updateDisplay()
723 {
724 #if ENABLE(VIDEO_TRACK)
725     static NeverDestroyed<AtomicString> selectedClassValue("selected", AtomicString::ConstructFromLiteral);
726
727     if (!mediaController()->hasClosedCaptions())
728         return;
729
730     if (!document().page())
731         return;
732     CaptionUserPreferences::CaptionDisplayMode displayMode = document().page()->group().captionPreferences().captionDisplayMode();
733
734     HTMLMediaElement* mediaElement = parentMediaElement(this);
735     if (!mediaElement)
736         return;
737
738     if (!mediaElement->textTracks().length())
739         return;
740
741     rebuildTrackListMenu();
742
743     RefPtr<Element> offMenuItem;
744     bool trackMenuItemSelected = false;
745
746     for (auto& trackItem : m_menuItems) {
747         RefPtr<TextTrack> textTrack;
748         MenuItemToTrackMap::iterator iter = m_menuToTrackMap.find(trackItem.get());
749         if (iter == m_menuToTrackMap.end())
750             continue;
751         textTrack = iter->value;
752         if (!textTrack)
753             continue;
754
755         if (textTrack == TextTrack::captionMenuOffItem()) {
756             offMenuItem = trackItem;
757             continue;
758         }
759
760         if (textTrack == TextTrack::captionMenuAutomaticItem()) {
761             if (displayMode == CaptionUserPreferences::Automatic)
762                 trackItem->classList().add(selectedClassValue, ASSERT_NO_EXCEPTION);
763             else
764                 trackItem->classList().remove(selectedClassValue, ASSERT_NO_EXCEPTION);
765             continue;
766         }
767
768         if (displayMode != CaptionUserPreferences::Automatic && textTrack->mode() == TextTrack::Mode::Showing) {
769             trackMenuItemSelected = true;
770             trackItem->classList().add(selectedClassValue, ASSERT_NO_EXCEPTION);
771         } else
772             trackItem->classList().remove(selectedClassValue, ASSERT_NO_EXCEPTION);
773     }
774
775     if (offMenuItem) {
776         if (displayMode == CaptionUserPreferences::ForcedOnly && !trackMenuItemSelected)
777             offMenuItem->classList().add(selectedClassValue, ASSERT_NO_EXCEPTION);
778         else
779             offMenuItem->classList().remove(selectedClassValue, ASSERT_NO_EXCEPTION);
780     }
781 #endif
782 }
783
784 void MediaControlClosedCaptionsTrackListElement::rebuildTrackListMenu()
785 {
786 #if ENABLE(VIDEO_TRACK)
787     // Remove any existing content.
788     removeChildren();
789     m_menuItems.clear();
790     m_menuToTrackMap.clear();
791
792     if (!mediaController()->hasClosedCaptions())
793         return;
794
795     HTMLMediaElement* mediaElement = parentMediaElement(this);
796     if (!mediaElement)
797         return;
798
799     TextTrackList& trackList = mediaElement->textTracks();
800     if (!trackList.length())
801         return;
802
803     if (!document().page())
804         return;
805     auto& captionPreferences = document().page()->group().captionPreferences();
806     Vector<RefPtr<TextTrack>> tracksForMenu = captionPreferences.sortedTrackListForMenu(&trackList);
807
808     auto captionsHeader = document().createElement(h3Tag, ASSERT_NO_EXCEPTION);
809     captionsHeader->appendChild(document().createTextNode(textTrackSubtitlesText()));
810     appendChild(captionsHeader);
811     auto captionsMenuList = document().createElement(ulTag, ASSERT_NO_EXCEPTION);
812
813     for (auto& textTrack : tracksForMenu) {
814         auto menuItem = document().createElement(liTag, ASSERT_NO_EXCEPTION);
815         menuItem->appendChild(document().createTextNode(captionPreferences.displayNameForTrack(textTrack.get())));
816         captionsMenuList->appendChild(menuItem);
817         m_menuItems.append(menuItem.ptr());
818         m_menuToTrackMap.add(menuItem.ptr(), textTrack);
819     }
820
821     appendChild(captionsMenuList);
822 #endif
823 }
824
825 // ----------------------------
826
827 MediaControlTimelineElement::MediaControlTimelineElement(Document& document, MediaControls* controls)
828     : MediaControlInputElement(document, MediaSlider)
829     , m_controls(controls)
830 {
831     setPseudo(AtomicString("-webkit-media-controls-timeline", AtomicString::ConstructFromLiteral));
832 }
833
834 Ref<MediaControlTimelineElement> MediaControlTimelineElement::create(Document& document, MediaControls* controls)
835 {
836     ASSERT(controls);
837
838     Ref<MediaControlTimelineElement> timeline = adoptRef(*new MediaControlTimelineElement(document, controls));
839     timeline->ensureUserAgentShadowRoot();
840     timeline->setType("range");
841     timeline->setAttributeWithoutSynchronization(precisionAttr, AtomicString("float", AtomicString::ConstructFromLiteral));
842     return timeline;
843 }
844
845 void MediaControlTimelineElement::defaultEventHandler(Event& event)
846 {
847     // Left button is 0. Rejects mouse events not from left button.
848     if (is<MouseEvent>(event) && downcast<MouseEvent>(event).button())
849         return;
850
851     if (!renderer())
852         return;
853
854     if (event.type() == eventNames().mousedownEvent)
855         mediaController()->beginScrubbing();
856
857     if (event.type() == eventNames().mouseupEvent)
858         mediaController()->endScrubbing();
859
860     MediaControlInputElement::defaultEventHandler(event);
861
862     if (event.type() == eventNames().mouseoverEvent || event.type() == eventNames().mouseoutEvent || event.type() == eventNames().mousemoveEvent)
863         return;
864
865     double time = value().toDouble();
866     if ((event.isInputEvent() || event.type() == eventNames().inputEvent) && time != mediaController()->currentTime())
867         mediaController()->setCurrentTime(time);
868
869     RenderSlider& slider = downcast<RenderSlider>(*renderer());
870     if (slider.inDragMode())
871         m_controls->updateCurrentTimeDisplay();
872 }
873
874 #if !PLATFORM(IOS)
875 bool MediaControlTimelineElement::willRespondToMouseClickEvents()
876 {
877     if (!renderer())
878         return false;
879
880     return true;
881 }
882 #endif // !PLATFORM(IOS)
883
884 void MediaControlTimelineElement::setPosition(double currentTime)
885 {
886     setValue(String::number(currentTime));
887 }
888
889 void MediaControlTimelineElement::setDuration(double duration)
890 {
891     setAttribute(maxAttr, AtomicString::number(std::isfinite(duration) ? duration : 0));
892 }
893
894 // ----------------------------
895
896 MediaControlPanelVolumeSliderElement::MediaControlPanelVolumeSliderElement(Document& document)
897     : MediaControlVolumeSliderElement(document)
898 {
899     setPseudo(AtomicString("-webkit-media-controls-volume-slider", AtomicString::ConstructFromLiteral));
900 }
901
902 Ref<MediaControlPanelVolumeSliderElement> MediaControlPanelVolumeSliderElement::create(Document& document)
903 {
904     Ref<MediaControlPanelVolumeSliderElement> slider = adoptRef(*new MediaControlPanelVolumeSliderElement(document));
905     slider->ensureUserAgentShadowRoot();
906     slider->setType("range");
907     slider->setAttributeWithoutSynchronization(precisionAttr, AtomicString("float", AtomicString::ConstructFromLiteral));
908     slider->setAttributeWithoutSynchronization(maxAttr, AtomicString("1", AtomicString::ConstructFromLiteral));
909     return slider;
910 }
911
912 // ----------------------------
913
914 MediaControlFullscreenVolumeSliderElement::MediaControlFullscreenVolumeSliderElement(Document& document)
915     : MediaControlVolumeSliderElement(document)
916 {
917     setPseudo(AtomicString("-webkit-media-controls-fullscreen-volume-slider", AtomicString::ConstructFromLiteral));
918 }
919
920 Ref<MediaControlFullscreenVolumeSliderElement> MediaControlFullscreenVolumeSliderElement::create(Document& document)
921 {
922     Ref<MediaControlFullscreenVolumeSliderElement> slider = adoptRef(*new MediaControlFullscreenVolumeSliderElement(document));
923     slider->ensureUserAgentShadowRoot();
924     slider->setType("range");
925     slider->setAttributeWithoutSynchronization(precisionAttr, AtomicString("float", AtomicString::ConstructFromLiteral));
926     slider->setAttributeWithoutSynchronization(maxAttr, AtomicString("1", AtomicString::ConstructFromLiteral));
927     return slider;
928 }
929
930 // ----------------------------
931
932 MediaControlFullscreenButtonElement::MediaControlFullscreenButtonElement(Document& document)
933     : MediaControlInputElement(document, MediaEnterFullscreenButton)
934 {
935     setPseudo(AtomicString("-webkit-media-controls-fullscreen-button", AtomicString::ConstructFromLiteral));
936 }
937
938 Ref<MediaControlFullscreenButtonElement> MediaControlFullscreenButtonElement::create(Document& document)
939 {
940     Ref<MediaControlFullscreenButtonElement> button = adoptRef(*new MediaControlFullscreenButtonElement(document));
941     button->ensureUserAgentShadowRoot();
942     button->setType("button");
943     button->hide();
944     return button;
945 }
946
947 void MediaControlFullscreenButtonElement::defaultEventHandler(Event& event)
948 {
949     if (event.type() == eventNames().clickEvent) {
950 #if ENABLE(FULLSCREEN_API)
951         // Only use the new full screen API if the fullScreenEnabled setting has
952         // been explicitly enabled. Otherwise, use the old fullscreen API. This
953         // allows apps which embed a WebView to retain the existing full screen
954         // video implementation without requiring them to implement their own full
955         // screen behavior.
956         if (document().settings() && document().settings()->fullScreenEnabled()) {
957             if (document().webkitIsFullScreen() && document().webkitCurrentFullScreenElement() == parentMediaElement(this))
958                 document().webkitCancelFullScreen();
959             else
960                 document().requestFullScreenForElement(parentMediaElement(this), 0, Document::ExemptIFrameAllowFullScreenRequirement);
961         } else
962 #endif
963             mediaController()->enterFullscreen();
964         event.setDefaultHandled();
965     }
966     HTMLInputElement::defaultEventHandler(event);
967 }
968
969 void MediaControlFullscreenButtonElement::setIsFullscreen(bool isFullscreen)
970 {
971     setDisplayType(isFullscreen ? MediaExitFullscreenButton : MediaEnterFullscreenButton);
972 }
973
974 // ----------------------------
975
976 MediaControlFullscreenVolumeMinButtonElement::MediaControlFullscreenVolumeMinButtonElement(Document& document)
977     : MediaControlInputElement(document, MediaUnMuteButton)
978 {
979     setPseudo(AtomicString("-webkit-media-controls-fullscreen-volume-min-button", AtomicString::ConstructFromLiteral));
980 }
981
982 Ref<MediaControlFullscreenVolumeMinButtonElement> MediaControlFullscreenVolumeMinButtonElement::create(Document& document)
983 {
984     Ref<MediaControlFullscreenVolumeMinButtonElement> button = adoptRef(*new MediaControlFullscreenVolumeMinButtonElement(document));
985     button->ensureUserAgentShadowRoot();
986     button->setType("button");
987     return button;
988 }
989
990 void MediaControlFullscreenVolumeMinButtonElement::defaultEventHandler(Event& event)
991 {
992     if (event.type() == eventNames().clickEvent) {
993         ExceptionCode code = 0;
994         mediaController()->setVolume(0, code);
995         event.setDefaultHandled();
996     }
997     HTMLInputElement::defaultEventHandler(event);
998 }
999
1000 // ----------------------------
1001
1002 MediaControlFullscreenVolumeMaxButtonElement::MediaControlFullscreenVolumeMaxButtonElement(Document& document)
1003 : MediaControlInputElement(document, MediaMuteButton)
1004 {
1005     setPseudo(AtomicString("-webkit-media-controls-fullscreen-volume-max-button", AtomicString::ConstructFromLiteral));
1006 }
1007
1008 Ref<MediaControlFullscreenVolumeMaxButtonElement> MediaControlFullscreenVolumeMaxButtonElement::create(Document& document)
1009 {
1010     Ref<MediaControlFullscreenVolumeMaxButtonElement> button = adoptRef(*new MediaControlFullscreenVolumeMaxButtonElement(document));
1011     button->ensureUserAgentShadowRoot();
1012     button->setType("button");
1013     return button;
1014 }
1015
1016 void MediaControlFullscreenVolumeMaxButtonElement::defaultEventHandler(Event& event)
1017 {
1018     if (event.type() == eventNames().clickEvent) {
1019         ExceptionCode code = 0;
1020         mediaController()->setVolume(1, code);
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, ASSERT_NO_EXCEPTION);
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::updateTimerFired()
1236 {
1237     if (!document().page())
1238         return;
1239
1240     if (m_textTrackRepresentation)
1241         updateStyleForTextTrackRepresentation();
1242
1243     updateActiveCuesFontSize();
1244     updateDisplay();
1245 }
1246
1247 void MediaControlTextTrackContainerElement::updateTextTrackRepresentation()
1248 {
1249     HTMLMediaElement* mediaElement = parentMediaElement(this);
1250     if (!mediaElement)
1251         return;
1252
1253     if (!mediaElement->requiresTextTrackRepresentation())
1254         return;
1255
1256     if (!m_textTrackRepresentation) {
1257         m_textTrackRepresentation = TextTrackRepresentation::create(*this);
1258         m_updateTextTrackRepresentationStyle = true;
1259         mediaElement->setTextTrackRepresentation(m_textTrackRepresentation.get());
1260     }
1261
1262     m_textTrackRepresentation->update();
1263     updateStyleForTextTrackRepresentation();
1264 }
1265
1266 void MediaControlTextTrackContainerElement::clearTextTrackRepresentation()
1267 {
1268     if (!m_textTrackRepresentation)
1269         return;
1270
1271     m_textTrackRepresentation = nullptr;
1272     m_updateTextTrackRepresentationStyle = true;
1273     if (HTMLMediaElement* mediaElement = parentMediaElement(this))
1274         mediaElement->setTextTrackRepresentation(nullptr);
1275     updateStyleForTextTrackRepresentation();
1276     updateActiveCuesFontSize();
1277 }
1278
1279 void MediaControlTextTrackContainerElement::updateStyleForTextTrackRepresentation()
1280 {
1281     if (!m_updateTextTrackRepresentationStyle)
1282         return;
1283     m_updateTextTrackRepresentationStyle = false;
1284
1285     if (m_textTrackRepresentation) {
1286         setInlineStyleProperty(CSSPropertyWidth, m_videoDisplaySize.size().width(), CSSPrimitiveValue::CSS_PX);
1287         setInlineStyleProperty(CSSPropertyHeight, m_videoDisplaySize.size().height(), CSSPrimitiveValue::CSS_PX);
1288         setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
1289         setInlineStyleProperty(CSSPropertyLeft, 0, CSSPrimitiveValue::CSS_PX);
1290         setInlineStyleProperty(CSSPropertyTop, 0, CSSPrimitiveValue::CSS_PX);
1291         return;
1292     }
1293
1294     removeInlineStyleProperty(CSSPropertyPosition);
1295     removeInlineStyleProperty(CSSPropertyWidth);
1296     removeInlineStyleProperty(CSSPropertyHeight);
1297     removeInlineStyleProperty(CSSPropertyLeft);
1298     removeInlineStyleProperty(CSSPropertyTop);
1299 }
1300
1301 void MediaControlTextTrackContainerElement::enteredFullscreen()
1302 {
1303     if (hasChildNodes())
1304         updateTextTrackRepresentation();
1305     updateSizes(true);
1306 }
1307
1308 void MediaControlTextTrackContainerElement::exitedFullscreen()
1309 {
1310     clearTextTrackRepresentation();
1311     updateSizes(true);
1312 }
1313
1314 void MediaControlTextTrackContainerElement::updateSizes(bool forceUpdate)
1315 {
1316     HTMLMediaElement* mediaElement = parentMediaElement(this);
1317     if (!mediaElement)
1318         return;
1319
1320     if (!document().page())
1321         return;
1322
1323     mediaElement->syncTextTrackBounds();
1324
1325     IntRect videoBox;
1326     if (m_textTrackRepresentation)
1327         videoBox = m_textTrackRepresentation->bounds();
1328     else {
1329         if (!is<RenderVideo>(mediaElement->renderer()))
1330             return;
1331         videoBox = downcast<RenderVideo>(*mediaElement->renderer()).videoBox();
1332     }
1333
1334     if (!forceUpdate && m_videoDisplaySize == videoBox)
1335         return;
1336
1337     m_videoDisplaySize = videoBox;
1338     m_updateTextTrackRepresentationStyle = true;
1339
1340     // FIXME (121170): This function is called during layout, and should lay out the text tracks immediately.
1341     m_updateTimer.startOneShot(0);
1342 }
1343
1344 RefPtr<Image> MediaControlTextTrackContainerElement::createTextTrackRepresentationImage()
1345 {
1346     if (!hasChildNodes())
1347         return nullptr;
1348
1349     Frame* frame = document().frame();
1350     if (!frame)
1351         return nullptr;
1352
1353     document().updateLayout();
1354
1355     auto* renderer = this->renderer();
1356     if (!renderer)
1357         return nullptr;
1358
1359     if (!renderer->hasLayer())
1360         return nullptr;
1361
1362     RenderLayer* layer = downcast<RenderLayerModelObject>(*renderer).layer();
1363
1364     float deviceScaleFactor = 1;
1365     if (Page* page = document().page())
1366         deviceScaleFactor = page->deviceScaleFactor();
1367
1368     IntRect paintingRect = IntRect(IntPoint(), layer->size());
1369
1370     // FIXME (149422): This buffer should not be unconditionally unaccelerated.
1371     std::unique_ptr<ImageBuffer> buffer(ImageBuffer::create(paintingRect.size(), Unaccelerated, deviceScaleFactor));
1372     if (!buffer)
1373         return nullptr;
1374
1375     layer->paint(buffer->context(), paintingRect, LayoutSize(), PaintBehaviorFlattenCompositingLayers, nullptr, RenderLayer::PaintLayerPaintingCompositingAllPhases);
1376
1377     return ImageBuffer::sinkIntoImage(WTFMove(buffer));
1378 }
1379
1380 void MediaControlTextTrackContainerElement::textTrackRepresentationBoundsChanged(const IntRect&)
1381 {
1382     if (hasChildNodes())
1383         updateTextTrackRepresentation();
1384     updateSizes();
1385 }
1386
1387 #endif // ENABLE(VIDEO_TRACK)
1388
1389 // ----------------------------
1390
1391 } // namespace WebCore
1392
1393 #endif // ENABLE(VIDEO)