| /* |
| * Copyright (C) 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "MediaControlElements.h" |
| |
| #if ENABLE(VIDEO) |
| |
| #include "DOMTokenList.h" |
| #include "ElementChildIterator.h" |
| #include "EventHandler.h" |
| #include "EventNames.h" |
| #include "Frame.h" |
| #include "FullscreenManager.h" |
| #include "GraphicsContext.h" |
| #include "HTMLHeadingElement.h" |
| #include "HTMLLIElement.h" |
| #include "HTMLUListElement.h" |
| #include "HTMLVideoElement.h" |
| #include "ImageBuffer.h" |
| #include "LocalizedStrings.h" |
| #include "Logging.h" |
| #include "MediaControls.h" |
| #include "MouseEvent.h" |
| #include "Page.h" |
| #include "PageGroup.h" |
| #include "RenderLayer.h" |
| #include "RenderMediaControlElements.h" |
| #include "RenderSlider.h" |
| #include "RenderTheme.h" |
| #include "RenderVideo.h" |
| #include "RenderView.h" |
| #include "Settings.h" |
| #include "ShadowRoot.h" |
| #include "TextTrackList.h" |
| #include "VTTRegionList.h" |
| #include <wtf/IsoMallocInlines.h> |
| #include <wtf/Language.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPanelElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPanelEnclosureElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlOverlayEnclosureElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlTimelineContainerElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlVolumeSliderContainerElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlStatusDisplayElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPanelMuteButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlVolumeSliderMuteButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPlayButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlOverlayPlayButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlSeekForwardButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlSeekBackButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlRewindButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlReturnToRealtimeButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlToggleClosedCaptionsButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlClosedCaptionsContainerElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlClosedCaptionsTrackListElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlTimelineElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlFullscreenButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlPanelVolumeSliderElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlFullscreenVolumeSliderElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlFullscreenVolumeMinButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlFullscreenVolumeMaxButtonElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlTimeRemainingDisplayElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlCurrentTimeDisplayElement); |
| WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlTextTrackContainerElement); |
| |
| using namespace HTMLNames; |
| |
| static const AtomString& getMediaControlCurrentTimeDisplayElementShadowPseudoId(); |
| static const AtomString& getMediaControlTimeRemainingDisplayElementShadowPseudoId(); |
| |
| MediaControlPanelElement::MediaControlPanelElement(Document& document) |
| : MediaControlDivElement(document, MediaControlsPanel) |
| , m_canBeDragged(false) |
| , m_isBeingDragged(false) |
| , m_isDisplayed(false) |
| , m_opaque(true) |
| , m_transitionTimer(*this, &MediaControlPanelElement::transitionTimerFired) |
| { |
| setPseudo(AtomString("-webkit-media-controls-panel", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlPanelElement> MediaControlPanelElement::create(Document& document) |
| { |
| return adoptRef(*new MediaControlPanelElement(document)); |
| } |
| |
| void MediaControlPanelElement::startDrag(const LayoutPoint& eventLocation) |
| { |
| if (!m_canBeDragged) |
| return; |
| |
| if (m_isBeingDragged) |
| return; |
| |
| auto renderer = this->renderer(); |
| if (!renderer || !renderer->isBox()) |
| return; |
| |
| RefPtr<Frame> frame = document().frame(); |
| if (!frame) |
| return; |
| |
| m_lastDragEventLocation = eventLocation; |
| |
| frame->eventHandler().setCapturingMouseEventsElement(this); |
| |
| m_isBeingDragged = true; |
| } |
| |
| void MediaControlPanelElement::continueDrag(const LayoutPoint& eventLocation) |
| { |
| if (!m_isBeingDragged) |
| return; |
| |
| LayoutSize distanceDragged = eventLocation - m_lastDragEventLocation; |
| m_cumulativeDragOffset.move(distanceDragged); |
| m_lastDragEventLocation = eventLocation; |
| setPosition(m_cumulativeDragOffset); |
| } |
| |
| void MediaControlPanelElement::endDrag() |
| { |
| if (!m_isBeingDragged) |
| return; |
| |
| m_isBeingDragged = false; |
| |
| RefPtr<Frame> frame = document().frame(); |
| if (!frame) |
| return; |
| |
| frame->eventHandler().setCapturingMouseEventsElement(nullptr); |
| } |
| |
| void MediaControlPanelElement::startTimer() |
| { |
| stopTimer(); |
| |
| // The timer is required to set the property display:'none' on the panel, |
| // such that captions are correctly displayed at the bottom of the video |
| // at the end of the fadeout transition. |
| Seconds duration = RenderTheme::singleton().mediaControlsFadeOutDuration(); |
| m_transitionTimer.startOneShot(duration); |
| } |
| |
| void MediaControlPanelElement::stopTimer() |
| { |
| if (m_transitionTimer.isActive()) |
| m_transitionTimer.stop(); |
| } |
| |
| void MediaControlPanelElement::transitionTimerFired() |
| { |
| if (!m_opaque) |
| hide(); |
| |
| stopTimer(); |
| } |
| |
| void MediaControlPanelElement::setPosition(const LayoutPoint& position) |
| { |
| double left = position.x(); |
| double top = position.y(); |
| |
| // Set the left and top to control the panel's position; this depends on it being absolute positioned. |
| // Set the margin to zero since the position passed in will already include the effect of the margin. |
| setInlineStyleProperty(CSSPropertyLeft, left, CSSPrimitiveValue::CSS_PX); |
| setInlineStyleProperty(CSSPropertyTop, top, CSSPrimitiveValue::CSS_PX); |
| setInlineStyleProperty(CSSPropertyMarginLeft, 0.0, CSSPrimitiveValue::CSS_PX); |
| setInlineStyleProperty(CSSPropertyMarginTop, 0.0, CSSPrimitiveValue::CSS_PX); |
| |
| classList().add("dragged"); |
| } |
| |
| void MediaControlPanelElement::resetPosition() |
| { |
| removeInlineStyleProperty(CSSPropertyLeft); |
| removeInlineStyleProperty(CSSPropertyTop); |
| removeInlineStyleProperty(CSSPropertyMarginLeft); |
| removeInlineStyleProperty(CSSPropertyMarginTop); |
| |
| classList().remove("dragged"); |
| |
| m_cumulativeDragOffset.setX(0); |
| m_cumulativeDragOffset.setY(0); |
| } |
| |
| void MediaControlPanelElement::makeOpaque() |
| { |
| if (m_opaque) |
| return; |
| |
| double duration = RenderTheme::singleton().mediaControlsFadeInDuration(); |
| |
| setInlineStyleProperty(CSSPropertyTransitionProperty, CSSPropertyOpacity); |
| setInlineStyleProperty(CSSPropertyTransitionDuration, duration, CSSPrimitiveValue::CSS_S); |
| setInlineStyleProperty(CSSPropertyOpacity, 1.0, CSSPrimitiveValue::CSS_NUMBER); |
| |
| m_opaque = true; |
| |
| if (m_isDisplayed) |
| show(); |
| } |
| |
| void MediaControlPanelElement::makeTransparent() |
| { |
| if (!m_opaque) |
| return; |
| |
| Seconds duration = RenderTheme::singleton().mediaControlsFadeOutDuration(); |
| |
| setInlineStyleProperty(CSSPropertyTransitionProperty, CSSPropertyOpacity); |
| setInlineStyleProperty(CSSPropertyTransitionDuration, duration.value(), CSSPrimitiveValue::CSS_S); |
| setInlineStyleProperty(CSSPropertyOpacity, 0.0, CSSPrimitiveValue::CSS_NUMBER); |
| |
| m_opaque = false; |
| startTimer(); |
| } |
| |
| void MediaControlPanelElement::defaultEventHandler(Event& event) |
| { |
| MediaControlDivElement::defaultEventHandler(event); |
| |
| if (is<MouseEvent>(event)) { |
| LayoutPoint location = downcast<MouseEvent>(event).absoluteLocation(); |
| if (event.type() == eventNames().mousedownEvent && event.target() == this) { |
| startDrag(location); |
| event.setDefaultHandled(); |
| } else if (event.type() == eventNames().mousemoveEvent && m_isBeingDragged) |
| continueDrag(location); |
| else if (event.type() == eventNames().mouseupEvent && m_isBeingDragged) { |
| continueDrag(location); |
| endDrag(); |
| event.setDefaultHandled(); |
| } |
| } |
| } |
| |
| void MediaControlPanelElement::setCanBeDragged(bool canBeDragged) |
| { |
| if (m_canBeDragged == canBeDragged) |
| return; |
| |
| m_canBeDragged = canBeDragged; |
| |
| if (!canBeDragged) |
| endDrag(); |
| } |
| |
| void MediaControlPanelElement::setIsDisplayed(bool isDisplayed) |
| { |
| m_isDisplayed = isDisplayed; |
| } |
| |
| // ---------------------------- |
| |
| MediaControlPanelEnclosureElement::MediaControlPanelEnclosureElement(Document& document) |
| // Mapping onto same MediaControlElementType as panel element, since it has similar properties. |
| : MediaControlDivElement(document, MediaControlsPanel) |
| { |
| setPseudo(AtomString("-webkit-media-controls-enclosure", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlPanelEnclosureElement> MediaControlPanelEnclosureElement::create(Document& document) |
| { |
| return adoptRef(*new MediaControlPanelEnclosureElement(document)); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlOverlayEnclosureElement::MediaControlOverlayEnclosureElement(Document& document) |
| // Mapping onto same MediaControlElementType as panel element, since it has similar properties. |
| : MediaControlDivElement(document, MediaControlsPanel) |
| { |
| setPseudo(AtomString("-webkit-media-controls-overlay-enclosure", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlOverlayEnclosureElement> MediaControlOverlayEnclosureElement::create(Document& document) |
| { |
| return adoptRef(*new MediaControlOverlayEnclosureElement(document)); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlTimelineContainerElement::MediaControlTimelineContainerElement(Document& document) |
| : MediaControlDivElement(document, MediaTimelineContainer) |
| { |
| setPseudo(AtomString("-webkit-media-controls-timeline-container", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlTimelineContainerElement> MediaControlTimelineContainerElement::create(Document& document) |
| { |
| Ref<MediaControlTimelineContainerElement> element = adoptRef(*new MediaControlTimelineContainerElement(document)); |
| element->hide(); |
| return element; |
| } |
| |
| void MediaControlTimelineContainerElement::setTimeDisplaysHidden(bool hidden) |
| { |
| for (auto& element : childrenOfType<Element>(*this)) { |
| if (element.shadowPseudoId() != getMediaControlTimeRemainingDisplayElementShadowPseudoId() |
| && element.shadowPseudoId() != getMediaControlCurrentTimeDisplayElementShadowPseudoId()) |
| continue; |
| |
| MediaControlTimeDisplayElement& timeDisplay = static_cast<MediaControlTimeDisplayElement&>(element); |
| if (hidden) |
| timeDisplay.hide(); |
| else |
| timeDisplay.show(); |
| } |
| } |
| |
| RenderPtr<RenderElement> MediaControlTimelineContainerElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
| { |
| return createRenderer<RenderMediaControlTimelineContainer>(*this, WTFMove(style)); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlVolumeSliderContainerElement::MediaControlVolumeSliderContainerElement(Document& document) |
| : MediaControlDivElement(document, MediaVolumeSliderContainer) |
| { |
| setPseudo(AtomString("-webkit-media-controls-volume-slider-container", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlVolumeSliderContainerElement> MediaControlVolumeSliderContainerElement::create(Document& document) |
| { |
| Ref<MediaControlVolumeSliderContainerElement> element = adoptRef(*new MediaControlVolumeSliderContainerElement(document)); |
| element->hide(); |
| return element; |
| } |
| |
| RenderPtr<RenderElement> MediaControlVolumeSliderContainerElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
| { |
| return createRenderer<RenderMediaVolumeSliderContainer>(*this, WTFMove(style)); |
| } |
| |
| void MediaControlVolumeSliderContainerElement::defaultEventHandler(Event& event) |
| { |
| // Poor man's mouseleave event detection. |
| |
| if (!is<MouseEvent>(event) || event.type() != eventNames().mouseoutEvent) |
| return; |
| |
| if (!is<Node>(downcast<MouseEvent>(event).relatedTarget())) |
| return; |
| |
| if (containsIncludingShadowDOM(&downcast<Node>(*downcast<MouseEvent>(event).relatedTarget()))) |
| return; |
| |
| hide(); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlStatusDisplayElement::MediaControlStatusDisplayElement(Document& document) |
| : MediaControlDivElement(document, MediaStatusDisplay) |
| , m_stateBeingDisplayed(Nothing) |
| { |
| setPseudo(AtomString("-webkit-media-controls-status-display", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlStatusDisplayElement> MediaControlStatusDisplayElement::create(Document& document) |
| { |
| Ref<MediaControlStatusDisplayElement> element = adoptRef(*new MediaControlStatusDisplayElement(document)); |
| element->hide(); |
| return element; |
| } |
| |
| void MediaControlStatusDisplayElement::update() |
| { |
| // Get the new state that we'll have to display. |
| StateBeingDisplayed newStateToDisplay = Nothing; |
| |
| if (mediaController()->readyState() <= MediaControllerInterface::HAVE_METADATA && mediaController()->hasCurrentSrc()) |
| newStateToDisplay = Loading; |
| else if (mediaController()->isLiveStream()) |
| newStateToDisplay = LiveBroadcast; |
| |
| if (newStateToDisplay == m_stateBeingDisplayed) |
| return; |
| |
| if (m_stateBeingDisplayed == Nothing) |
| show(); |
| else if (newStateToDisplay == Nothing) |
| hide(); |
| |
| m_stateBeingDisplayed = newStateToDisplay; |
| |
| switch (m_stateBeingDisplayed) { |
| case Nothing: |
| setInnerText(emptyString()); |
| break; |
| case Loading: |
| setInnerText(mediaElementLoadingStateText()); |
| break; |
| case LiveBroadcast: |
| setInnerText(mediaElementLiveBroadcastStateText()); |
| break; |
| } |
| } |
| |
| // ---------------------------- |
| |
| MediaControlPanelMuteButtonElement::MediaControlPanelMuteButtonElement(Document& document, MediaControls* controls) |
| : MediaControlMuteButtonElement(document, MediaMuteButton) |
| , m_controls(controls) |
| { |
| setPseudo(AtomString("-webkit-media-controls-mute-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlPanelMuteButtonElement> MediaControlPanelMuteButtonElement::create(Document& document, MediaControls* controls) |
| { |
| ASSERT(controls); |
| |
| Ref<MediaControlPanelMuteButtonElement> button = adoptRef(*new MediaControlPanelMuteButtonElement(document, controls)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| return button; |
| } |
| |
| void MediaControlPanelMuteButtonElement::defaultEventHandler(Event& event) |
| { |
| if (event.type() == eventNames().mouseoverEvent) |
| m_controls->showVolumeSlider(); |
| |
| MediaControlMuteButtonElement::defaultEventHandler(event); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlVolumeSliderMuteButtonElement::MediaControlVolumeSliderMuteButtonElement(Document& document) |
| : MediaControlMuteButtonElement(document, MediaMuteButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-volume-slider-mute-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlVolumeSliderMuteButtonElement> MediaControlVolumeSliderMuteButtonElement::create(Document& document) |
| { |
| Ref<MediaControlVolumeSliderMuteButtonElement> button = adoptRef(*new MediaControlVolumeSliderMuteButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| return button; |
| } |
| |
| // ---------------------------- |
| |
| MediaControlPlayButtonElement::MediaControlPlayButtonElement(Document& document) |
| : MediaControlInputElement(document, MediaPlayButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-play-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlPlayButtonElement> MediaControlPlayButtonElement::create(Document& document) |
| { |
| Ref<MediaControlPlayButtonElement> button = adoptRef(*new MediaControlPlayButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| return button; |
| } |
| |
| void MediaControlPlayButtonElement::defaultEventHandler(Event& event) |
| { |
| if (event.type() == eventNames().clickEvent) { |
| if (mediaController()->canPlay()) |
| mediaController()->play(); |
| else |
| mediaController()->pause(); |
| updateDisplayType(); |
| event.setDefaultHandled(); |
| } |
| HTMLInputElement::defaultEventHandler(event); |
| } |
| |
| void MediaControlPlayButtonElement::updateDisplayType() |
| { |
| setDisplayType(mediaController()->canPlay() ? MediaPlayButton : MediaPauseButton); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlOverlayPlayButtonElement::MediaControlOverlayPlayButtonElement(Document& document) |
| : MediaControlInputElement(document, MediaOverlayPlayButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-overlay-play-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlOverlayPlayButtonElement> MediaControlOverlayPlayButtonElement::create(Document& document) |
| { |
| Ref<MediaControlOverlayPlayButtonElement> button = adoptRef(*new MediaControlOverlayPlayButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| return button; |
| } |
| |
| void MediaControlOverlayPlayButtonElement::defaultEventHandler(Event& event) |
| { |
| if (event.type() == eventNames().clickEvent && mediaController()->canPlay()) { |
| mediaController()->play(); |
| updateDisplayType(); |
| event.setDefaultHandled(); |
| } |
| HTMLInputElement::defaultEventHandler(event); |
| } |
| |
| void MediaControlOverlayPlayButtonElement::updateDisplayType() |
| { |
| if (mediaController()->canPlay()) { |
| show(); |
| } else |
| hide(); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlSeekForwardButtonElement::MediaControlSeekForwardButtonElement(Document& document) |
| : MediaControlSeekButtonElement(document, MediaSeekForwardButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-seek-forward-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlSeekForwardButtonElement> MediaControlSeekForwardButtonElement::create(Document& document) |
| { |
| Ref<MediaControlSeekForwardButtonElement> button = adoptRef(*new MediaControlSeekForwardButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| return button; |
| } |
| |
| // ---------------------------- |
| |
| MediaControlSeekBackButtonElement::MediaControlSeekBackButtonElement(Document& document) |
| : MediaControlSeekButtonElement(document, MediaSeekBackButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-seek-back-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlSeekBackButtonElement> MediaControlSeekBackButtonElement::create(Document& document) |
| { |
| Ref<MediaControlSeekBackButtonElement> button = adoptRef(*new MediaControlSeekBackButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| return button; |
| } |
| |
| // ---------------------------- |
| |
| MediaControlRewindButtonElement::MediaControlRewindButtonElement(Document& document) |
| : MediaControlInputElement(document, MediaRewindButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-rewind-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlRewindButtonElement> MediaControlRewindButtonElement::create(Document& document) |
| { |
| Ref<MediaControlRewindButtonElement> button = adoptRef(*new MediaControlRewindButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| return button; |
| } |
| |
| void MediaControlRewindButtonElement::defaultEventHandler(Event& event) |
| { |
| if (event.type() == eventNames().clickEvent) { |
| mediaController()->setCurrentTime(std::max<double>(0, mediaController()->currentTime() - 30)); |
| event.setDefaultHandled(); |
| } |
| HTMLInputElement::defaultEventHandler(event); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlReturnToRealtimeButtonElement::MediaControlReturnToRealtimeButtonElement(Document& document) |
| : MediaControlInputElement(document, MediaReturnToRealtimeButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-return-to-realtime-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlReturnToRealtimeButtonElement> MediaControlReturnToRealtimeButtonElement::create(Document& document) |
| { |
| Ref<MediaControlReturnToRealtimeButtonElement> button = adoptRef(*new MediaControlReturnToRealtimeButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| button->hide(); |
| return button; |
| } |
| |
| void MediaControlReturnToRealtimeButtonElement::defaultEventHandler(Event& event) |
| { |
| if (event.type() == eventNames().clickEvent) { |
| mediaController()->returnToRealtime(); |
| event.setDefaultHandled(); |
| } |
| HTMLInputElement::defaultEventHandler(event); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlToggleClosedCaptionsButtonElement::MediaControlToggleClosedCaptionsButtonElement(Document& document, MediaControls* controls) |
| : MediaControlInputElement(document, MediaShowClosedCaptionsButton) |
| #if PLATFORM(COCOA) || PLATFORM(WIN) || PLATFORM(GTK) |
| , m_controls(controls) |
| #endif |
| { |
| #if !PLATFORM(COCOA) && !PLATFORM(WIN) || !PLATFORM(GTK) |
| UNUSED_PARAM(controls); |
| #endif |
| setPseudo(AtomString("-webkit-media-controls-toggle-closed-captions-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlToggleClosedCaptionsButtonElement> MediaControlToggleClosedCaptionsButtonElement::create(Document& document, MediaControls* controls) |
| { |
| ASSERT(controls); |
| |
| Ref<MediaControlToggleClosedCaptionsButtonElement> button = adoptRef(*new MediaControlToggleClosedCaptionsButtonElement(document, controls)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| button->hide(); |
| return button; |
| } |
| |
| void MediaControlToggleClosedCaptionsButtonElement::updateDisplayType() |
| { |
| bool captionsVisible = mediaController()->closedCaptionsVisible(); |
| setDisplayType(captionsVisible ? MediaHideClosedCaptionsButton : MediaShowClosedCaptionsButton); |
| setChecked(captionsVisible); |
| } |
| |
| void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event& event) |
| { |
| if (event.type() == eventNames().clickEvent) { |
| // FIXME: It's not great that the shared code is dictating behavior of platform-specific |
| // UI. Not all ports may want the closed captions button to toggle a list of tracks, so |
| // we have to use #if. |
| // https://bugs.webkit.org/show_bug.cgi?id=101877 |
| #if !PLATFORM(COCOA) && !PLATFORM(WIN) && !PLATFORM(GTK) |
| mediaController()->setClosedCaptionsVisible(!mediaController()->closedCaptionsVisible()); |
| setChecked(mediaController()->closedCaptionsVisible()); |
| updateDisplayType(); |
| #else |
| m_controls->toggleClosedCaptionTrackList(); |
| #endif |
| event.setDefaultHandled(); |
| } |
| |
| HTMLInputElement::defaultEventHandler(event); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlClosedCaptionsContainerElement::MediaControlClosedCaptionsContainerElement(Document& document) |
| : MediaControlDivElement(document, MediaClosedCaptionsContainer) |
| { |
| setPseudo(AtomString("-webkit-media-controls-closed-captions-container", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlClosedCaptionsContainerElement> MediaControlClosedCaptionsContainerElement::create(Document& document) |
| { |
| Ref<MediaControlClosedCaptionsContainerElement> element = adoptRef(*new MediaControlClosedCaptionsContainerElement(document)); |
| element->setAttributeWithoutSynchronization(dirAttr, AtomString("auto", AtomString::ConstructFromLiteral)); |
| element->hide(); |
| return element; |
| } |
| |
| // ---------------------------- |
| |
| MediaControlClosedCaptionsTrackListElement::MediaControlClosedCaptionsTrackListElement(Document& document, MediaControls* controls) |
| : MediaControlDivElement(document, MediaClosedCaptionsTrackList) |
| #if ENABLE(VIDEO_TRACK) |
| , m_controls(controls) |
| #endif |
| { |
| #if !ENABLE(VIDEO_TRACK) |
| UNUSED_PARAM(controls); |
| #endif |
| setPseudo(AtomString("-webkit-media-controls-closed-captions-track-list", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlClosedCaptionsTrackListElement> MediaControlClosedCaptionsTrackListElement::create(Document& document, MediaControls* controls) |
| { |
| ASSERT(controls); |
| Ref<MediaControlClosedCaptionsTrackListElement> element = adoptRef(*new MediaControlClosedCaptionsTrackListElement(document, controls)); |
| return element; |
| } |
| |
| void MediaControlClosedCaptionsTrackListElement::defaultEventHandler(Event& event) |
| { |
| #if ENABLE(VIDEO_TRACK) |
| if (event.type() == eventNames().clickEvent) { |
| if (!is<Element>(event.target())) |
| return; |
| |
| // When we created the elements in the track list, we gave them a custom |
| // attribute representing the index in the HTMLMediaElement's list of tracks. |
| // Check if the event target has such a custom element and, if so, |
| // tell the HTMLMediaElement to enable that track. |
| |
| auto textTrack = makeRefPtr(m_menuToTrackMap.get(&downcast<Element>(*event.target()))); |
| m_menuToTrackMap.clear(); |
| m_controls->toggleClosedCaptionTrackList(); |
| if (!textTrack) |
| return; |
| |
| auto mediaElement = parentMediaElement(this); |
| if (!mediaElement) |
| return; |
| |
| mediaElement->setSelectedTextTrack(textTrack.get()); |
| |
| updateDisplay(); |
| } |
| |
| MediaControlDivElement::defaultEventHandler(event); |
| #else |
| UNUSED_PARAM(event); |
| #endif |
| } |
| |
| void MediaControlClosedCaptionsTrackListElement::updateDisplay() |
| { |
| #if ENABLE(VIDEO_TRACK) |
| static NeverDestroyed<AtomString> selectedClassValue("selected", AtomString::ConstructFromLiteral); |
| |
| if (!mediaController()->hasClosedCaptions()) |
| return; |
| |
| if (!document().page()) |
| return; |
| CaptionUserPreferences::CaptionDisplayMode displayMode = document().page()->group().captionPreferences().captionDisplayMode(); |
| |
| auto mediaElement = parentMediaElement(this); |
| if (!mediaElement) |
| return; |
| |
| if (!mediaElement->textTracks() || !mediaElement->textTracks()->length()) |
| return; |
| |
| rebuildTrackListMenu(); |
| |
| RefPtr<Element> offMenuItem; |
| bool trackMenuItemSelected = false; |
| |
| for (auto& trackItem : m_menuItems) { |
| RefPtr<TextTrack> textTrack; |
| MenuItemToTrackMap::iterator iter = m_menuToTrackMap.find(trackItem.get()); |
| if (iter == m_menuToTrackMap.end()) |
| continue; |
| textTrack = iter->value; |
| if (!textTrack) |
| continue; |
| |
| if (textTrack == TextTrack::captionMenuOffItem()) { |
| offMenuItem = trackItem; |
| continue; |
| } |
| |
| if (textTrack == TextTrack::captionMenuAutomaticItem()) { |
| if (displayMode == CaptionUserPreferences::Automatic) |
| trackItem->classList().add(selectedClassValue); |
| else |
| trackItem->classList().remove(selectedClassValue); |
| continue; |
| } |
| |
| if (displayMode != CaptionUserPreferences::Automatic && textTrack->mode() == TextTrack::Mode::Showing) { |
| trackMenuItemSelected = true; |
| trackItem->classList().add(selectedClassValue); |
| } else |
| trackItem->classList().remove(selectedClassValue); |
| } |
| |
| if (offMenuItem) { |
| if (displayMode == CaptionUserPreferences::ForcedOnly && !trackMenuItemSelected) |
| offMenuItem->classList().add(selectedClassValue); |
| else |
| offMenuItem->classList().remove(selectedClassValue); |
| } |
| #endif |
| } |
| |
| void MediaControlClosedCaptionsTrackListElement::rebuildTrackListMenu() |
| { |
| #if ENABLE(VIDEO_TRACK) |
| // Remove any existing content. |
| removeChildren(); |
| m_menuItems.clear(); |
| m_menuToTrackMap.clear(); |
| |
| if (!mediaController()->hasClosedCaptions()) |
| return; |
| |
| auto mediaElement = parentMediaElement(this); |
| if (!mediaElement) |
| return; |
| |
| auto* trackList = mediaElement->textTracks(); |
| if (!trackList || !trackList->length()) |
| return; |
| |
| if (!document().page()) |
| return; |
| auto& captionPreferences = document().page()->group().captionPreferences(); |
| Vector<RefPtr<TextTrack>> tracksForMenu = captionPreferences.sortedTrackListForMenu(trackList); |
| |
| auto captionsHeader = HTMLHeadingElement::create(h3Tag, document()); |
| captionsHeader->appendChild(document().createTextNode(textTrackSubtitlesText())); |
| appendChild(captionsHeader); |
| auto captionsMenuList = HTMLUListElement::create(document()); |
| |
| for (auto& textTrack : tracksForMenu) { |
| auto menuItem = HTMLLIElement::create(document()); |
| menuItem->appendChild(document().createTextNode(captionPreferences.displayNameForTrack(textTrack.get()))); |
| captionsMenuList->appendChild(menuItem); |
| m_menuItems.append(menuItem.ptr()); |
| m_menuToTrackMap.add(menuItem.ptr(), textTrack); |
| } |
| |
| appendChild(captionsMenuList); |
| #endif |
| } |
| |
| // ---------------------------- |
| |
| MediaControlTimelineElement::MediaControlTimelineElement(Document& document, MediaControls* controls) |
| : MediaControlInputElement(document, MediaSlider) |
| , m_controls(controls) |
| { |
| setPseudo(AtomString("-webkit-media-controls-timeline", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlTimelineElement> MediaControlTimelineElement::create(Document& document, MediaControls* controls) |
| { |
| ASSERT(controls); |
| |
| Ref<MediaControlTimelineElement> timeline = adoptRef(*new MediaControlTimelineElement(document, controls)); |
| timeline->ensureUserAgentShadowRoot(); |
| timeline->setType("range"); |
| timeline->setAttributeWithoutSynchronization(precisionAttr, AtomString("float", AtomString::ConstructFromLiteral)); |
| return timeline; |
| } |
| |
| void MediaControlTimelineElement::defaultEventHandler(Event& event) |
| { |
| // Left button is 0. Rejects mouse events not from left button. |
| if (is<MouseEvent>(event) && downcast<MouseEvent>(event).button()) |
| return; |
| |
| if (!renderer()) |
| return; |
| |
| if (event.type() == eventNames().mousedownEvent) |
| mediaController()->beginScrubbing(); |
| |
| if (event.type() == eventNames().mouseupEvent) |
| mediaController()->endScrubbing(); |
| |
| MediaControlInputElement::defaultEventHandler(event); |
| |
| if (event.type() == eventNames().mouseoverEvent || event.type() == eventNames().mouseoutEvent || event.type() == eventNames().mousemoveEvent) |
| return; |
| |
| double time = value().toDouble(); |
| if ((event.isInputEvent() || event.type() == eventNames().inputEvent) && time != mediaController()->currentTime()) |
| mediaController()->setCurrentTime(time); |
| |
| RenderSlider& slider = downcast<RenderSlider>(*renderer()); |
| if (slider.inDragMode()) |
| m_controls->updateCurrentTimeDisplay(); |
| } |
| |
| #if !PLATFORM(IOS_FAMILY) |
| bool MediaControlTimelineElement::willRespondToMouseClickEvents() |
| { |
| if (!renderer()) |
| return false; |
| |
| return true; |
| } |
| #endif // !PLATFORM(IOS_FAMILY) |
| |
| void MediaControlTimelineElement::setPosition(double currentTime) |
| { |
| setValue(String::number(currentTime)); |
| } |
| |
| void MediaControlTimelineElement::setDuration(double duration) |
| { |
| setAttribute(maxAttr, AtomString::number(duration)); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlPanelVolumeSliderElement::MediaControlPanelVolumeSliderElement(Document& document) |
| : MediaControlVolumeSliderElement(document) |
| { |
| setPseudo(AtomString("-webkit-media-controls-volume-slider", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlPanelVolumeSliderElement> MediaControlPanelVolumeSliderElement::create(Document& document) |
| { |
| Ref<MediaControlPanelVolumeSliderElement> slider = adoptRef(*new MediaControlPanelVolumeSliderElement(document)); |
| slider->ensureUserAgentShadowRoot(); |
| slider->setType("range"); |
| slider->setAttributeWithoutSynchronization(precisionAttr, AtomString("float", AtomString::ConstructFromLiteral)); |
| slider->setAttributeWithoutSynchronization(maxAttr, AtomString("1", AtomString::ConstructFromLiteral)); |
| return slider; |
| } |
| |
| // ---------------------------- |
| |
| MediaControlFullscreenVolumeSliderElement::MediaControlFullscreenVolumeSliderElement(Document& document) |
| : MediaControlVolumeSliderElement(document) |
| { |
| setPseudo(AtomString("-webkit-media-controls-fullscreen-volume-slider", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlFullscreenVolumeSliderElement> MediaControlFullscreenVolumeSliderElement::create(Document& document) |
| { |
| Ref<MediaControlFullscreenVolumeSliderElement> slider = adoptRef(*new MediaControlFullscreenVolumeSliderElement(document)); |
| slider->ensureUserAgentShadowRoot(); |
| slider->setType("range"); |
| slider->setAttributeWithoutSynchronization(precisionAttr, AtomString("float", AtomString::ConstructFromLiteral)); |
| slider->setAttributeWithoutSynchronization(maxAttr, AtomString("1", AtomString::ConstructFromLiteral)); |
| return slider; |
| } |
| |
| // ---------------------------- |
| |
| MediaControlFullscreenButtonElement::MediaControlFullscreenButtonElement(Document& document) |
| : MediaControlInputElement(document, MediaEnterFullscreenButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-fullscreen-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlFullscreenButtonElement> MediaControlFullscreenButtonElement::create(Document& document) |
| { |
| Ref<MediaControlFullscreenButtonElement> button = adoptRef(*new MediaControlFullscreenButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| button->hide(); |
| return button; |
| } |
| |
| void MediaControlFullscreenButtonElement::defaultEventHandler(Event& event) |
| { |
| if (event.type() == eventNames().clickEvent) { |
| #if ENABLE(FULLSCREEN_API) |
| // Only use the new full screen API if the fullScreenEnabled setting has |
| // been explicitly enabled. Otherwise, use the old fullscreen API. This |
| // allows apps which embed a WebView to retain the existing full screen |
| // video implementation without requiring them to implement their own full |
| // screen behavior. |
| if (document().settings().fullScreenEnabled()) { |
| if (document().fullscreenManager().isFullscreen() && document().fullscreenManager().currentFullscreenElement() == parentMediaElement(this)) |
| document().fullscreenManager().cancelFullscreen(); |
| else |
| document().fullscreenManager().requestFullscreenForElement(parentMediaElement(this).get(), FullscreenManager::ExemptIFrameAllowFullscreenRequirement); |
| } else |
| #endif |
| mediaController()->enterFullscreen(); |
| event.setDefaultHandled(); |
| } |
| HTMLInputElement::defaultEventHandler(event); |
| } |
| |
| void MediaControlFullscreenButtonElement::setIsFullscreen(bool isFullscreen) |
| { |
| setDisplayType(isFullscreen ? MediaExitFullscreenButton : MediaEnterFullscreenButton); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlFullscreenVolumeMinButtonElement::MediaControlFullscreenVolumeMinButtonElement(Document& document) |
| : MediaControlInputElement(document, MediaUnMuteButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-fullscreen-volume-min-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlFullscreenVolumeMinButtonElement> MediaControlFullscreenVolumeMinButtonElement::create(Document& document) |
| { |
| Ref<MediaControlFullscreenVolumeMinButtonElement> button = adoptRef(*new MediaControlFullscreenVolumeMinButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| return button; |
| } |
| |
| void MediaControlFullscreenVolumeMinButtonElement::defaultEventHandler(Event& event) |
| { |
| if (event.type() == eventNames().clickEvent) { |
| mediaController()->setVolume(0); |
| event.setDefaultHandled(); |
| } |
| HTMLInputElement::defaultEventHandler(event); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlFullscreenVolumeMaxButtonElement::MediaControlFullscreenVolumeMaxButtonElement(Document& document) |
| : MediaControlInputElement(document, MediaMuteButton) |
| { |
| setPseudo(AtomString("-webkit-media-controls-fullscreen-volume-max-button", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlFullscreenVolumeMaxButtonElement> MediaControlFullscreenVolumeMaxButtonElement::create(Document& document) |
| { |
| Ref<MediaControlFullscreenVolumeMaxButtonElement> button = adoptRef(*new MediaControlFullscreenVolumeMaxButtonElement(document)); |
| button->ensureUserAgentShadowRoot(); |
| button->setType("button"); |
| return button; |
| } |
| |
| void MediaControlFullscreenVolumeMaxButtonElement::defaultEventHandler(Event& event) |
| { |
| if (event.type() == eventNames().clickEvent) { |
| mediaController()->setVolume(1); |
| event.setDefaultHandled(); |
| } |
| HTMLInputElement::defaultEventHandler(event); |
| } |
| |
| // ---------------------------- |
| |
| MediaControlTimeRemainingDisplayElement::MediaControlTimeRemainingDisplayElement(Document& document) |
| : MediaControlTimeDisplayElement(document, MediaTimeRemainingDisplay) |
| { |
| setPseudo(getMediaControlTimeRemainingDisplayElementShadowPseudoId()); |
| } |
| |
| Ref<MediaControlTimeRemainingDisplayElement> MediaControlTimeRemainingDisplayElement::create(Document& document) |
| { |
| return adoptRef(*new MediaControlTimeRemainingDisplayElement(document)); |
| } |
| |
| static const AtomString& getMediaControlTimeRemainingDisplayElementShadowPseudoId() |
| { |
| static NeverDestroyed<AtomString> id("-webkit-media-controls-time-remaining-display", AtomString::ConstructFromLiteral); |
| return id; |
| } |
| |
| // ---------------------------- |
| |
| MediaControlCurrentTimeDisplayElement::MediaControlCurrentTimeDisplayElement(Document& document) |
| : MediaControlTimeDisplayElement(document, MediaCurrentTimeDisplay) |
| { |
| setPseudo(getMediaControlCurrentTimeDisplayElementShadowPseudoId()); |
| } |
| |
| Ref<MediaControlCurrentTimeDisplayElement> MediaControlCurrentTimeDisplayElement::create(Document& document) |
| { |
| return adoptRef(*new MediaControlCurrentTimeDisplayElement(document)); |
| } |
| |
| static const AtomString& getMediaControlCurrentTimeDisplayElementShadowPseudoId() |
| { |
| static NeverDestroyed<AtomString> id("-webkit-media-controls-current-time-display", AtomString::ConstructFromLiteral); |
| return id; |
| } |
| |
| // ---------------------------- |
| |
| #if ENABLE(VIDEO_TRACK) |
| |
| MediaControlTextTrackContainerElement::MediaControlTextTrackContainerElement(Document& document) |
| : MediaControlDivElement(document, MediaTextTrackDisplayContainer) |
| , m_updateTimer(*this, &MediaControlTextTrackContainerElement::updateTimerFired) |
| , m_fontSize(0) |
| , m_fontSizeIsImportant(false) |
| , m_updateTextTrackRepresentationStyle(false) |
| { |
| setPseudo(AtomString("-webkit-media-text-track-container", AtomString::ConstructFromLiteral)); |
| } |
| |
| Ref<MediaControlTextTrackContainerElement> MediaControlTextTrackContainerElement::create(Document& document) |
| { |
| auto element = adoptRef(*new MediaControlTextTrackContainerElement(document)); |
| element->hide(); |
| return element; |
| } |
| |
| RenderPtr<RenderElement> MediaControlTextTrackContainerElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
| { |
| return createRenderer<RenderTextTrackContainerElement>(*this, WTFMove(style)); |
| } |
| |
| static bool compareCueIntervalForDisplay(const CueInterval& one, const CueInterval& two) |
| { |
| return one.data()->isPositionedAbove(two.data()); |
| }; |
| |
| void MediaControlTextTrackContainerElement::updateDisplay() |
| { |
| if (!mediaController()->closedCaptionsVisible()) |
| removeChildren(); |
| |
| auto mediaElement = parentMediaElement(this); |
| // 1. If the media element is an audio element, or is another playback |
| // mechanism with no rendering area, abort these steps. There is nothing to |
| // render. |
| if (!mediaElement || !mediaElement->isVideo()) |
| return; |
| |
| // 2. Let video be the media element or other playback mechanism. |
| HTMLVideoElement& video = downcast<HTMLVideoElement>(*mediaElement); |
| |
| // 3. Let output be an empty list of absolutely positioned CSS block boxes. |
| Vector<RefPtr<HTMLDivElement>> output; |
| |
| // 4. If the user agent is exposing a user interface for video, add to |
| // output one or more completely transparent positioned CSS block boxes that |
| // cover the same region as the user interface. |
| |
| // 5. If the last time these rules were run, the user agent was not exposing |
| // a user interface for video, but now it is, let reset be true. Otherwise, |
| // let reset be false. |
| |
| // There is nothing to be done explicitly for 4th and 5th steps, as |
| // everything is handled through CSS. The caption box is on top of the |
| // controls box, in a container set with the -webkit-box display property. |
| |
| // 6. Let tracks be the subset of video's list of text tracks that have as |
| // their rules for updating the text track rendering these rules for |
| // updating the display of WebVTT text tracks, and whose text track mode is |
| // showing or showing by default. |
| // 7. Let cues be an empty list of text track cues. |
| // 8. For each track track in tracks, append to cues all the cues from |
| // track's list of cues that have their text track cue active flag set. |
| CueList activeCues = video.currentlyActiveCues(); |
| |
| // 9. If reset is false, then, for each text track cue cue in cues: if cue's |
| // text track cue display state has a set of CSS boxes, then add those boxes |
| // to output, and remove cue from cues. |
| |
| // There is nothing explicitly to be done here, as all the caching occurs |
| // within the TextTrackCue instance itself. If parameters of the cue change, |
| // the display tree is cleared. |
| |
| // If the number of CSS boxes in the output is less than the number of cues |
| // we wish to render (e.g., we are adding another cue in a set of roll-up |
| // cues), remove all the existing CSS boxes representing the cues and re-add |
| // them so that the new cue is at the bottom. |
| // FIXME: Calling countChildNodes() here is inefficient. We don't need to |
| // traverse all children just to check if there are less children than cues. |
| if (countChildNodes() < activeCues.size()) |
| removeChildren(); |
| |
| activeCues.removeAllMatching([] (CueInterval& cueInterval) { |
| if (!is<VTTCue>(cueInterval.data())) |
| return true; |
| |
| Ref<VTTCue> cue = downcast<VTTCue>(*cueInterval.data()); |
| |
| return !cue->isRenderable() |
| || !cue->track() |
| || !cue->track()->isRendered() |
| || cue->track()->mode() == TextTrack::Mode::Disabled |
| || !cue->isActive() |
| || cue->text().isEmpty(); |
| }); |
| |
| // Sort the active cues for the appropriate display order. For example, for roll-up |
| // or paint-on captions, we need to add the cues in reverse chronological order, |
| // so that the newest captions appear at the bottom. |
| std::sort(activeCues.begin(), activeCues.end(), &compareCueIntervalForDisplay); |
| |
| // 10. For each text track cue cue in cues that has not yet had |
| // corresponding CSS boxes added to output, in text track cue order, run the |
| // following substeps: |
| for (size_t i = 0; i < activeCues.size(); ++i) { |
| if (!mediaController()->closedCaptionsVisible()) |
| continue; |
| |
| RefPtr<VTTCue> cue = downcast<VTTCue>(activeCues[i].data()); |
| |
| DEBUG_LOG(LOGIDENTIFIER, "adding and positioning cue ", i, ": \"", cue->text(), "\", start=", cue->startTime(), ", end=", cue->endTime(), ", line=", cue->line()); |
| Ref<VTTCueBox> displayBox = cue->getDisplayTree(m_videoDisplaySize.size(), m_fontSize); |
| RefPtr<VTTRegion> region = cue->track()->regions()->getRegionById(cue->regionId()); |
| if (!region) { |
| // If cue has an empty text track cue region identifier or there is no |
| // WebVTT region whose region identifier is identical to cue's text |
| // track cue region identifier, run the following substeps: |
| if (displayBox->hasChildNodes() && !contains(displayBox.ptr())) { |
| // Note: the display tree of a cue is removed when the active flag of the cue is unset. |
| appendChild(displayBox); |
| cue->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant); |
| } |
| } else { |
| // Let region be the WebVTT region whose region identifier |
| // matches the text track cue region identifier of cue. |
| Ref<HTMLDivElement> regionNode = region->getDisplayTree(); |
| |
| // Append the region to the viewport, if it was not already. |
| if (!contains(regionNode.ptr())) |
| appendChild(region->getDisplayTree()); |
| |
| region->appendTextTrackCueBox(WTFMove(displayBox)); |
| } |
| } |
| |
| // 11. Return output. |
| if (hasChildNodes()) { |
| show(); |
| updateTextTrackRepresentation(); |
| } else { |
| hide(); |
| clearTextTrackRepresentation(); |
| } |
| } |
| |
| void MediaControlTextTrackContainerElement::updateActiveCuesFontSize() |
| { |
| if (!document().page()) |
| return; |
| |
| auto mediaElement = parentMediaElement(this); |
| if (!mediaElement) |
| return; |
| |
| float smallestDimension = std::min(m_videoDisplaySize.size().height(), m_videoDisplaySize.size().width()); |
| float fontScale = document().page()->group().captionPreferences().captionFontSizeScaleAndImportance(m_fontSizeIsImportant); |
| m_fontSize = lroundf(smallestDimension * fontScale); |
| |
| for (auto& activeCue : mediaElement->currentlyActiveCues()) { |
| RefPtr<TextTrackCue> cue = activeCue.data(); |
| if (!cue->isRenderable()) |
| continue; |
| |
| toVTTCue(cue.get())->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant); |
| } |
| |
| } |
| |
| void MediaControlTextTrackContainerElement::updateTextStrokeStyle() |
| { |
| if (!document().page()) |
| return; |
| |
| auto mediaElement = parentMediaElement(this); |
| if (!mediaElement) |
| return; |
| |
| String language; |
| |
| // FIXME: Since it is possible to have more than one text track enabled, the following code may not find the correct language. |
| // 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 |
| // this differently, see <https://bugs.webkit.org/show_bug.cgi?id=169875>. |
| if (auto* tracks = mediaElement->textTracks()) { |
| for (unsigned i = 0; i < tracks->length(); ++i) { |
| auto track = tracks->item(i); |
| if (track && track->mode() == TextTrack::Mode::Showing) { |
| language = track->validBCP47Language(); |
| break; |
| } |
| } |
| } |
| |
| float strokeWidth; |
| bool important; |
| |
| // 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>. |
| if (document().page()->group().captionPreferences().captionStrokeWidthForFont(m_fontSize, language, strokeWidth, important)) |
| setInlineStyleProperty(CSSPropertyStrokeWidth, strokeWidth, CSSPrimitiveValue::CSS_PX, important); |
| } |
| |
| void MediaControlTextTrackContainerElement::updateTimerFired() |
| { |
| if (!document().page()) |
| return; |
| |
| if (m_textTrackRepresentation) |
| updateStyleForTextTrackRepresentation(); |
| |
| updateActiveCuesFontSize(); |
| updateDisplay(); |
| updateTextStrokeStyle(); |
| } |
| |
| void MediaControlTextTrackContainerElement::updateTextTrackRepresentation() |
| { |
| auto mediaElement = parentMediaElement(this); |
| if (!mediaElement) |
| return; |
| |
| if (!mediaElement->requiresTextTrackRepresentation()) { |
| if (m_textTrackRepresentation) { |
| clearTextTrackRepresentation(); |
| updateSizes(true); |
| } |
| return; |
| } |
| |
| if (!m_textTrackRepresentation) { |
| m_textTrackRepresentation = TextTrackRepresentation::create(*this); |
| if (document().page()) |
| m_textTrackRepresentation->setContentScale(document().page()->deviceScaleFactor()); |
| m_updateTextTrackRepresentationStyle = true; |
| mediaElement->setTextTrackRepresentation(m_textTrackRepresentation.get()); |
| } |
| |
| m_textTrackRepresentation->update(); |
| updateStyleForTextTrackRepresentation(); |
| } |
| |
| void MediaControlTextTrackContainerElement::clearTextTrackRepresentation() |
| { |
| if (!m_textTrackRepresentation) |
| return; |
| |
| m_textTrackRepresentation = nullptr; |
| m_updateTextTrackRepresentationStyle = true; |
| if (auto mediaElement = parentMediaElement(this)) |
| mediaElement->setTextTrackRepresentation(nullptr); |
| updateStyleForTextTrackRepresentation(); |
| updateActiveCuesFontSize(); |
| } |
| |
| void MediaControlTextTrackContainerElement::updateStyleForTextTrackRepresentation() |
| { |
| if (!m_updateTextTrackRepresentationStyle) |
| return; |
| m_updateTextTrackRepresentationStyle = false; |
| |
| if (m_textTrackRepresentation) { |
| setInlineStyleProperty(CSSPropertyWidth, m_videoDisplaySize.size().width(), CSSPrimitiveValue::CSS_PX); |
| setInlineStyleProperty(CSSPropertyHeight, m_videoDisplaySize.size().height(), CSSPrimitiveValue::CSS_PX); |
| setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute); |
| setInlineStyleProperty(CSSPropertyLeft, 0, CSSPrimitiveValue::CSS_PX); |
| setInlineStyleProperty(CSSPropertyTop, 0, CSSPrimitiveValue::CSS_PX); |
| return; |
| } |
| |
| removeInlineStyleProperty(CSSPropertyPosition); |
| removeInlineStyleProperty(CSSPropertyWidth); |
| removeInlineStyleProperty(CSSPropertyHeight); |
| removeInlineStyleProperty(CSSPropertyLeft); |
| removeInlineStyleProperty(CSSPropertyTop); |
| } |
| |
| void MediaControlTextTrackContainerElement::enteredFullscreen() |
| { |
| if (hasChildNodes()) |
| updateTextTrackRepresentation(); |
| updateSizes(true); |
| } |
| |
| void MediaControlTextTrackContainerElement::exitedFullscreen() |
| { |
| clearTextTrackRepresentation(); |
| updateSizes(true); |
| } |
| |
| void MediaControlTextTrackContainerElement::updateSizes(bool forceUpdate) |
| { |
| auto mediaElement = parentMediaElement(this); |
| if (!mediaElement) |
| return; |
| |
| if (!document().page()) |
| return; |
| |
| IntRect videoBox; |
| if (m_textTrackRepresentation) { |
| videoBox = m_textTrackRepresentation->bounds(); |
| float deviceScaleFactor = document().page()->deviceScaleFactor(); |
| videoBox.setWidth(videoBox.width() * deviceScaleFactor); |
| videoBox.setHeight(videoBox.height() * deviceScaleFactor); |
| } else { |
| if (!is<RenderVideo>(mediaElement->renderer())) |
| return; |
| videoBox = downcast<RenderVideo>(*mediaElement->renderer()).videoBox(); |
| } |
| |
| if (!forceUpdate && m_videoDisplaySize == videoBox) |
| return; |
| |
| m_videoDisplaySize = videoBox; |
| m_updateTextTrackRepresentationStyle = true; |
| mediaElement->syncTextTrackBounds(); |
| |
| // FIXME (121170): This function is called during layout, and should lay out the text tracks immediately. |
| m_updateTimer.startOneShot(0_s); |
| } |
| |
| RefPtr<Image> MediaControlTextTrackContainerElement::createTextTrackRepresentationImage() |
| { |
| if (!hasChildNodes()) |
| return nullptr; |
| |
| RefPtr<Frame> frame = document().frame(); |
| if (!frame) |
| return nullptr; |
| |
| document().updateLayout(); |
| |
| auto* renderer = this->renderer(); |
| if (!renderer) |
| return nullptr; |
| |
| if (!renderer->hasLayer()) |
| return nullptr; |
| |
| RenderLayer* layer = downcast<RenderLayerModelObject>(*renderer).layer(); |
| |
| float deviceScaleFactor = 1; |
| if (Page* page = document().page()) |
| deviceScaleFactor = page->deviceScaleFactor(); |
| |
| IntRect paintingRect = IntRect(IntPoint(), layer->size()); |
| |
| // FIXME (149422): This buffer should not be unconditionally unaccelerated. |
| std::unique_ptr<ImageBuffer> buffer(ImageBuffer::create(paintingRect.size(), Unaccelerated, deviceScaleFactor)); |
| if (!buffer) |
| return nullptr; |
| |
| layer->paint(buffer->context(), paintingRect, LayoutSize(), { PaintBehavior::FlattenCompositingLayers, PaintBehavior::Snapshotting }, nullptr, RenderLayer::paintLayerPaintingCompositingAllPhasesFlags()); |
| |
| return ImageBuffer::sinkIntoImage(WTFMove(buffer)); |
| } |
| |
| void MediaControlTextTrackContainerElement::textTrackRepresentationBoundsChanged(const IntRect&) |
| { |
| if (hasChildNodes()) |
| updateTextTrackRepresentation(); |
| updateSizes(); |
| } |
| |
| #if !RELEASE_LOG_DISABLED |
| const Logger& MediaControlTextTrackContainerElement::logger() const |
| { |
| return document().logger(); |
| } |
| |
| const void* MediaControlTextTrackContainerElement::logIdentifier() const |
| { |
| if (auto mediaElement = parentMediaElement(this)) |
| return mediaElement->logIdentifier(); |
| return nullptr; |
| } |
| |
| WTFLogChannel& MediaControlTextTrackContainerElement::logChannel() const |
| { |
| return LogMedia; |
| } |
| #endif // !RELEASE_LOG_DISABLED |
| |
| #endif // ENABLE(VIDEO_TRACK) |
| |
| // ---------------------------- |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(VIDEO) |