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