| /* |
| * Copyright (C) 2011, 2012 Apple Inc. All rights reserved. |
| * Copyright (C) 2011, 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 COMPUTER, INC. OR |
| * 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 "modules/media_controls/MediaControlsImpl.h" |
| |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "core/dom/ClientRect.h" |
| #include "core/dom/Fullscreen.h" |
| #include "core/dom/MutationCallback.h" |
| #include "core/dom/MutationObserver.h" |
| #include "core/dom/MutationObserverInit.h" |
| #include "core/dom/MutationRecord.h" |
| #include "core/dom/ResizeObserver.h" |
| #include "core/dom/ResizeObserverEntry.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "core/events/KeyboardEvent.h" |
| #include "core/events/MouseEvent.h" |
| #include "core/frame/Settings.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/html/HTMLMediaElement.h" |
| #include "core/html/HTMLVideoElement.h" |
| #include "core/html/media/HTMLMediaElementControlsList.h" |
| #include "core/html/track/TextTrackContainer.h" |
| #include "core/html/track/TextTrackList.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/layout/LayoutTheme.h" |
| #include "core/page/SpatialNavigation.h" |
| #include "modules/media_controls/MediaControlsMediaEventListener.h" |
| #include "modules/media_controls/MediaControlsOrientationLockDelegate.h" |
| #include "modules/media_controls/MediaControlsRotateToFullscreenDelegate.h" |
| #include "modules/media_controls/MediaControlsWindowEventListener.h" |
| #include "modules/media_controls/elements/MediaControlCastButtonElement.h" |
| #include "modules/media_controls/elements/MediaControlCurrentTimeDisplayElement.h" |
| #include "modules/media_controls/elements/MediaControlDownloadButtonElement.h" |
| #include "modules/media_controls/elements/MediaControlFullscreenButtonElement.h" |
| #include "modules/media_controls/elements/MediaControlMuteButtonElement.h" |
| #include "modules/media_controls/elements/MediaControlOverflowMenuButtonElement.h" |
| #include "modules/media_controls/elements/MediaControlOverflowMenuListElement.h" |
| #include "modules/media_controls/elements/MediaControlOverlayEnclosureElement.h" |
| #include "modules/media_controls/elements/MediaControlOverlayPlayButtonElement.h" |
| #include "modules/media_controls/elements/MediaControlPanelElement.h" |
| #include "modules/media_controls/elements/MediaControlPanelEnclosureElement.h" |
| #include "modules/media_controls/elements/MediaControlPlayButtonElement.h" |
| #include "modules/media_controls/elements/MediaControlRemainingTimeDisplayElement.h" |
| #include "modules/media_controls/elements/MediaControlTextTrackListElement.h" |
| #include "modules/media_controls/elements/MediaControlTimelineElement.h" |
| #include "modules/media_controls/elements/MediaControlToggleClosedCaptionsButtonElement.h" |
| #include "modules/media_controls/elements/MediaControlVolumeSliderElement.h" |
| #include "modules/remoteplayback/HTMLMediaElementRemotePlayback.h" |
| #include "modules/remoteplayback/RemotePlayback.h" |
| #include "platform/EventDispatchForbiddenScope.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // TODO(steimel): should have better solution than hard-coding pixel values. |
| // Defined in core/css/mediaControls.css, core/css/mediaControlsAndroid.css, |
| // and core/paint/MediaControlsPainter.cpp. |
| constexpr int kOverlayPlayButtonWidth = 48; |
| constexpr int kOverlayPlayButtonHeight = 48; |
| constexpr int kOverlayBottomMargin = 10; |
| constexpr int kAndroidMediaPanelHeight = 48; |
| |
| constexpr int kMinWidthForOverlayPlayButton = kOverlayPlayButtonWidth; |
| constexpr int kMinHeightForOverlayPlayButton = kOverlayPlayButtonHeight + |
| kAndroidMediaPanelHeight + |
| (2 * kOverlayBottomMargin); |
| |
| // If you change this value, then also update the corresponding value in |
| // LayoutTests/media/media-controls.js. |
| const double kTimeWithoutMouseMovementBeforeHidingMediaControls = 3; |
| |
| bool ShouldShowFullscreenButton(const HTMLMediaElement& media_element) { |
| // Unconditionally allow the user to exit fullscreen if we are in it |
| // now. Especially on android, when we might not yet know if |
| // fullscreen is supported, we sometimes guess incorrectly and show |
| // the button earlier, and we don't want to remove it here if the |
| // user chose to enter fullscreen. crbug.com/500732 . |
| if (media_element.IsFullscreen()) |
| return true; |
| |
| if (!media_element.IsHTMLVideoElement()) |
| return false; |
| |
| if (!media_element.HasVideo()) |
| return false; |
| |
| if (!Fullscreen::FullscreenEnabled(media_element.GetDocument())) |
| return false; |
| |
| if (media_element.ControlsListInternal()->ShouldHideFullscreen()) { |
| UseCounter::Count(media_element.GetDocument(), |
| UseCounter::kHTMLMediaElementControlsListNoFullscreen); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ShouldShowCastButton(HTMLMediaElement& media_element) { |
| if (media_element.FastHasAttribute(HTMLNames::disableremoteplaybackAttr)) |
| return false; |
| |
| // Explicitly do not show cast button when the mediaControlsEnabled setting is |
| // false to make sure the overlay does not appear. |
| Document& document = media_element.GetDocument(); |
| if (document.GetSettings() && |
| !document.GetSettings()->GetMediaControlsEnabled()) |
| return false; |
| |
| // The page disabled the button via the attribute. |
| if (media_element.ControlsListInternal()->ShouldHideRemotePlayback()) { |
| UseCounter::Count( |
| media_element.GetDocument(), |
| UseCounter::kHTMLMediaElementControlsListNoRemotePlayback); |
| return false; |
| } |
| |
| RemotePlayback* remote = |
| HTMLMediaElementRemotePlayback::remote(media_element); |
| return remote && remote->RemotePlaybackAvailable(); |
| } |
| |
| bool PreferHiddenVolumeControls(const Document& document) { |
| return !document.GetSettings() || |
| document.GetSettings()->GetPreferHiddenVolumeControls(); |
| } |
| |
| } // anonymous namespace |
| |
| class MediaControlsImpl::BatchedControlUpdate { |
| WTF_MAKE_NONCOPYABLE(BatchedControlUpdate); |
| STACK_ALLOCATED(); |
| |
| public: |
| explicit BatchedControlUpdate(MediaControlsImpl* controls) |
| : controls_(controls) { |
| DCHECK(IsMainThread()); |
| DCHECK_GE(batch_depth_, 0); |
| ++batch_depth_; |
| } |
| ~BatchedControlUpdate() { |
| DCHECK(IsMainThread()); |
| DCHECK_GT(batch_depth_, 0); |
| if (!(--batch_depth_)) |
| controls_->ComputeWhichControlsFit(); |
| } |
| |
| private: |
| Member<MediaControlsImpl> controls_; |
| static int batch_depth_; |
| }; |
| |
| // Count of number open batches for controls visibility. |
| int MediaControlsImpl::BatchedControlUpdate::batch_depth_ = 0; |
| |
| class MediaControlsImpl::MediaControlsResizeObserverDelegate final |
| : public ResizeObserver::Delegate { |
| public: |
| explicit MediaControlsResizeObserverDelegate(MediaControlsImpl* controls) |
| : controls_(controls) { |
| DCHECK(controls); |
| } |
| ~MediaControlsResizeObserverDelegate() override = default; |
| |
| void OnResize( |
| const HeapVector<Member<ResizeObserverEntry>>& entries) override { |
| DCHECK_EQ(1u, entries.size()); |
| DCHECK_EQ(entries[0]->target(), controls_->MediaElement()); |
| controls_->NotifyElementSizeChanged(entries[0]->contentRect()); |
| } |
| |
| DEFINE_INLINE_TRACE() { |
| visitor->Trace(controls_); |
| ResizeObserver::Delegate::Trace(visitor); |
| } |
| |
| private: |
| Member<MediaControlsImpl> controls_; |
| }; |
| |
| // Observes changes to the HTMLMediaElement attributes that affect controls. |
| // Currently only observes the disableRemotePlayback attribute. |
| class MediaControlsImpl::MediaElementMutationCallback |
| : public MutationCallback { |
| public: |
| explicit MediaElementMutationCallback(MediaControlsImpl* controls) |
| : controls_(controls) { |
| observer_ = MutationObserver::Create(this); |
| Vector<String> filter; |
| filter.push_back(HTMLNames::disableremoteplaybackAttr.ToString()); |
| MutationObserverInit init; |
| init.setAttributeOldValue(true); |
| init.setAttributes(true); |
| init.setAttributeFilter(filter); |
| observer_->observe(&controls_->MediaElement(), init, ASSERT_NO_EXCEPTION); |
| } |
| |
| DEFINE_INLINE_VIRTUAL_TRACE() { |
| visitor->Trace(controls_); |
| visitor->Trace(observer_); |
| MutationCallback::Trace(visitor); |
| } |
| |
| void Disconnect() { observer_->disconnect(); } |
| |
| private: |
| void Call(const HeapVector<Member<MutationRecord>>& records, |
| MutationObserver*) override { |
| for (const auto& record : records) { |
| if (record->type() != "attributes") |
| continue; |
| |
| const Element& element = *ToElement(record->target()); |
| if (record->oldValue() == element.getAttribute(record->attributeName())) |
| continue; |
| |
| DCHECK_EQ(HTMLNames::disableremoteplaybackAttr.ToString(), |
| record->attributeName()); |
| controls_->RefreshCastButtonVisibility(); |
| return; |
| } |
| } |
| |
| ExecutionContext* GetExecutionContext() const override { |
| return &controls_->GetDocument(); |
| } |
| |
| Member<MediaControlsImpl> controls_; |
| Member<MutationObserver> observer_; |
| }; |
| |
| MediaControls* MediaControlsImpl::Factory::Create( |
| HTMLMediaElement& media_element, |
| ShadowRoot& shadow_root) { |
| return MediaControlsImpl::Create(media_element, shadow_root); |
| } |
| |
| MediaControlsImpl::MediaControlsImpl(HTMLMediaElement& media_element) |
| : HTMLDivElement(media_element.GetDocument()), |
| MediaControls(media_element), |
| overlay_enclosure_(nullptr), |
| overlay_play_button_(nullptr), |
| overlay_cast_button_(nullptr), |
| enclosure_(nullptr), |
| panel_(nullptr), |
| play_button_(nullptr), |
| timeline_(nullptr), |
| current_time_display_(nullptr), |
| duration_display_(nullptr), |
| mute_button_(nullptr), |
| volume_slider_(nullptr), |
| toggle_closed_captions_button_(nullptr), |
| text_track_list_(nullptr), |
| overflow_list_(nullptr), |
| cast_button_(nullptr), |
| fullscreen_button_(nullptr), |
| download_button_(nullptr), |
| media_event_listener_(new MediaControlsMediaEventListener(this)), |
| window_event_listener_(MediaControlsWindowEventListener::Create( |
| this, |
| WTF::Bind(&MediaControlsImpl::HideAllMenus, |
| WrapWeakPersistent(this)))), |
| orientation_lock_delegate_(nullptr), |
| rotate_to_fullscreen_delegate_(nullptr), |
| hide_media_controls_timer_( |
| TaskRunnerHelper::Get(TaskType::kUnspecedTimer, |
| &media_element.GetDocument()), |
| this, |
| &MediaControlsImpl::HideMediaControlsTimerFired), |
| hide_timer_behavior_flags_(kIgnoreNone), |
| is_mouse_over_controls_(false), |
| is_paused_for_scrubbing_(false), |
| resize_observer_(ResizeObserver::Create( |
| media_element.GetDocument(), |
| new MediaControlsResizeObserverDelegate(this))), |
| element_size_changed_timer_( |
| TaskRunnerHelper::Get(TaskType::kUnspecedTimer, |
| &media_element.GetDocument()), |
| this, |
| &MediaControlsImpl::ElementSizeChangedTimerFired), |
| keep_showing_until_timer_fires_(false) { |
| resize_observer_->observe(&media_element); |
| } |
| |
| MediaControlsImpl* MediaControlsImpl::Create(HTMLMediaElement& media_element, |
| ShadowRoot& shadow_root) { |
| MediaControlsImpl* controls = new MediaControlsImpl(media_element); |
| controls->SetShadowPseudoId(AtomicString("-webkit-media-controls")); |
| controls->InitializeControls(); |
| controls->Reset(); |
| |
| if (RuntimeEnabledFeatures::videoFullscreenOrientationLockEnabled() && |
| media_element.IsHTMLVideoElement()) { |
| // Initialize the orientation lock when going fullscreen feature. |
| controls->orientation_lock_delegate_ = |
| new MediaControlsOrientationLockDelegate( |
| toHTMLVideoElement(media_element)); |
| } |
| if (RuntimeEnabledFeatures::videoRotateToFullscreenEnabled() && |
| media_element.IsHTMLVideoElement()) { |
| // Initialize the rotate-to-fullscreen feature. |
| controls->rotate_to_fullscreen_delegate_ = |
| new MediaControlsRotateToFullscreenDelegate( |
| toHTMLVideoElement(media_element)); |
| } |
| |
| shadow_root.AppendChild(controls); |
| return controls; |
| } |
| |
| // The media controls DOM structure looks like: |
| // |
| // MediaControlsImpl |
| // (-webkit-media-controls) |
| // +-MediaControlOverlayEnclosureElement |
| // | (-webkit-media-controls-overlay-enclosure) |
| // | +-MediaControlOverlayPlayButtonElement |
| // | | (-webkit-media-controls-overlay-play-button) |
| // | | {if mediaControlsOverlayPlayButtonEnabled} |
| // | \-MediaControlCastButtonElement |
| // | (-internal-media-controls-overlay-cast-button) |
| // \-MediaControlPanelEnclosureElement |
| // | (-webkit-media-controls-enclosure) |
| // \-MediaControlPanelElement |
| // | (-webkit-media-controls-panel) |
| // +-MediaControlPlayButtonElement |
| // | (-webkit-media-controls-play-button) |
| // +-MediaControlCurrentTimeDisplayElement |
| // | (-webkit-media-controls-current-time-display) |
| // +-MediaControlRemainingTimeDisplayElement |
| // | (-webkit-media-controls-time-remaining-display) |
| // +-MediaControlTimelineElement |
| // | (-webkit-media-controls-timeline) |
| // +-MediaControlMuteButtonElement |
| // | (-webkit-media-controls-mute-button) |
| // +-MediaControlVolumeSliderElement |
| // | (-webkit-media-controls-volume-slider) |
| // +-MediaControlFullscreenButtonElement |
| // | (-webkit-media-controls-fullscreen-button) |
| // +-MediaControlDownloadButtonElement |
| // | (-internal-media-controls-download-button) |
| // +-MediaControlToggleClosedCaptionsButtonElement |
| // | (-webkit-media-controls-toggle-closed-captions-button) |
| // \-MediaControlCastButtonElement |
| // (-internal-media-controls-cast-button) |
| // +-MediaControlTextTrackListElement |
| // | (-internal-media-controls-text-track-list) |
| // | {for each renderable text track} |
| // \-MediaControlTextTrackListItem |
| // | (-internal-media-controls-text-track-list-item) |
| // +-MediaControlTextTrackListItemInput |
| // | (-internal-media-controls-text-track-list-item-input) |
| // +-MediaControlTextTrackListItemCaptions |
| // | (-internal-media-controls-text-track-list-kind-captions) |
| // +-MediaControlTextTrackListItemSubtitles |
| // (-internal-media-controls-text-track-list-kind-subtitles) |
| void MediaControlsImpl::InitializeControls() { |
| overlay_enclosure_ = new MediaControlOverlayEnclosureElement(*this); |
| |
| if (RuntimeEnabledFeatures::mediaControlsOverlayPlayButtonEnabled()) { |
| overlay_play_button_ = new MediaControlOverlayPlayButtonElement(*this); |
| overlay_enclosure_->AppendChild(overlay_play_button_); |
| } |
| |
| overlay_cast_button_ = new MediaControlCastButtonElement(*this, true); |
| overlay_enclosure_->AppendChild(overlay_cast_button_); |
| |
| AppendChild(overlay_enclosure_); |
| |
| // Create an enclosing element for the panel so we can visually offset the |
| // controls correctly. |
| enclosure_ = new MediaControlPanelEnclosureElement(*this); |
| |
| panel_ = new MediaControlPanelElement(*this); |
| |
| play_button_ = new MediaControlPlayButtonElement(*this); |
| panel_->AppendChild(play_button_); |
| |
| current_time_display_ = new MediaControlCurrentTimeDisplayElement(*this); |
| current_time_display_->SetIsWanted(true); |
| panel_->AppendChild(current_time_display_); |
| |
| duration_display_ = new MediaControlRemainingTimeDisplayElement(*this); |
| panel_->AppendChild(duration_display_); |
| |
| timeline_ = new MediaControlTimelineElement(*this); |
| panel_->AppendChild(timeline_); |
| |
| mute_button_ = new MediaControlMuteButtonElement(*this); |
| panel_->AppendChild(mute_button_); |
| |
| volume_slider_ = new MediaControlVolumeSliderElement(*this); |
| panel_->AppendChild(volume_slider_); |
| if (PreferHiddenVolumeControls(GetDocument())) |
| volume_slider_->SetIsWanted(false); |
| |
| fullscreen_button_ = new MediaControlFullscreenButtonElement(*this); |
| panel_->AppendChild(fullscreen_button_); |
| |
| download_button_ = new MediaControlDownloadButtonElement(*this); |
| panel_->AppendChild(download_button_); |
| |
| cast_button_ = new MediaControlCastButtonElement(*this, false); |
| panel_->AppendChild(cast_button_); |
| |
| toggle_closed_captions_button_ = |
| new MediaControlToggleClosedCaptionsButtonElement(*this); |
| panel_->AppendChild(toggle_closed_captions_button_); |
| |
| enclosure_->AppendChild(panel_); |
| |
| AppendChild(enclosure_); |
| |
| text_track_list_ = new MediaControlTextTrackListElement(*this); |
| AppendChild(text_track_list_); |
| |
| overflow_menu_ = new MediaControlOverflowMenuButtonElement(*this); |
| panel_->AppendChild(overflow_menu_); |
| |
| overflow_list_ = new MediaControlOverflowMenuListElement(*this); |
| AppendChild(overflow_list_); |
| |
| // The order in which we append elements to the overflow list is significant |
| // because it determines how the elements show up in the overflow menu |
| // relative to each other. The first item appended appears at the top of the |
| // overflow menu. |
| overflow_list_->AppendChild(play_button_->CreateOverflowElement( |
| *this, new MediaControlPlayButtonElement(*this))); |
| overflow_list_->AppendChild(fullscreen_button_->CreateOverflowElement( |
| *this, new MediaControlFullscreenButtonElement(*this))); |
| overflow_list_->AppendChild(download_button_->CreateOverflowElement( |
| *this, new MediaControlDownloadButtonElement(*this))); |
| overflow_list_->AppendChild(mute_button_->CreateOverflowElement( |
| *this, new MediaControlMuteButtonElement(*this))); |
| overflow_list_->AppendChild(cast_button_->CreateOverflowElement( |
| *this, new MediaControlCastButtonElement(*this, false))); |
| overflow_list_->AppendChild( |
| toggle_closed_captions_button_->CreateOverflowElement( |
| *this, new MediaControlToggleClosedCaptionsButtonElement(*this))); |
| } |
| |
| Node::InsertionNotificationRequest MediaControlsImpl::InsertedInto( |
| ContainerNode* root) { |
| if (!MediaElement().isConnected()) |
| return HTMLDivElement::InsertedInto(root); |
| |
| // TODO(mlamouri): we should show the controls instead of having |
| // HTMLMediaElement do it. |
| |
| // m_windowEventListener doesn't need to be re-attached as it's only needed |
| // when a menu is visible. |
| media_event_listener_->Attach(); |
| if (orientation_lock_delegate_) |
| orientation_lock_delegate_->Attach(); |
| if (rotate_to_fullscreen_delegate_) |
| rotate_to_fullscreen_delegate_->Attach(); |
| |
| if (!resize_observer_) { |
| resize_observer_ = |
| ResizeObserver::Create(MediaElement().GetDocument(), |
| new MediaControlsResizeObserverDelegate(this)); |
| HTMLMediaElement& html_media_element = MediaElement(); |
| resize_observer_->observe(&html_media_element); |
| } |
| |
| if (!element_mutation_callback_) |
| element_mutation_callback_ = new MediaElementMutationCallback(this); |
| |
| return HTMLDivElement::InsertedInto(root); |
| } |
| |
| void MediaControlsImpl::RemovedFrom(ContainerNode*) { |
| DCHECK(!MediaElement().isConnected()); |
| |
| // TODO(mlamouri): we hide show the controls instead of having |
| // HTMLMediaElement do it. |
| |
| window_event_listener_->Stop(); |
| media_event_listener_->Detach(); |
| if (orientation_lock_delegate_) |
| orientation_lock_delegate_->Detach(); |
| if (rotate_to_fullscreen_delegate_) |
| rotate_to_fullscreen_delegate_->Detach(); |
| |
| if (resize_observer_) { |
| resize_observer_->disconnect(); |
| resize_observer_.Clear(); |
| } |
| |
| if (element_mutation_callback_) { |
| element_mutation_callback_->Disconnect(); |
| element_mutation_callback_.Clear(); |
| } |
| } |
| |
| void MediaControlsImpl::Reset() { |
| EventDispatchForbiddenScope::AllowUserAgentEvents allow_events_in_shadow; |
| BatchedControlUpdate batch(this); |
| |
| const double duration = MediaElement().duration(); |
| duration_display_->setTextContent( |
| LayoutTheme::GetTheme().FormatMediaControlsTime(duration)); |
| duration_display_->SetCurrentValue(duration); |
| |
| // Show everything that we might hide. |
| // If we don't have a duration, then mark it to be hidden. For the |
| // old UI case, want / don't want is the same as show / hide since |
| // it is never marked as not fitting. |
| duration_display_->SetIsWanted(std::isfinite(duration)); |
| current_time_display_->SetIsWanted(true); |
| timeline_->SetIsWanted(true); |
| |
| // If the player has entered an error state, force it into the paused state. |
| if (MediaElement().error()) |
| MediaElement().pause(); |
| |
| UpdatePlayState(); |
| |
| UpdateCurrentTimeDisplay(); |
| |
| timeline_->SetDuration(duration); |
| timeline_->SetPosition(MediaElement().currentTime()); |
| |
| OnVolumeChange(); |
| OnTextTracksAddedOrRemoved(); |
| |
| OnControlsListUpdated(); |
| } |
| |
| void MediaControlsImpl::OnControlsListUpdated() { |
| BatchedControlUpdate batch(this); |
| |
| fullscreen_button_->SetIsWanted(ShouldShowFullscreenButton(MediaElement())); |
| |
| RefreshCastButtonVisibilityWithoutUpdate(); |
| |
| download_button_->SetIsWanted( |
| download_button_->ShouldDisplayDownloadButton()); |
| } |
| |
| LayoutObject* MediaControlsImpl::PanelLayoutObject() { |
| return panel_->GetLayoutObject(); |
| } |
| |
| LayoutObject* MediaControlsImpl::ContainerLayoutObject() { |
| return GetLayoutObject(); |
| } |
| |
| void MediaControlsImpl::MaybeShow() { |
| panel_->SetIsWanted(true); |
| panel_->SetIsDisplayed(true); |
| if (overlay_play_button_) |
| overlay_play_button_->UpdateDisplayType(); |
| // Only make the controls visible if they won't get hidden by OnTimeUpdate. |
| if (MediaElement().paused() || !ShouldHideMediaControls()) |
| MakeOpaque(); |
| } |
| |
| void MediaControlsImpl::Hide() { |
| panel_->SetIsWanted(false); |
| panel_->SetIsDisplayed(false); |
| if (overlay_play_button_) |
| overlay_play_button_->SetIsWanted(false); |
| } |
| |
| bool MediaControlsImpl::IsVisible() const { |
| return panel_->IsOpaque(); |
| } |
| |
| void MediaControlsImpl::MakeOpaque() { |
| panel_->MakeOpaque(); |
| } |
| |
| void MediaControlsImpl::MakeTransparent() { |
| panel_->MakeTransparent(); |
| } |
| |
| bool MediaControlsImpl::ShouldHideMediaControls(unsigned behavior_flags) const { |
| // Never hide for a media element without visual representation. |
| if (!MediaElement().IsHTMLVideoElement() || !MediaElement().HasVideo() || |
| toHTMLVideoElement(MediaElement()).GetMediaRemotingStatus() == |
| HTMLVideoElement::MediaRemotingStatus::kStarted) { |
| return false; |
| } |
| |
| RemotePlayback* remote = |
| HTMLMediaElementRemotePlayback::remote(MediaElement()); |
| if (remote && remote->GetState() != WebRemotePlaybackState::kDisconnected) |
| return false; |
| |
| // Keep the controls visible as long as the timer is running. |
| const bool ignore_wait_for_timer = behavior_flags & kIgnoreWaitForTimer; |
| if (!ignore_wait_for_timer && keep_showing_until_timer_fires_) |
| return false; |
| |
| // Don't hide if the mouse is over the controls. |
| const bool ignore_controls_hover = behavior_flags & kIgnoreControlsHover; |
| if (!ignore_controls_hover && panel_->IsHovered()) |
| return false; |
| |
| // Don't hide if the mouse is over the video area. |
| const bool ignore_video_hover = behavior_flags & kIgnoreVideoHover; |
| if (!ignore_video_hover && is_mouse_over_controls_) |
| return false; |
| |
| // Don't hide if focus is on the HTMLMediaElement or within the |
| // controls/shadow tree. (Perform the checks separately to avoid going |
| // through all the potential ancestor hosts for the focused element.) |
| const bool ignore_focus = behavior_flags & kIgnoreFocus; |
| if (!ignore_focus && (MediaElement().IsFocused() || |
| contains(GetDocument().FocusedElement()))) { |
| return false; |
| } |
| |
| // Don't hide the media controls when a panel is showing. |
| if (text_track_list_->IsWanted() || overflow_list_->IsWanted()) |
| return false; |
| |
| return true; |
| } |
| |
| void MediaControlsImpl::UpdatePlayState() { |
| if (is_paused_for_scrubbing_) |
| return; |
| |
| if (overlay_play_button_) |
| overlay_play_button_->UpdateDisplayType(); |
| play_button_->UpdateDisplayType(); |
| } |
| |
| HTMLDivElement* MediaControlsImpl::PanelElement() { |
| return panel_; |
| } |
| |
| void MediaControlsImpl::BeginScrubbing() { |
| if (!MediaElement().paused()) { |
| is_paused_for_scrubbing_ = true; |
| MediaElement().pause(); |
| } |
| } |
| |
| void MediaControlsImpl::EndScrubbing() { |
| if (is_paused_for_scrubbing_) { |
| is_paused_for_scrubbing_ = false; |
| if (MediaElement().paused()) |
| MediaElement().Play(); |
| } |
| } |
| |
| void MediaControlsImpl::UpdateCurrentTimeDisplay() { |
| double now = MediaElement().currentTime(); |
| double duration = MediaElement().duration(); |
| |
| // Allow the theme to format the time. |
| current_time_display_->setInnerText( |
| LayoutTheme::GetTheme().FormatMediaControlsCurrentTime(now, duration), |
| IGNORE_EXCEPTION_FOR_TESTING); |
| current_time_display_->SetCurrentValue(now); |
| } |
| |
| void MediaControlsImpl::ToggleTextTrackList() { |
| if (!MediaElement().HasClosedCaptions()) { |
| text_track_list_->SetVisible(false); |
| return; |
| } |
| |
| if (!text_track_list_->IsWanted()) |
| window_event_listener_->Start(); |
| |
| text_track_list_->SetVisible(!text_track_list_->IsWanted()); |
| } |
| |
| void MediaControlsImpl::ShowTextTrackAtIndex(unsigned index_to_enable) { |
| TextTrackList* track_list = MediaElement().textTracks(); |
| if (index_to_enable >= track_list->length()) |
| return; |
| TextTrack* track = track_list->AnonymousIndexedGetter(index_to_enable); |
| if (track && track->CanBeRendered()) |
| track->setMode(TextTrack::ShowingKeyword()); |
| } |
| |
| void MediaControlsImpl::DisableShowingTextTracks() { |
| TextTrackList* track_list = MediaElement().textTracks(); |
| for (unsigned i = 0; i < track_list->length(); ++i) { |
| TextTrack* track = track_list->AnonymousIndexedGetter(i); |
| if (track->mode() == TextTrack::ShowingKeyword()) |
| track->setMode(TextTrack::DisabledKeyword()); |
| } |
| } |
| |
| void MediaControlsImpl::RefreshCastButtonVisibility() { |
| RefreshCastButtonVisibilityWithoutUpdate(); |
| BatchedControlUpdate batch(this); |
| } |
| |
| void MediaControlsImpl::RefreshCastButtonVisibilityWithoutUpdate() { |
| if (!ShouldShowCastButton(MediaElement())) { |
| cast_button_->SetIsWanted(false); |
| overlay_cast_button_->SetIsWanted(false); |
| return; |
| } |
| |
| // The reason for the autoplay test is that some pages (e.g. vimeo.com) have |
| // an autoplay background video, which doesn't autoplay on Chrome for Android |
| // (we prevent it) so starts paused. In such cases we don't want to |
| // automatically show the cast button, since it looks strange and is unlikely |
| // to correspond with anything the user wants to do. If a user does want to |
| // cast a paused autoplay video then they can still do so by touching or |
| // clicking on the video, which will cause the cast button to appear. |
| if (!MediaElement().ShouldShowControls() && !MediaElement().Autoplay() && |
| MediaElement().paused()) { |
| // Note that this is a case where we add the overlay cast button |
| // without wanting the panel cast button. We depend on the fact |
| // that computeWhichControlsFit() won't change overlay cast button |
| // visibility in the case where the cast button isn't wanted. |
| // We don't call compute...() here, but it will be called as |
| // non-cast changes (e.g., resize) occur. If the panel button |
| // is shown, however, compute...() will take control of the |
| // overlay cast button if it needs to hide it from the panel. |
| overlay_cast_button_->TryShowOverlay(); |
| cast_button_->SetIsWanted(false); |
| } else if (MediaElement().ShouldShowControls()) { |
| overlay_cast_button_->SetIsWanted(false); |
| cast_button_->SetIsWanted(true); |
| } |
| } |
| |
| void MediaControlsImpl::ShowOverlayCastButtonIfNeeded() { |
| if (MediaElement().ShouldShowControls() || |
| !ShouldShowCastButton(MediaElement())) { |
| return; |
| } |
| |
| overlay_cast_button_->TryShowOverlay(); |
| ResetHideMediaControlsTimer(); |
| } |
| |
| void MediaControlsImpl::EnterFullscreen() { |
| Fullscreen::RequestFullscreen(MediaElement()); |
| } |
| |
| void MediaControlsImpl::ExitFullscreen() { |
| Fullscreen::ExitFullscreen(GetDocument()); |
| } |
| |
| void MediaControlsImpl::RemotePlaybackStateChanged() { |
| cast_button_->UpdateDisplayType(); |
| overlay_cast_button_->UpdateDisplayType(); |
| } |
| |
| void MediaControlsImpl::DefaultEventHandler(Event* event) { |
| HTMLDivElement::DefaultEventHandler(event); |
| |
| // Do not handle events to not interfere with the rest of the page if no |
| // controls should be visible. |
| if (!MediaElement().ShouldShowControls()) |
| return; |
| |
| // Add IgnoreControlsHover to m_hideTimerBehaviorFlags when we see a touch |
| // event, to allow the hide-timer to do the right thing when it fires. |
| // FIXME: Preferably we would only do this when we're actually handling the |
| // event here ourselves. |
| bool is_touch_event = |
| event->IsTouchEvent() || event->IsGestureEvent() || |
| (event->IsMouseEvent() && ToMouseEvent(event)->FromTouch()); |
| hide_timer_behavior_flags_ |= |
| is_touch_event ? kIgnoreControlsHover : kIgnoreNone; |
| |
| // Touch events are treated differently to avoid fake mouse events to trigger |
| // random behavior. The expect behaviour for touch is that a tap will show the |
| // controls and they will hide when the timer to hide fires. |
| if (is_touch_event) { |
| if (event->type() != EventTypeNames::gesturetap) |
| return; |
| |
| if (!ContainsRelatedTarget(event)) { |
| if (!MediaElement().paused()) { |
| if (!IsVisible()) { |
| MakeOpaque(); |
| // When the panel switches from invisible to visible, we need to mark |
| // the event handled to avoid buttons below the tap to be activated. |
| event->SetDefaultHandled(); |
| } |
| if (ShouldHideMediaControls(kIgnoreWaitForTimer)) { |
| keep_showing_until_timer_fires_ = true; |
| StartHideMediaControlsTimer(); |
| } |
| } |
| } |
| |
| return; |
| } |
| |
| if (event->type() == EventTypeNames::mouseover) { |
| if (!ContainsRelatedTarget(event)) { |
| is_mouse_over_controls_ = true; |
| if (!MediaElement().paused()) { |
| MakeOpaque(); |
| if (ShouldHideMediaControls()) |
| StartHideMediaControlsTimer(); |
| } |
| } |
| return; |
| } |
| |
| if (event->type() == EventTypeNames::mouseout) { |
| if (!ContainsRelatedTarget(event)) { |
| is_mouse_over_controls_ = false; |
| StopHideMediaControlsTimer(); |
| } |
| return; |
| } |
| |
| if (event->type() == EventTypeNames::mousemove) { |
| // When we get a mouse move, show the media controls, and start a timer |
| // that will hide the media controls after a 3 seconds without a mouse move. |
| MakeOpaque(); |
| if (ShouldHideMediaControls(kIgnoreVideoHover)) |
| StartHideMediaControlsTimer(); |
| return; |
| } |
| |
| // If the user is interacting with the controls via the keyboard, don't hide |
| // the controls. This will fire when the user tabs between controls (focusin) |
| // or when they seek either the timeline or volume sliders (input). |
| if (event->type() == EventTypeNames::focusin || |
| event->type() == EventTypeNames::input) |
| ResetHideMediaControlsTimer(); |
| |
| if (event->IsKeyboardEvent() && |
| !IsSpatialNavigationEnabled(GetDocument().GetFrame())) { |
| const String& key = ToKeyboardEvent(event)->key(); |
| if (key == "Enter" || ToKeyboardEvent(event)->keyCode() == ' ') { |
| play_button_->OnMediaKeyboardEvent(event); |
| return; |
| } |
| if (key == "ArrowLeft" || key == "ArrowRight" || key == "Home" || |
| key == "End") { |
| timeline_->OnMediaKeyboardEvent(event); |
| return; |
| } |
| if (key == "ArrowDown" || key == "ArrowUp") { |
| volume_slider_->OnMediaKeyboardEvent(event); |
| return; |
| } |
| } |
| } |
| |
| void MediaControlsImpl::HideMediaControlsTimerFired(TimerBase*) { |
| unsigned behavior_flags = |
| hide_timer_behavior_flags_ | kIgnoreFocus | kIgnoreVideoHover; |
| hide_timer_behavior_flags_ = kIgnoreNone; |
| keep_showing_until_timer_fires_ = false; |
| |
| if (MediaElement().paused()) |
| return; |
| |
| if (!ShouldHideMediaControls(behavior_flags)) |
| return; |
| |
| MakeTransparent(); |
| overlay_cast_button_->SetIsWanted(false); |
| } |
| |
| void MediaControlsImpl::StartHideMediaControlsTimer() { |
| hide_media_controls_timer_.StartOneShot( |
| kTimeWithoutMouseMovementBeforeHidingMediaControls, BLINK_FROM_HERE); |
| } |
| |
| void MediaControlsImpl::StopHideMediaControlsTimer() { |
| keep_showing_until_timer_fires_ = false; |
| hide_media_controls_timer_.Stop(); |
| } |
| |
| void MediaControlsImpl::ResetHideMediaControlsTimer() { |
| StopHideMediaControlsTimer(); |
| if (!MediaElement().paused()) |
| StartHideMediaControlsTimer(); |
| } |
| |
| bool MediaControlsImpl::ContainsRelatedTarget(Event* event) { |
| if (!event->IsMouseEvent()) |
| return false; |
| EventTarget* related_target = ToMouseEvent(event)->relatedTarget(); |
| if (!related_target) |
| return false; |
| return contains(related_target->ToNode()); |
| } |
| |
| void MediaControlsImpl::OnVolumeChange() { |
| mute_button_->UpdateDisplayType(); |
| volume_slider_->SetVolume(MediaElement().muted() ? 0 |
| : MediaElement().volume()); |
| |
| // Update visibility of volume controls. |
| // TODO(mlamouri): it should not be part of the volumechange handling because |
| // it is using audio availability as input. |
| BatchedControlUpdate batch(this); |
| volume_slider_->SetIsWanted(MediaElement().HasAudio() && |
| !PreferHiddenVolumeControls(GetDocument())); |
| mute_button_->SetIsWanted(MediaElement().HasAudio()); |
| } |
| |
| void MediaControlsImpl::OnFocusIn() { |
| if (!MediaElement().ShouldShowControls()) |
| return; |
| |
| ResetHideMediaControlsTimer(); |
| MaybeShow(); |
| } |
| |
| void MediaControlsImpl::OnTimeUpdate() { |
| timeline_->SetPosition(MediaElement().currentTime()); |
| UpdateCurrentTimeDisplay(); |
| |
| // 'timeupdate' might be called in a paused state. The controls should not |
| // become transparent in that case. |
| if (MediaElement().paused()) { |
| MakeOpaque(); |
| return; |
| } |
| |
| if (IsVisible() && ShouldHideMediaControls()) |
| MakeTransparent(); |
| } |
| |
| void MediaControlsImpl::OnDurationChange() { |
| const double duration = MediaElement().duration(); |
| |
| // Update the displayed current time/duration. |
| duration_display_->setTextContent( |
| LayoutTheme::GetTheme().FormatMediaControlsTime(duration)); |
| duration_display_->SetCurrentValue(duration); |
| UpdateCurrentTimeDisplay(); |
| |
| // Update the timeline (the UI with the seek marker). |
| timeline_->SetDuration(duration); |
| } |
| |
| void MediaControlsImpl::OnPlay() { |
| UpdatePlayState(); |
| timeline_->SetPosition(MediaElement().currentTime()); |
| UpdateCurrentTimeDisplay(); |
| |
| StartHideMediaControlsTimer(); |
| } |
| |
| void MediaControlsImpl::OnPlaying() { |
| timeline_->OnPlaying(); |
| } |
| |
| void MediaControlsImpl::OnPause() { |
| UpdatePlayState(); |
| timeline_->SetPosition(MediaElement().currentTime()); |
| UpdateCurrentTimeDisplay(); |
| MakeOpaque(); |
| |
| StopHideMediaControlsTimer(); |
| } |
| |
| void MediaControlsImpl::OnTextTracksAddedOrRemoved() { |
| toggle_closed_captions_button_->SetIsWanted( |
| MediaElement().HasClosedCaptions()); |
| BatchedControlUpdate batch(this); |
| } |
| |
| void MediaControlsImpl::OnTextTracksChanged() { |
| toggle_closed_captions_button_->UpdateDisplayType(); |
| } |
| |
| void MediaControlsImpl::OnError() { |
| // TODO(mlamouri): we should only change the aspects of the control that need |
| // to be changed. |
| Reset(); |
| } |
| |
| void MediaControlsImpl::OnLoadedMetadata() { |
| // TODO(mlamouri): we should only change the aspects of the control that need |
| // to be changed. |
| Reset(); |
| } |
| |
| void MediaControlsImpl::OnEnteredFullscreen() { |
| fullscreen_button_->SetIsFullscreen(true); |
| StopHideMediaControlsTimer(); |
| StartHideMediaControlsTimer(); |
| } |
| |
| void MediaControlsImpl::OnExitedFullscreen() { |
| fullscreen_button_->SetIsFullscreen(false); |
| StopHideMediaControlsTimer(); |
| StartHideMediaControlsTimer(); |
| } |
| |
| void MediaControlsImpl::OnPanelKeypress() { |
| // If the user is interacting with the controls via the keyboard, don't hide |
| // the controls. This is called when the user mutes/unmutes, turns CC on/off, |
| // etc. |
| ResetHideMediaControlsTimer(); |
| } |
| |
| void MediaControlsImpl::NotifyElementSizeChanged(ClientRect* new_size) { |
| // Note that this code permits a bad frame on resize, since it is |
| // run after the relayout / paint happens. It would be great to improve |
| // this, but it would be even greater to move this code entirely to |
| // JS and fix it there. |
| |
| IntSize old_size = size_; |
| size_.SetWidth(new_size->width()); |
| size_.SetHeight(new_size->height()); |
| |
| // Adjust for effective zoom. |
| if (panel_->GetLayoutObject() && panel_->GetLayoutObject()->Style()) { |
| size_.SetWidth(ceil(size_.Width() / |
| panel_->GetLayoutObject()->Style()->EffectiveZoom())); |
| size_.SetHeight(ceil(size_.Height() / |
| panel_->GetLayoutObject()->Style()->EffectiveZoom())); |
| } |
| |
| // Don't bother to do any work if this matches the most recent size. |
| if (old_size != size_) |
| element_size_changed_timer_.StartOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void MediaControlsImpl::ElementSizeChangedTimerFired(TimerBase*) { |
| ComputeWhichControlsFit(); |
| } |
| |
| void MediaControlsImpl::ComputeWhichControlsFit() { |
| // Hide all controls that don't fit, and show the ones that do. |
| // This might be better suited for a layout, but since JS media controls |
| // won't benefit from that anwyay, we just do it here like JS will. |
| |
| // Controls that we'll hide / show, in order of decreasing priority. |
| MediaControlElement* elements[] = { |
| // Exclude m_overflowMenu; we handle it specially. |
| play_button_.Get(), |
| fullscreen_button_.Get(), |
| download_button_.Get(), |
| timeline_.Get(), |
| mute_button_.Get(), |
| volume_slider_.Get(), |
| toggle_closed_captions_button_.Get(), |
| cast_button_.Get(), |
| current_time_display_.Get(), |
| duration_display_.Get(), |
| }; |
| |
| // TODO(mlamouri): we need a more dynamic way to find out the width of an |
| // element. |
| const int kSliderMargin = 36; // Sliders have 18px margin on each side. |
| |
| if (!size_.Width()) { |
| // No layout yet -- hide everything, then make them show up later. |
| // This prevents the wrong controls from being shown briefly |
| // immediately after the first layout and paint, but before we have |
| // a chance to revise them. |
| for (MediaControlElement* element : elements) { |
| if (element) |
| element->SetDoesFit(false); |
| } |
| return; |
| } |
| |
| // Assume that all controls require 48px, unless we can get the computed |
| // style for a button. The minimumWidth is recorded and re-use for future |
| // MediaControls instances and future calls to this method given that at the |
| // moment the controls button width is per plataform. |
| // TODO(mlamouri): improve the mechanism without bandaid. |
| static int minimum_width = 48; |
| if (play_button_->GetLayoutObject() && |
| play_button_->GetLayoutObject()->Style()) { |
| const ComputedStyle* style = play_button_->GetLayoutObject()->Style(); |
| minimum_width = ceil(style->Width().Pixels() / style->EffectiveZoom()); |
| } else if (overflow_menu_->GetLayoutObject() && |
| overflow_menu_->GetLayoutObject()->Style()) { |
| const ComputedStyle* style = overflow_menu_->GetLayoutObject()->Style(); |
| minimum_width = ceil(style->Width().Pixels() / style->EffectiveZoom()); |
| } |
| |
| // Insert an overflow menu. However, if we see that the overflow menu |
| // doesn't end up containing at least two elements, we will not display it |
| // but instead make place for the first element that was dropped. |
| overflow_menu_->SetDoesFit(true); |
| overflow_menu_->SetIsWanted(true); |
| int used_width = minimum_width; |
| |
| std::list<MediaControlElement*> overflow_elements; |
| MediaControlElement* first_displaced_element = nullptr; |
| // For each control that fits, enable it in order of decreasing priority. |
| for (MediaControlElement* element : elements) { |
| if (!element) |
| continue; |
| int width = minimum_width; |
| if ((element == timeline_.Get()) || (element == volume_slider_.Get())) |
| width += kSliderMargin; |
| element->ShouldShowButtonInOverflowMenu(false); |
| if (element->IsWanted()) { |
| if (used_width + width <= size_.Width()) { |
| element->SetDoesFit(true); |
| used_width += width; |
| } else { |
| element->SetDoesFit(false); |
| element->ShouldShowButtonInOverflowMenu(true); |
| if (element->HasOverflowButton()) |
| overflow_elements.push_front(element); |
| // We want a way to access the first media element that was |
| // removed. If we don't end up needing an overflow menu, we can |
| // use the space the overflow menu would have taken up to |
| // instead display that media element. |
| if (!element->HasOverflowButton() && !first_displaced_element) |
| first_displaced_element = element; |
| } |
| } |
| } |
| |
| // If we don't have at least two overflow elements, we will not show the |
| // overflow menu. |
| if (overflow_elements.empty()) { |
| overflow_menu_->SetIsWanted(false); |
| used_width -= minimum_width; |
| if (first_displaced_element) { |
| int width = minimum_width; |
| if ((first_displaced_element == timeline_.Get()) || |
| (first_displaced_element == volume_slider_.Get())) |
| width += kSliderMargin; |
| if (used_width + width <= size_.Width()) |
| first_displaced_element->SetDoesFit(true); |
| } |
| } else if (overflow_elements.size() == 1) { |
| overflow_menu_->SetIsWanted(false); |
| overflow_elements.front()->SetDoesFit(true); |
| } |
| |
| // Decide if the overlay play button fits. |
| if (overlay_play_button_) { |
| bool does_fit = size_.Width() >= kMinWidthForOverlayPlayButton && |
| size_.Height() >= kMinHeightForOverlayPlayButton; |
| overlay_play_button_->SetDoesFit(does_fit); |
| } |
| } |
| |
| void MediaControlsImpl::Invalidate(Element* element) { |
| if (!element) |
| return; |
| |
| if (LayoutObject* layout_object = element->GetLayoutObject()) { |
| layout_object |
| ->SetShouldDoFullPaintInvalidationIncludingNonCompositingDescendants(); |
| } |
| } |
| |
| void MediaControlsImpl::NetworkStateChanged() { |
| Invalidate(play_button_); |
| Invalidate(overlay_play_button_); |
| Invalidate(mute_button_); |
| Invalidate(fullscreen_button_); |
| Invalidate(download_button_); |
| Invalidate(timeline_); |
| Invalidate(volume_slider_); |
| |
| // Update the display state of the download button in case we now have a |
| // source or no longer have a source. |
| download_button_->SetIsWanted( |
| download_button_->ShouldDisplayDownloadButton()); |
| } |
| |
| bool MediaControlsImpl::OverflowMenuVisible() { |
| return overflow_list_ ? overflow_list_->IsWanted() : false; |
| } |
| |
| void MediaControlsImpl::ToggleOverflowMenu() { |
| DCHECK(overflow_list_); |
| |
| if (!overflow_list_->IsWanted()) |
| window_event_listener_->Start(); |
| overflow_list_->SetIsWanted(!overflow_list_->IsWanted()); |
| } |
| |
| void MediaControlsImpl::HideAllMenus() { |
| window_event_listener_->Stop(); |
| |
| if (overflow_list_->IsWanted()) |
| overflow_list_->SetIsWanted(false); |
| if (text_track_list_->IsWanted()) |
| text_track_list_->SetVisible(false); |
| } |
| |
| DEFINE_TRACE(MediaControlsImpl) { |
| visitor->Trace(element_mutation_callback_); |
| visitor->Trace(resize_observer_); |
| visitor->Trace(panel_); |
| visitor->Trace(overlay_play_button_); |
| visitor->Trace(overlay_enclosure_); |
| visitor->Trace(play_button_); |
| visitor->Trace(current_time_display_); |
| visitor->Trace(timeline_); |
| visitor->Trace(mute_button_); |
| visitor->Trace(volume_slider_); |
| visitor->Trace(toggle_closed_captions_button_); |
| visitor->Trace(fullscreen_button_); |
| visitor->Trace(download_button_); |
| visitor->Trace(duration_display_); |
| visitor->Trace(enclosure_); |
| visitor->Trace(text_track_list_); |
| visitor->Trace(overflow_menu_); |
| visitor->Trace(overflow_list_); |
| visitor->Trace(cast_button_); |
| visitor->Trace(overlay_cast_button_); |
| visitor->Trace(media_event_listener_); |
| visitor->Trace(window_event_listener_); |
| visitor->Trace(orientation_lock_delegate_); |
| visitor->Trace(rotate_to_fullscreen_delegate_); |
| MediaControls::Trace(visitor); |
| HTMLDivElement::Trace(visitor); |
| } |
| |
| } // namespace blink |