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 | |
66 | namespace WebCore { |
67 | |
68 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPanelElement); |
69 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPanelEnclosureElement); |
70 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlOverlayEnclosureElement); |
71 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlTimelineContainerElement); |
72 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlVolumeSliderContainerElement); |
73 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlStatusDisplayElement); |
74 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPanelMuteButtonElement); |
75 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlVolumeSliderMuteButtonElement); |
76 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPlayButtonElement); |
77 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlOverlayPlayButtonElement); |
78 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlSeekForwardButtonElement); |
79 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlSeekBackButtonElement); |
80 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlRewindButtonElement); |
81 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlReturnToRealtimeButtonElement); |
82 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlToggleClosedCaptionsButtonElement); |
83 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlClosedCaptionsContainerElement); |
84 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlClosedCaptionsTrackListElement); |
85 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlTimelineElement); |
86 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlFullscreenButtonElement); |
87 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPanelVolumeSliderElement); |
88 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlFullscreenVolumeSliderElement); |
89 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlFullscreenVolumeMinButtonElement); |
90 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlFullscreenVolumeMaxButtonElement); |
91 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlTimeRemainingDisplayElement); |
92 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlCurrentTimeDisplayElement); |
93 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlTextTrackContainerElement); |
94 | |
95 | using namespace HTMLNames; |
96 | |
97 | static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId(); |
98 | static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId(); |
99 | |
100 | MediaControlPanelElement::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 | |
111 | Ref<MediaControlPanelElement> MediaControlPanelElement::create(Document& document) |
112 | { |
113 | return adoptRef(*new MediaControlPanelElement(document)); |
114 | } |
115 | |
116 | void 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 | |
139 | void 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 | |
150 | void 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 | |
164 | void 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 | |
175 | void MediaControlPanelElement::stopTimer() |
176 | { |
177 | if (m_transitionTimer.isActive()) |
178 | m_transitionTimer.stop(); |
179 | } |
180 | |
181 | void MediaControlPanelElement::transitionTimerFired() |
182 | { |
183 | if (!m_opaque) |
184 | hide(); |
185 | |
186 | stopTimer(); |
187 | } |
188 | |
189 | void 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 | |
204 | void 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 | |
217 | void 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 | |
234 | void 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 | |
249 | void 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 | |
268 | void 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 | |
279 | void MediaControlPanelElement::setIsDisplayed(bool isDisplayed) |
280 | { |
281 | m_isDisplayed = isDisplayed; |
282 | } |
283 | |
284 | // ---------------------------- |
285 | |
286 | MediaControlPanelEnclosureElement::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 | |
293 | Ref<MediaControlPanelEnclosureElement> MediaControlPanelEnclosureElement::create(Document& document) |
294 | { |
295 | return adoptRef(*new MediaControlPanelEnclosureElement(document)); |
296 | } |
297 | |
298 | // ---------------------------- |
299 | |
300 | MediaControlOverlayEnclosureElement::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 | |
307 | Ref<MediaControlOverlayEnclosureElement> MediaControlOverlayEnclosureElement::create(Document& document) |
308 | { |
309 | return adoptRef(*new MediaControlOverlayEnclosureElement(document)); |
310 | } |
311 | |
312 | // ---------------------------- |
313 | |
314 | MediaControlTimelineContainerElement::MediaControlTimelineContainerElement(Document& document) |
315 | : MediaControlDivElement(document, MediaTimelineContainer) |
316 | { |
317 | setPseudo(AtomicString("-webkit-media-controls-timeline-container" , AtomicString::ConstructFromLiteral)); |
318 | } |
319 | |
320 | Ref<MediaControlTimelineContainerElement> MediaControlTimelineContainerElement::create(Document& document) |
321 | { |
322 | Ref<MediaControlTimelineContainerElement> element = adoptRef(*new MediaControlTimelineContainerElement(document)); |
323 | element->hide(); |
324 | return element; |
325 | } |
326 | |
327 | void 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 | |
342 | RenderPtr<RenderElement> MediaControlTimelineContainerElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
343 | { |
344 | return createRenderer<RenderMediaControlTimelineContainer>(*this, WTFMove(style)); |
345 | } |
346 | |
347 | // ---------------------------- |
348 | |
349 | MediaControlVolumeSliderContainerElement::MediaControlVolumeSliderContainerElement(Document& document) |
350 | : MediaControlDivElement(document, MediaVolumeSliderContainer) |
351 | { |
352 | setPseudo(AtomicString("-webkit-media-controls-volume-slider-container" , AtomicString::ConstructFromLiteral)); |
353 | } |
354 | |
355 | Ref<MediaControlVolumeSliderContainerElement> MediaControlVolumeSliderContainerElement::create(Document& document) |
356 | { |
357 | Ref<MediaControlVolumeSliderContainerElement> element = adoptRef(*new MediaControlVolumeSliderContainerElement(document)); |
358 | element->hide(); |
359 | return element; |
360 | } |
361 | |
362 | RenderPtr<RenderElement> MediaControlVolumeSliderContainerElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
363 | { |
364 | return createRenderer<RenderMediaVolumeSliderContainer>(*this, WTFMove(style)); |
365 | } |
366 | |
367 | void 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 | |
385 | MediaControlStatusDisplayElement::MediaControlStatusDisplayElement(Document& document) |
386 | : MediaControlDivElement(document, MediaStatusDisplay) |
387 | , m_stateBeingDisplayed(Nothing) |
388 | { |
389 | setPseudo(AtomicString("-webkit-media-controls-status-display" , AtomicString::ConstructFromLiteral)); |
390 | } |
391 | |
392 | Ref<MediaControlStatusDisplayElement> MediaControlStatusDisplayElement::create(Document& document) |
393 | { |
394 | Ref<MediaControlStatusDisplayElement> element = adoptRef(*new MediaControlStatusDisplayElement(document)); |
395 | element->hide(); |
396 | return element; |
397 | } |
398 | |
399 | void 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 | |
434 | MediaControlPanelMuteButtonElement::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 | |
441 | Ref<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 | |
451 | void MediaControlPanelMuteButtonElement::defaultEventHandler(Event& event) |
452 | { |
453 | if (event.type() == eventNames().mouseoverEvent) |
454 | m_controls->showVolumeSlider(); |
455 | |
456 | MediaControlMuteButtonElement::defaultEventHandler(event); |
457 | } |
458 | |
459 | // ---------------------------- |
460 | |
461 | MediaControlVolumeSliderMuteButtonElement::MediaControlVolumeSliderMuteButtonElement(Document& document) |
462 | : MediaControlMuteButtonElement(document, MediaMuteButton) |
463 | { |
464 | setPseudo(AtomicString("-webkit-media-controls-volume-slider-mute-button" , AtomicString::ConstructFromLiteral)); |
465 | } |
466 | |
467 | Ref<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 | |
477 | MediaControlPlayButtonElement::MediaControlPlayButtonElement(Document& document) |
478 | : MediaControlInputElement(document, MediaPlayButton) |
479 | { |
480 | setPseudo(AtomicString("-webkit-media-controls-play-button" , AtomicString::ConstructFromLiteral)); |
481 | } |
482 | |
483 | Ref<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 | |
491 | void 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 | |
504 | void MediaControlPlayButtonElement::updateDisplayType() |
505 | { |
506 | setDisplayType(mediaController()->canPlay() ? MediaPlayButton : MediaPauseButton); |
507 | } |
508 | |
509 | // ---------------------------- |
510 | |
511 | MediaControlOverlayPlayButtonElement::MediaControlOverlayPlayButtonElement(Document& document) |
512 | : MediaControlInputElement(document, MediaOverlayPlayButton) |
513 | { |
514 | setPseudo(AtomicString("-webkit-media-controls-overlay-play-button" , AtomicString::ConstructFromLiteral)); |
515 | } |
516 | |
517 | Ref<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 | |
525 | void 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 | |
535 | void MediaControlOverlayPlayButtonElement::updateDisplayType() |
536 | { |
537 | if (mediaController()->canPlay()) { |
538 | show(); |
539 | } else |
540 | hide(); |
541 | } |
542 | |
543 | // ---------------------------- |
544 | |
545 | MediaControlSeekForwardButtonElement::MediaControlSeekForwardButtonElement(Document& document) |
546 | : MediaControlSeekButtonElement(document, MediaSeekForwardButton) |
547 | { |
548 | setPseudo(AtomicString("-webkit-media-controls-seek-forward-button" , AtomicString::ConstructFromLiteral)); |
549 | } |
550 | |
551 | Ref<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 | |
561 | MediaControlSeekBackButtonElement::MediaControlSeekBackButtonElement(Document& document) |
562 | : MediaControlSeekButtonElement(document, MediaSeekBackButton) |
563 | { |
564 | setPseudo(AtomicString("-webkit-media-controls-seek-back-button" , AtomicString::ConstructFromLiteral)); |
565 | } |
566 | |
567 | Ref<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 | |
577 | MediaControlRewindButtonElement::MediaControlRewindButtonElement(Document& document) |
578 | : MediaControlInputElement(document, MediaRewindButton) |
579 | { |
580 | setPseudo(AtomicString("-webkit-media-controls-rewind-button" , AtomicString::ConstructFromLiteral)); |
581 | } |
582 | |
583 | Ref<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 | |
591 | void 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 | |
602 | MediaControlReturnToRealtimeButtonElement::MediaControlReturnToRealtimeButtonElement(Document& document) |
603 | : MediaControlInputElement(document, MediaReturnToRealtimeButton) |
604 | { |
605 | setPseudo(AtomicString("-webkit-media-controls-return-to-realtime-button" , AtomicString::ConstructFromLiteral)); |
606 | } |
607 | |
608 | Ref<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 | |
617 | void 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 | |
628 | MediaControlToggleClosedCaptionsButtonElement::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 | |
640 | Ref<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 | |
651 | void MediaControlToggleClosedCaptionsButtonElement::updateDisplayType() |
652 | { |
653 | bool captionsVisible = mediaController()->closedCaptionsVisible(); |
654 | setDisplayType(captionsVisible ? MediaHideClosedCaptionsButton : MediaShowClosedCaptionsButton); |
655 | setChecked(captionsVisible); |
656 | } |
657 | |
658 | void 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 | |
680 | MediaControlClosedCaptionsContainerElement::MediaControlClosedCaptionsContainerElement(Document& document) |
681 | : MediaControlDivElement(document, MediaClosedCaptionsContainer) |
682 | { |
683 | setPseudo(AtomicString("-webkit-media-controls-closed-captions-container" , AtomicString::ConstructFromLiteral)); |
684 | } |
685 | |
686 | Ref<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 | |
696 | MediaControlClosedCaptionsTrackListElement::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 | |
708 | Ref<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 | |
715 | void 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 | |
748 | void 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> ; |
770 | bool = 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 | |
810 | void MediaControlClosedCaptionsTrackListElement::() |
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>> = captionPreferences.sortedTrackListForMenu(trackList); |
833 | |
834 | auto = HTMLHeadingElement::create(h3Tag, document()); |
835 | captionsHeader->appendChild(document().createTextNode(textTrackSubtitlesText())); |
836 | appendChild(captionsHeader); |
837 | auto = HTMLUListElement::create(document()); |
838 | |
839 | for (auto& textTrack : tracksForMenu) { |
840 | auto = 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 | |
853 | MediaControlTimelineElement::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 | |
860 | Ref<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 | |
871 | void 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) |
901 | bool MediaControlTimelineElement::willRespondToMouseClickEvents() |
902 | { |
903 | if (!renderer()) |
904 | return false; |
905 | |
906 | return true; |
907 | } |
908 | #endif // !PLATFORM(IOS_FAMILY) |
909 | |
910 | void MediaControlTimelineElement::setPosition(double currentTime) |
911 | { |
912 | setValue(String::numberToStringECMAScript(currentTime)); |
913 | } |
914 | |
915 | void MediaControlTimelineElement::setDuration(double duration) |
916 | { |
917 | setAttribute(maxAttr, AtomicString::number(duration)); |
918 | } |
919 | |
920 | // ---------------------------- |
921 | |
922 | MediaControlPanelVolumeSliderElement::MediaControlPanelVolumeSliderElement(Document& document) |
923 | : MediaControlVolumeSliderElement(document) |
924 | { |
925 | setPseudo(AtomicString("-webkit-media-controls-volume-slider" , AtomicString::ConstructFromLiteral)); |
926 | } |
927 | |
928 | Ref<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 | |
940 | MediaControlFullscreenVolumeSliderElement::MediaControlFullscreenVolumeSliderElement(Document& document) |
941 | : MediaControlVolumeSliderElement(document) |
942 | { |
943 | setPseudo(AtomicString("-webkit-media-controls-fullscreen-volume-slider" , AtomicString::ConstructFromLiteral)); |
944 | } |
945 | |
946 | Ref<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 | |
958 | MediaControlFullscreenButtonElement::MediaControlFullscreenButtonElement(Document& document) |
959 | : MediaControlInputElement(document, MediaEnterFullscreenButton) |
960 | { |
961 | setPseudo(AtomicString("-webkit-media-controls-fullscreen-button" , AtomicString::ConstructFromLiteral)); |
962 | } |
963 | |
964 | Ref<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 | |
973 | void 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 | |
995 | void MediaControlFullscreenButtonElement::setIsFullscreen(bool isFullscreen) |
996 | { |
997 | setDisplayType(isFullscreen ? MediaExitFullscreenButton : MediaEnterFullscreenButton); |
998 | } |
999 | |
1000 | // ---------------------------- |
1001 | |
1002 | MediaControlFullscreenVolumeMinButtonElement::MediaControlFullscreenVolumeMinButtonElement(Document& document) |
1003 | : MediaControlInputElement(document, MediaUnMuteButton) |
1004 | { |
1005 | setPseudo(AtomicString("-webkit-media-controls-fullscreen-volume-min-button" , AtomicString::ConstructFromLiteral)); |
1006 | } |
1007 | |
1008 | Ref<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 | |
1016 | void 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 | |
1027 | MediaControlFullscreenVolumeMaxButtonElement::MediaControlFullscreenVolumeMaxButtonElement(Document& document) |
1028 | : MediaControlInputElement(document, MediaMuteButton) |
1029 | { |
1030 | setPseudo(AtomicString("-webkit-media-controls-fullscreen-volume-max-button" , AtomicString::ConstructFromLiteral)); |
1031 | } |
1032 | |
1033 | Ref<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 | |
1041 | void 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 | |
1052 | MediaControlTimeRemainingDisplayElement::MediaControlTimeRemainingDisplayElement(Document& document) |
1053 | : MediaControlTimeDisplayElement(document, MediaTimeRemainingDisplay) |
1054 | { |
1055 | setPseudo(getMediaControlTimeRemainingDisplayElementShadowPseudoId()); |
1056 | } |
1057 | |
1058 | Ref<MediaControlTimeRemainingDisplayElement> MediaControlTimeRemainingDisplayElement::create(Document& document) |
1059 | { |
1060 | return adoptRef(*new MediaControlTimeRemainingDisplayElement(document)); |
1061 | } |
1062 | |
1063 | static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId() |
1064 | { |
1065 | static NeverDestroyed<AtomicString> id("-webkit-media-controls-time-remaining-display" , AtomicString::ConstructFromLiteral); |
1066 | return id; |
1067 | } |
1068 | |
1069 | // ---------------------------- |
1070 | |
1071 | MediaControlCurrentTimeDisplayElement::MediaControlCurrentTimeDisplayElement(Document& document) |
1072 | : MediaControlTimeDisplayElement(document, MediaCurrentTimeDisplay) |
1073 | { |
1074 | setPseudo(getMediaControlCurrentTimeDisplayElementShadowPseudoId()); |
1075 | } |
1076 | |
1077 | Ref<MediaControlCurrentTimeDisplayElement> MediaControlCurrentTimeDisplayElement::create(Document& document) |
1078 | { |
1079 | return adoptRef(*new MediaControlCurrentTimeDisplayElement(document)); |
1080 | } |
1081 | |
1082 | static 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 | |
1092 | MediaControlTextTrackContainerElement::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 | |
1102 | Ref<MediaControlTextTrackContainerElement> MediaControlTextTrackContainerElement::create(Document& document) |
1103 | { |
1104 | auto element = adoptRef(*new MediaControlTextTrackContainerElement(document)); |
1105 | element->hide(); |
1106 | return element; |
1107 | } |
1108 | |
1109 | RenderPtr<RenderElement> MediaControlTextTrackContainerElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
1110 | { |
1111 | return createRenderer<RenderTextTrackContainerElement>(*this, WTFMove(style)); |
1112 | } |
1113 | |
1114 | static bool compareCueIntervalForDisplay(const CueInterval& one, const CueInterval& two) |
1115 | { |
1116 | return one.data()->isPositionedAbove(two.data()); |
1117 | }; |
1118 | |
1119 | void 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 | |
1238 | void 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 | |
1261 | void 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 | |
1293 | void 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 | |
1306 | void 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 | |
1332 | void 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 | |
1345 | void 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 | |
1367 | void MediaControlTextTrackContainerElement::enteredFullscreen() |
1368 | { |
1369 | if (hasChildNodes()) |
1370 | updateTextTrackRepresentation(); |
1371 | updateSizes(true); |
1372 | } |
1373 | |
1374 | void MediaControlTextTrackContainerElement::exitedFullscreen() |
1375 | { |
1376 | clearTextTrackRepresentation(); |
1377 | updateSizes(true); |
1378 | } |
1379 | |
1380 | void 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 | |
1412 | RefPtr<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 | |
1448 | void MediaControlTextTrackContainerElement::textTrackRepresentationBoundsChanged(const IntRect&) |
1449 | { |
1450 | if (hasChildNodes()) |
1451 | updateTextTrackRepresentation(); |
1452 | updateSizes(); |
1453 | } |
1454 | |
1455 | #if !RELEASE_LOG_DISABLED |
1456 | const Logger& MediaControlTextTrackContainerElement::logger() const |
1457 | { |
1458 | return document().logger(); |
1459 | } |
1460 | |
1461 | const void* MediaControlTextTrackContainerElement::logIdentifier() const |
1462 | { |
1463 | if (auto mediaElement = parentMediaElement(this)) |
1464 | return mediaElement->logIdentifier(); |
1465 | return nullptr; |
1466 | } |
1467 | |
1468 | WTFLogChannel& 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 | |