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