2 * Copyright (C) 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
30 #include "MediaElementSession.h"
33 #include "ChromeClient.h"
36 #include "FrameView.h"
37 #include "HTMLAudioElement.h"
38 #include "HTMLMediaElement.h"
39 #include "HTMLNames.h"
40 #include "HTMLVideoElement.h"
41 #include "HitTestResult.h"
43 #include "MainFrame.h"
45 #include "PlatformMediaSessionManager.h"
46 #include "RenderMedia.h"
47 #include "RenderView.h"
48 #include "ScriptController.h"
49 #include "SourceBuffer.h"
50 #include <wtf/CurrentTime.h>
53 #include "AudioSession.h"
54 #include "RuntimeApplicationChecks.h"
55 #include <wtf/spi/darwin/dyldSPI.h>
60 static const double elementMainContentCheckInterval = .250;
62 static bool isElementRectMostlyInMainFrame(const HTMLMediaElement&);
63 static bool isElementLargeEnoughForMainContent(const HTMLMediaElement&, MediaSessionMainContentPurpose);
66 static String restrictionName(MediaElementSession::BehaviorRestrictions restriction)
68 StringBuilder restrictionBuilder;
69 #define CASE(restrictionType) \
70 if (restriction & MediaElementSession::restrictionType) { \
71 if (!restrictionBuilder.isEmpty()) \
72 restrictionBuilder.appendLiteral(", "); \
73 restrictionBuilder.append(#restrictionType); \
77 CASE(RequireUserGestureForLoad);
78 CASE(RequireUserGestureForVideoRateChange);
79 CASE(RequireUserGestureForAudioRateChange);
80 CASE(RequireUserGestureForFullscreen);
81 CASE(RequirePageConsentToLoadMedia);
82 CASE(RequirePageConsentToResumeMedia);
83 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
84 CASE(RequireUserGestureToShowPlaybackTargetPicker);
85 CASE(WirelessVideoPlaybackDisabled);
87 CASE(InvisibleAutoplayNotPermitted);
88 CASE(OverrideUserGestureRequirementForMainContent);
90 return restrictionBuilder.toString();
94 static bool pageExplicitlyAllowsElementToAutoplayInline(const HTMLMediaElement& element)
96 Document& document = element.document();
97 Page* page = document.page();
98 return document.isMediaDocument() && !document.ownerElement() && page && page->allowsMediaDocumentInlinePlayback();
101 MediaElementSession::MediaElementSession(HTMLMediaElement& element)
102 : PlatformMediaSession(element)
104 , m_restrictions(NoRestrictions)
105 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
106 , m_targetAvailabilityChangedTimer(*this, &MediaElementSession::targetAvailabilityChangedTimerFired)
108 , m_mainContentCheckTimer(*this, &MediaElementSession::mainContentCheckTimerFired)
112 void MediaElementSession::registerWithDocument(Document& document)
114 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
115 document.addPlaybackTargetPickerClient(*this);
117 UNUSED_PARAM(document);
121 void MediaElementSession::unregisterWithDocument(Document& document)
123 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
124 document.removePlaybackTargetPickerClient(*this);
126 UNUSED_PARAM(document);
130 void MediaElementSession::addBehaviorRestriction(BehaviorRestrictions restriction)
132 LOG(Media, "MediaElementSession::addBehaviorRestriction - adding %s", restrictionName(restriction).utf8().data());
133 m_restrictions |= restriction;
135 if (restriction & OverrideUserGestureRequirementForMainContent)
136 m_mainContentCheckTimer.startRepeating(elementMainContentCheckInterval);
139 void MediaElementSession::removeBehaviorRestriction(BehaviorRestrictions restriction)
141 if (restriction & RequireUserGestureToControlControlsManager) {
142 m_mostRecentUserInteractionTime = monotonicallyIncreasingTime();
143 if (auto page = m_element.document().page())
144 page->setAllowsPlaybackControlsForAutoplayingAudio(true);
147 LOG(Media, "MediaElementSession::removeBehaviorRestriction - removing %s", restrictionName(restriction).utf8().data());
148 m_restrictions &= ~restriction;
151 bool MediaElementSession::playbackPermitted(const HTMLMediaElement& element) const
153 if (pageExplicitlyAllowsElementToAutoplayInline(element))
156 if (requiresFullscreenForVideoPlayback(element) && !fullscreenPermitted(element)) {
157 LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE because of fullscreen restriction");
161 if (m_restrictions & OverrideUserGestureRequirementForMainContent && updateIsMainContent())
164 if (m_restrictions & RequireUserGestureForVideoRateChange && element.isVideo() && !ScriptController::processingUserGestureForMedia()) {
165 LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE because of video rate change restriction");
169 if (m_restrictions & RequireUserGestureForAudioRateChange && (!element.isVideo() || element.hasAudio()) && !element.muted() && !ScriptController::processingUserGestureForMedia()) {
170 LOG(Media, "MediaElementSession::playbackPermitted - returning FALSE because of audio rate change restriction");
177 bool MediaElementSession::autoplayPermitted() const
179 const Document& document = m_element.document();
180 if (document.pageCacheState() != Document::NotInPageCache)
182 if (document.activeDOMObjectsAreSuspended())
185 if (!hasBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted))
188 auto* renderer = m_element.renderer();
191 if (renderer->style().visibility() != VISIBLE)
193 if (renderer->view().frameView().isOffscreen())
195 if (renderer->visibleInViewportState() != RenderElement::VisibleInViewport)
200 bool MediaElementSession::dataLoadingPermitted(const HTMLMediaElement&) const
202 if (m_restrictions & OverrideUserGestureRequirementForMainContent && updateIsMainContent())
205 if (m_restrictions & RequireUserGestureForLoad && !ScriptController::processingUserGestureForMedia()) {
206 LOG(Media, "MediaElementSession::dataLoadingPermitted - returning FALSE");
213 bool MediaElementSession::fullscreenPermitted(const HTMLMediaElement&) const
215 if (m_restrictions & RequireUserGestureForFullscreen && !ScriptController::processingUserGestureForMedia()) {
216 LOG(Media, "MediaElementSession::fullscreenPermitted - returning FALSE");
223 bool MediaElementSession::pageAllowsDataLoading(const HTMLMediaElement& element) const
225 Page* page = element.document().page();
226 if (m_restrictions & RequirePageConsentToLoadMedia && page && !page->canStartMedia()) {
227 LOG(Media, "MediaElementSession::pageAllowsDataLoading - returning FALSE");
234 bool MediaElementSession::pageAllowsPlaybackAfterResuming(const HTMLMediaElement& element) const
236 Page* page = element.document().page();
237 if (m_restrictions & RequirePageConsentToResumeMedia && page && !page->canStartMedia()) {
238 LOG(Media, "MediaElementSession::pageAllowsPlaybackAfterResuming - returning FALSE");
245 bool MediaElementSession::canShowControlsManager(PlaybackControlsPurpose purpose) const
247 if (m_element.isFullscreen()) {
248 LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Is fullscreen");
252 if (m_element.muted()) {
253 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Muted");
257 if (m_element.document().isMediaDocument()) {
258 LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Is media document");
262 if (client().presentationType() == Audio) {
263 if (!hasBehaviorRestriction(RequireUserGestureToControlControlsManager) || ScriptController::processingUserGestureForMedia()) {
264 LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Audio element with user gesture");
268 if (m_element.isPlaying() && allowsPlaybackControlsForAutoplayingAudio()) {
269 LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: User has played media before");
273 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Audio element is not suitable");
277 if (purpose == PlaybackControlsPurpose::ControlsManager && !isElementRectMostlyInMainFrame(m_element)) {
278 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Not in main frame");
282 if (!m_element.hasAudio() && !m_element.hasEverHadAudio()) {
283 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No audio");
287 if (m_element.document().activeDOMObjectsAreSuspended()) {
288 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: activeDOMObjectsAreSuspended()");
292 if (!playbackPermitted(m_element)) {
293 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Playback not permitted");
297 if (!hasBehaviorRestriction(RequireUserGestureToControlControlsManager) || ScriptController::processingUserGestureForMedia()) {
298 LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: No user gesture required");
302 if (purpose == PlaybackControlsPurpose::ControlsManager && hasBehaviorRestriction(RequirePlaybackToControlControlsManager) && !m_element.isPlaying()) {
303 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Needs to be playing");
307 if (!m_element.hasEverNotifiedAboutPlaying()) {
308 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Hasn't fired playing notification");
312 // Only allow the main content heuristic to forbid videos from showing up if our purpose is the controls manager.
313 if (purpose == PlaybackControlsPurpose::ControlsManager && m_element.isVideo()) {
314 if (!m_element.renderer()) {
315 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No renderer");
319 if (!m_element.hasVideo() && !m_element.hasEverHadVideo()) {
320 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No video");
324 if (isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls)) {
325 LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Is main content");
330 if (purpose == PlaybackControlsPurpose::NowPlaying) {
331 LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Potentially plays audio");
335 LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No user gesture");
339 bool MediaElementSession::isLargeEnoughForMainContent(MediaSessionMainContentPurpose purpose) const
341 return isElementLargeEnoughForMainContent(m_element, purpose);
344 double MediaElementSession::mostRecentUserInteractionTime() const
346 return m_mostRecentUserInteractionTime;
349 bool MediaElementSession::wantsToObserveViewportVisibilityForMediaControls() const
351 return isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls);
354 bool MediaElementSession::wantsToObserveViewportVisibilityForAutoplay() const
356 return hasBehaviorRestriction(InvisibleAutoplayNotPermitted) || hasBehaviorRestriction(OverrideUserGestureRequirementForMainContent);
359 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
360 void MediaElementSession::showPlaybackTargetPicker(const HTMLMediaElement& element)
362 LOG(Media, "MediaElementSession::showPlaybackTargetPicker");
364 if (m_restrictions & RequireUserGestureToShowPlaybackTargetPicker && !ScriptController::processingUserGestureForMedia()) {
365 LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because of permissions");
369 if (!element.document().page()) {
370 LOG(Media, "MediaElementSession::showingPlaybackTargetPickerPermitted - returning early because page is NULL");
375 if (element.readyState() < HTMLMediaElementEnums::HAVE_METADATA) {
376 LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because element is not playable");
381 element.document().showPlaybackTargetPicker(*this, is<HTMLVideoElement>(element));
384 bool MediaElementSession::hasWirelessPlaybackTargets(const HTMLMediaElement&) const
387 // FIXME: consolidate Mac and iOS implementations
388 m_hasPlaybackTargets = PlatformMediaSessionManager::sharedManager().hasWirelessTargetsAvailable();
391 LOG(Media, "MediaElementSession::hasWirelessPlaybackTargets - returning %s", m_hasPlaybackTargets ? "TRUE" : "FALSE");
393 return m_hasPlaybackTargets;
396 bool MediaElementSession::wirelessVideoPlaybackDisabled(const HTMLMediaElement& element) const
398 Settings* settings = element.document().settings();
399 if (!settings || !settings->allowsAirPlayForMediaPlayback()) {
400 LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of settings");
404 if (element.hasAttributeWithoutSynchronization(HTMLNames::webkitwirelessvideoplaybackdisabledAttr)) {
405 LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of attribute");
410 String legacyAirplayAttributeValue = element.attributeWithoutSynchronization(HTMLNames::webkitairplayAttr);
411 if (equalLettersIgnoringASCIICase(legacyAirplayAttributeValue, "deny")) {
412 LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of legacy attribute");
415 if (equalLettersIgnoringASCIICase(legacyAirplayAttributeValue, "allow")) {
416 LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning FALSE because of legacy attribute");
421 MediaPlayer* player = element.player();
425 bool disabled = player->wirelessVideoPlaybackDisabled();
426 LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning %s because media engine says so", disabled ? "TRUE" : "FALSE");
431 void MediaElementSession::setWirelessVideoPlaybackDisabled(const HTMLMediaElement& element, bool disabled)
434 addBehaviorRestriction(WirelessVideoPlaybackDisabled);
436 removeBehaviorRestriction(WirelessVideoPlaybackDisabled);
438 MediaPlayer* player = element.player();
442 LOG(Media, "MediaElementSession::setWirelessVideoPlaybackDisabled - disabled %s", disabled ? "TRUE" : "FALSE");
443 player->setWirelessVideoPlaybackDisabled(disabled);
446 void MediaElementSession::setHasPlaybackTargetAvailabilityListeners(const HTMLMediaElement& element, bool hasListeners)
448 LOG(Media, "MediaElementSession::setHasPlaybackTargetAvailabilityListeners - hasListeners %s", hasListeners ? "TRUE" : "FALSE");
451 UNUSED_PARAM(element);
452 m_hasPlaybackTargetAvailabilityListeners = hasListeners;
453 PlatformMediaSessionManager::sharedManager().configureWireLessTargetMonitoring();
455 UNUSED_PARAM(hasListeners);
456 element.document().playbackTargetPickerClientStateDidChange(*this, element.mediaState());
460 void MediaElementSession::setPlaybackTarget(Ref<MediaPlaybackTarget>&& device)
462 m_playbackTarget = WTFMove(device);
463 client().setWirelessPlaybackTarget(*m_playbackTarget.copyRef());
466 void MediaElementSession::targetAvailabilityChangedTimerFired()
468 client().wirelessRoutesAvailableDidChange();
471 void MediaElementSession::externalOutputDeviceAvailableDidChange(bool hasTargets)
473 if (m_hasPlaybackTargets == hasTargets)
476 LOG(Media, "MediaElementSession::externalOutputDeviceAvailableDidChange(%p) - hasTargets %s", this, hasTargets ? "TRUE" : "FALSE");
478 m_hasPlaybackTargets = hasTargets;
479 m_targetAvailabilityChangedTimer.startOneShot(0);
482 bool MediaElementSession::canPlayToWirelessPlaybackTarget() const
485 if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
489 return client().canPlayToWirelessPlaybackTarget();
492 bool MediaElementSession::isPlayingToWirelessPlaybackTarget() const
495 if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
499 return client().isPlayingToWirelessPlaybackTarget();
502 void MediaElementSession::setShouldPlayToPlaybackTarget(bool shouldPlay)
504 LOG(Media, "MediaElementSession::setShouldPlayToPlaybackTarget - shouldPlay %s", shouldPlay ? "TRUE" : "FALSE");
505 m_shouldPlayToPlaybackTarget = shouldPlay;
506 client().setShouldPlayToPlaybackTarget(shouldPlay);
509 void MediaElementSession::mediaStateDidChange(const HTMLMediaElement& element, MediaProducer::MediaStateFlags state)
511 element.document().playbackTargetPickerClientStateDidChange(*this, state);
515 MediaPlayer::Preload MediaElementSession::effectivePreloadForElement(const HTMLMediaElement& element) const
517 MediaPlayer::Preload preload = element.preloadValue();
519 if (pageExplicitlyAllowsElementToAutoplayInline(element))
522 if (m_restrictions & MetadataPreloadingNotPermitted)
523 return MediaPlayer::None;
525 if (m_restrictions & AutoPreloadingNotPermitted) {
526 if (preload > MediaPlayer::MetaData)
527 return MediaPlayer::MetaData;
533 bool MediaElementSession::requiresFullscreenForVideoPlayback(const HTMLMediaElement& element) const
535 if (pageExplicitlyAllowsElementToAutoplayInline(element))
538 if (is<HTMLAudioElement>(element))
541 Settings* settings = element.document().settings();
542 if (!settings || !settings->allowsInlineMediaPlayback())
545 if (!settings->inlineMediaPlaybackRequiresPlaysInlineAttribute())
549 if (IOSApplication::isIBooks())
550 return !element.hasAttributeWithoutSynchronization(HTMLNames::webkit_playsinlineAttr) && !element.hasAttributeWithoutSynchronization(HTMLNames::playsinlineAttr);
551 if (dyld_get_program_sdk_version() < DYLD_IOS_VERSION_10_0)
552 return !element.hasAttributeWithoutSynchronization(HTMLNames::webkit_playsinlineAttr);
554 return !element.hasAttributeWithoutSynchronization(HTMLNames::playsinlineAttr);
557 bool MediaElementSession::allowsAutomaticMediaDataLoading(const HTMLMediaElement& element) const
559 if (pageExplicitlyAllowsElementToAutoplayInline(element))
562 Settings* settings = element.document().settings();
563 if (settings && settings->mediaDataLoadsAutomatically())
569 void MediaElementSession::mediaEngineUpdated(const HTMLMediaElement& element)
571 LOG(Media, "MediaElementSession::mediaEngineUpdated");
573 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
574 if (m_restrictions & WirelessVideoPlaybackDisabled)
575 setWirelessVideoPlaybackDisabled(element, true);
576 if (m_playbackTarget)
577 client().setWirelessPlaybackTarget(*m_playbackTarget.copyRef());
578 if (m_shouldPlayToPlaybackTarget)
579 client().setShouldPlayToPlaybackTarget(true);
581 UNUSED_PARAM(element);
586 void MediaElementSession::resetPlaybackSessionState()
588 m_mostRecentUserInteractionTime = 0;
589 addBehaviorRestriction(RequireUserGestureToControlControlsManager | RequirePlaybackToControlControlsManager);
592 bool MediaElementSession::allowsPictureInPicture(const HTMLMediaElement& element) const
594 Settings* settings = element.document().settings();
595 return settings && settings->allowsPictureInPictureMediaPlayback() && !element.webkitCurrentPlaybackTargetIsWireless();
599 bool MediaElementSession::requiresPlaybackTargetRouteMonitoring() const
601 return m_hasPlaybackTargetAvailabilityListeners && !client().elementIsHidden();
605 #if ENABLE(MEDIA_SOURCE)
606 const unsigned fiveMinutesOf1080PVideo = 290 * 1024 * 1024; // 290 MB is approximately 5 minutes of 8Mbps (1080p) content.
607 const unsigned fiveMinutesStereoAudio = 14 * 1024 * 1024; // 14 MB is approximately 5 minutes of 384kbps content.
609 size_t MediaElementSession::maximumMediaSourceBufferSize(const SourceBuffer& buffer) const
611 // A good quality 1080p video uses 8,000 kbps and stereo audio uses 384 kbps, so assume 95% for video and 5% for audio.
612 const float bufferBudgetPercentageForVideo = .95;
613 const float bufferBudgetPercentageForAudio = .05;
616 Settings* settings = buffer.document().settings();
618 maximum = settings->maximumSourceBufferSize();
620 maximum = fiveMinutesOf1080PVideo + fiveMinutesStereoAudio;
622 // Allow a SourceBuffer to buffer as though it is audio-only even if it doesn't have any active tracks (yet).
623 size_t bufferSize = static_cast<size_t>(maximum * bufferBudgetPercentageForAudio);
624 if (buffer.hasVideo())
625 bufferSize += static_cast<size_t>(maximum * bufferBudgetPercentageForVideo);
627 // FIXME: we might want to modify this algorithm to:
628 // - decrease the maximum size for background tabs
629 // - decrease the maximum size allowed for inactive elements when a process has more than one
630 // element, eg. so a page with many elements which are played one at a time doesn't keep
631 // everything buffered after an element has finished playing.
637 static bool isMainContentForPurposesOfAutoplay(const HTMLMediaElement& element)
639 if (!element.hasAudio() || !element.hasVideo())
642 // Elements which have not yet been laid out, or which are not yet in the DOM, cannot be main content.
643 auto* renderer = element.renderer();
647 if (!isElementLargeEnoughForMainContent(element, MediaSessionMainContentPurpose::Autoplay))
650 // Elements which are hidden by style, or have been scrolled out of view, cannot be main content.
651 // But elements which have audio & video and are already playing should not stop playing because
652 // they are scrolled off the page.
653 if (renderer->style().visibility() != VISIBLE)
655 if (renderer->visibleInViewportState() != RenderElement::VisibleInViewport && !element.isPlaying())
658 // Main content elements must be in the main frame.
659 Document& document = element.document();
660 if (!document.frame() || !document.frame()->isMainFrame())
663 MainFrame& mainFrame = document.frame()->mainFrame();
664 if (!mainFrame.view() || !mainFrame.view()->renderView())
667 RenderView& mainRenderView = *mainFrame.view()->renderView();
669 // Hit test the area of the main frame where the element appears, to determine if the element is being obscured.
670 IntRect rectRelativeToView = element.clientRect();
671 ScrollPosition scrollPosition = mainFrame.view()->documentScrollPositionRelativeToViewOrigin();
672 IntRect rectRelativeToTopDocument(rectRelativeToView.location() + scrollPosition, rectRelativeToView.size());
673 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AllowChildFrameContent | HitTestRequest::IgnoreClipping | HitTestRequest::DisallowUserAgentShadowContent);
674 HitTestResult result(rectRelativeToTopDocument.center());
676 // Elements which are obscured by other elements cannot be main content.
677 mainRenderView.hitTest(request, result);
678 result.setToNonUserAgentShadowAncestor();
679 Element* hitElement = result.innerElement();
680 if (hitElement != &element)
686 static bool isElementRectMostlyInMainFrame(const HTMLMediaElement& element)
688 if (!element.renderer())
691 auto* documentFrame = element.document().frame();
695 auto mainFrameView = documentFrame->mainFrame().view();
699 IntRect mainFrameRectAdjustedForScrollPosition = IntRect(-mainFrameView->documentScrollPositionRelativeToViewOrigin(), mainFrameView->contentsSize());
700 IntRect elementRectInMainFrame = element.clientRect();
701 unsigned totalElementArea = elementRectInMainFrame.area().unsafeGet();
702 elementRectInMainFrame.intersect(mainFrameRectAdjustedForScrollPosition);
704 return elementRectInMainFrame.area().unsafeGet() > totalElementArea / 2;
707 static bool isElementLargeRelativeToMainFrame(const HTMLMediaElement& element)
709 static const double minimumPercentageOfMainFrameAreaForMainContent = 0.9;
710 auto* renderer = element.renderer();
714 auto* documentFrame = element.document().frame();
718 if (!documentFrame->mainFrame().view())
721 auto& mainFrameView = *documentFrame->mainFrame().view();
722 auto maxVisibleClientWidth = std::min(renderer->clientWidth().toInt(), mainFrameView.visibleWidth());
723 auto maxVisibleClientHeight = std::min(renderer->clientHeight().toInt(), mainFrameView.visibleHeight());
725 return maxVisibleClientWidth * maxVisibleClientHeight > minimumPercentageOfMainFrameAreaForMainContent * mainFrameView.visibleWidth() * mainFrameView.visibleHeight();
728 static bool isElementLargeEnoughForMainContent(const HTMLMediaElement& element, MediaSessionMainContentPurpose purpose)
730 static const double elementMainContentAreaMinimum = 400 * 300;
731 static const double maximumAspectRatio = purpose == MediaSessionMainContentPurpose::MediaControls ? 3 : 1.8;
732 static const double minimumAspectRatio = .5; // Slightly smaller than 9:16.
734 // Elements which have not yet been laid out, or which are not yet in the DOM, cannot be main content.
735 auto* renderer = element.renderer();
739 double width = renderer->clientWidth();
740 double height = renderer->clientHeight();
741 double area = width * height;
742 double aspectRatio = width / height;
744 if (area < elementMainContentAreaMinimum)
747 if (aspectRatio >= minimumAspectRatio && aspectRatio <= maximumAspectRatio)
750 return isElementLargeRelativeToMainFrame(element);
753 void MediaElementSession::mainContentCheckTimerFired()
755 if (!hasBehaviorRestriction(OverrideUserGestureRequirementForMainContent))
758 updateIsMainContent();
761 bool MediaElementSession::updateIsMainContent() const
763 bool wasMainContent = m_isMainContent;
764 m_isMainContent = isMainContentForPurposesOfAutoplay(m_element);
766 if (m_isMainContent != wasMainContent)
767 m_element.updateShouldPlay();
769 return m_isMainContent;
772 bool MediaElementSession::allowsNowPlayingControlsVisibility() const
774 auto page = m_element.document().page();
775 return page && !page->isVisibleAndActive();
778 bool MediaElementSession::allowsPlaybackControlsForAutoplayingAudio() const
780 auto page = m_element.document().page();
781 return page && page->allowsPlaybackControlsForAutoplayingAudio();
786 #endif // ENABLE(VIDEO)