Add inspector logging for MediaElementSession autoplay
[WebKit-https.git] / Source / WebCore / html / MediaElementSession.cpp
1 /*
2  * Copyright (C) 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
26 #include "config.h"
27
28 #if ENABLE(VIDEO)
29
30 #include "MediaElementSession.h"
31
32 #include "Document.h"
33 #include "DocumentLoader.h"
34 #include "Frame.h"
35 #include "FrameView.h"
36 #include "HTMLAudioElement.h"
37 #include "HTMLMediaElement.h"
38 #include "HTMLNames.h"
39 #include "HTMLVideoElement.h"
40 #include "HitTestResult.h"
41 #include "Logging.h"
42 #include "MainFrame.h"
43 #include "Page.h"
44 #include "PlatformMediaSessionManager.h"
45 #include "RenderMedia.h"
46 #include "RenderView.h"
47 #include "ScriptController.h"
48 #include "Settings.h"
49 #include "SourceBuffer.h"
50 #include <wtf/CurrentTime.h>
51 #include <wtf/text/StringBuilder.h>
52
53 #if PLATFORM(IOS)
54 #include "AudioSession.h"
55 #include "RuntimeApplicationChecks.h"
56 #include <wtf/spi/darwin/dyldSPI.h>
57 #endif
58
59 namespace WebCore {
60
61 static const Seconds elementMainContentCheckInterval { 250_ms };
62
63 static bool isElementRectMostlyInMainFrame(const HTMLMediaElement&);
64 static bool isElementLargeEnoughForMainContent(const HTMLMediaElement&, MediaSessionMainContentPurpose);
65
66 #if !LOG_DISABLED
67 static String restrictionName(MediaElementSession::BehaviorRestrictions restriction)
68 {
69     StringBuilder restrictionBuilder;
70 #define CASE(restrictionType) \
71     if (restriction & MediaElementSession::restrictionType) { \
72         if (!restrictionBuilder.isEmpty()) \
73             restrictionBuilder.appendLiteral(", "); \
74         restrictionBuilder.append(#restrictionType); \
75     } \
76
77     CASE(NoRestrictions);
78     CASE(RequireUserGestureForLoad);
79     CASE(RequireUserGestureForVideoRateChange);
80     CASE(RequireUserGestureForAudioRateChange);
81     CASE(RequireUserGestureForFullscreen);
82     CASE(RequirePageConsentToLoadMedia);
83     CASE(RequirePageConsentToResumeMedia);
84 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
85     CASE(RequireUserGestureToShowPlaybackTargetPicker);
86     CASE(WirelessVideoPlaybackDisabled);
87 #endif
88     CASE(InvisibleAutoplayNotPermitted);
89     CASE(OverrideUserGestureRequirementForMainContent);
90
91     return restrictionBuilder.toString();
92 }
93 #endif
94
95 static bool pageExplicitlyAllowsElementToAutoplayInline(const HTMLMediaElement& element)
96 {
97     Document& document = element.document();
98     Page* page = document.page();
99     return document.isMediaDocument() && !document.ownerElement() && page && page->allowsMediaDocumentInlinePlayback();
100 }
101
102 MediaElementSession::MediaElementSession(HTMLMediaElement& element)
103     : PlatformMediaSession(element)
104     , m_element(element)
105     , m_restrictions(NoRestrictions)
106 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
107     , m_targetAvailabilityChangedTimer(*this, &MediaElementSession::targetAvailabilityChangedTimerFired)
108 #endif
109     , m_mainContentCheckTimer(*this, &MediaElementSession::mainContentCheckTimerFired)
110 {
111 }
112
113 void MediaElementSession::registerWithDocument(Document& document)
114 {
115 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
116     document.addPlaybackTargetPickerClient(*this);
117 #else
118     UNUSED_PARAM(document);
119 #endif
120 }
121
122 void MediaElementSession::unregisterWithDocument(Document& document)
123 {
124 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
125     document.removePlaybackTargetPickerClient(*this);
126 #else
127     UNUSED_PARAM(document);
128 #endif
129 }
130
131 void MediaElementSession::addBehaviorRestriction(BehaviorRestrictions restriction)
132 {
133     LOG(Media, "MediaElementSession::addBehaviorRestriction - adding %s", restrictionName(restriction).utf8().data());
134     m_restrictions |= restriction;
135
136     if (restriction & OverrideUserGestureRequirementForMainContent)
137         m_mainContentCheckTimer.startRepeating(elementMainContentCheckInterval);
138 }
139
140 void MediaElementSession::removeBehaviorRestriction(BehaviorRestrictions restriction)
141 {
142     if (restriction & RequireUserGestureToControlControlsManager) {
143         m_mostRecentUserInteractionTime = monotonicallyIncreasingTime();
144         if (auto page = m_element.document().page())
145             page->setAllowsPlaybackControlsForAutoplayingAudio(true);
146     }
147
148     LOG(Media, "MediaElementSession::removeBehaviorRestriction - removing %s", restrictionName(restriction).utf8().data());
149     m_restrictions &= ~restriction;
150 }
151
152 #if PLATFORM(MAC)
153 static bool needsArbitraryUserGestureAutoplayQuirk(const Document& document)
154 {
155     if (!document.settings().needsSiteSpecificQuirks())
156         return false;
157
158     auto* loader = document.loader();
159     return loader && loader->allowedAutoplayQuirks().contains(AutoplayQuirk::ArbitraryUserGestures);
160 }
161 #endif // PLATFORM(MAC)
162
163 SuccessOr<MediaPlaybackDenialReason> MediaElementSession::playbackPermitted(const HTMLMediaElement& element) const
164 {
165     if (element.document().isMediaDocument() && !element.document().ownerElement())
166         return { };
167
168     if (pageExplicitlyAllowsElementToAutoplayInline(element))
169         return { };
170
171     if (requiresFullscreenForVideoPlayback(element) && !fullscreenPermitted(element)) {
172         ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because of fullscreen restriction");
173         return MediaPlaybackDenialReason::FullscreenRequired;
174     }
175
176     if (m_restrictions & OverrideUserGestureRequirementForMainContent && updateIsMainContent())
177         return { };
178
179 #if ENABLE(MEDIA_STREAM)
180     if (element.hasMediaStreamSrcObject()) {
181         if (element.document().isCapturing())
182             return { };
183         if (element.document().mediaState() & MediaProducer::IsPlayingAudio)
184             return { };
185     }
186 #endif
187
188 #if PLATFORM(MAC)
189     // FIXME <https://webkit.org/b/175856>: Make this dependent on a runtime flag for desktop autoplay restrictions.
190     const auto& topDocument = element.document().topDocument();
191     if (topDocument.mediaState() & MediaProducer::HasUserInteractedWithMediaElement && topDocument.settings().needsSiteSpecificQuirks())
192         return { };
193
194     if (element.document().hasHadUserInteraction() && needsArbitraryUserGestureAutoplayQuirk(element.document()))
195         return { };
196 #endif
197
198     if (m_restrictions & RequireUserGestureForVideoRateChange && element.isVideo() && !element.document().processingUserGestureForMedia()) {
199         ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because a user gesture is required for video rate change restriction");
200         return MediaPlaybackDenialReason::UserGestureRequired;
201     }
202
203     if (m_restrictions & RequireUserGestureForAudioRateChange && (!element.isVideo() || element.hasAudio()) && !element.muted() && element.volume() && !element.document().processingUserGestureForMedia()) {
204         ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because a user gesture is required for audio rate change restriction");
205         return MediaPlaybackDenialReason::UserGestureRequired;
206     }
207
208     if (m_restrictions & RequireUserGestureForVideoDueToLowPowerMode && element.isVideo() && !element.document().processingUserGestureForMedia()) {
209         ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because of video low power mode restriction");
210         return MediaPlaybackDenialReason::UserGestureRequired;
211     }
212
213     return { };
214 }
215
216 bool MediaElementSession::autoplayPermitted() const
217 {
218     const Document& document = m_element.document();
219     if (document.pageCacheState() != Document::NotInPageCache)
220         return false;
221     if (document.activeDOMObjectsAreSuspended())
222         return false;
223
224     if (!hasBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted))
225         return true;
226
227     // If the media element is audible, allow autoplay even when not visible as pausing it would be observable by the user.
228     if ((!m_element.isVideo() || m_element.hasAudio()) && !m_element.muted() && m_element.volume())
229         return true;
230
231     auto* renderer = m_element.renderer();
232     if (!renderer) {
233         ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because element has no renderer");
234         return false;
235     }
236     if (renderer->style().visibility() != VISIBLE) {
237         ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because element is not visible");
238         return false;
239     }
240     if (renderer->view().frameView().isOffscreen()) {
241         ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because frame is offscreen");
242         return false;
243     }
244     if (renderer->visibleInViewportState() != VisibleInViewportState::Yes) {
245         ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because element is not visible in the viewport");
246         return false;
247     }
248     return true;
249 }
250
251 bool MediaElementSession::dataLoadingPermitted(const HTMLMediaElement& element) const
252 {
253     if (m_restrictions & OverrideUserGestureRequirementForMainContent && updateIsMainContent())
254         return true;
255
256     if (m_restrictions & RequireUserGestureForLoad && !element.document().processingUserGestureForMedia()) {
257         LOG(Media, "MediaElementSession::dataLoadingPermitted - returning FALSE");
258         return false;
259     }
260
261     return true;
262 }
263
264 bool MediaElementSession::fullscreenPermitted(const HTMLMediaElement& element) const
265 {
266     if (m_restrictions & RequireUserGestureForFullscreen && !element.document().processingUserGestureForMedia()) {
267         LOG(Media, "MediaElementSession::fullscreenPermitted - returning FALSE");
268         return false;
269     }
270
271     return true;
272 }
273
274 bool MediaElementSession::pageAllowsDataLoading(const HTMLMediaElement& element) const
275 {
276     Page* page = element.document().page();
277     if (m_restrictions & RequirePageConsentToLoadMedia && page && !page->canStartMedia()) {
278         LOG(Media, "MediaElementSession::pageAllowsDataLoading - returning FALSE");
279         return false;
280     }
281
282     return true;
283 }
284
285 bool MediaElementSession::pageAllowsPlaybackAfterResuming(const HTMLMediaElement& element) const
286 {
287     Page* page = element.document().page();
288     if (m_restrictions & RequirePageConsentToResumeMedia && page && !page->canStartMedia()) {
289         LOG(Media, "MediaElementSession::pageAllowsPlaybackAfterResuming - returning FALSE");
290         return false;
291     }
292
293     return true;
294 }
295
296 bool MediaElementSession::canShowControlsManager(PlaybackControlsPurpose purpose) const
297 {
298     if (m_element.isFullscreen()) {
299         LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Is fullscreen");
300         return true;
301     }
302
303     if (m_element.muted()) {
304         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Muted");
305         return false;
306     }
307
308     if (m_element.document().isMediaDocument() && (m_element.document().frame() && m_element.document().frame()->isMainFrame())) {
309         LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Is media document");
310         return true;
311     }
312
313     if (client().presentationType() == Audio) {
314         if (!hasBehaviorRestriction(RequireUserGestureToControlControlsManager) || m_element.document().processingUserGestureForMedia()) {
315             LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Audio element with user gesture");
316             return true;
317         }
318
319         if (m_element.isPlaying() && allowsPlaybackControlsForAutoplayingAudio()) {
320             LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: User has played media before");
321             return true;
322         }
323
324         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Audio element is not suitable");
325         return false;
326     }
327
328     if (purpose == PlaybackControlsPurpose::ControlsManager && !isElementRectMostlyInMainFrame(m_element)) {
329         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Not in main frame");
330         return false;
331     }
332
333     if (!m_element.hasAudio() && !m_element.hasEverHadAudio()) {
334         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No audio");
335         return false;
336     }
337
338     if (m_element.document().activeDOMObjectsAreSuspended()) {
339         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: activeDOMObjectsAreSuspended()");
340         return false;
341     }
342
343     if (!playbackPermitted(m_element)) {
344         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Playback not permitted");
345         return false;
346     }
347
348     if (!hasBehaviorRestriction(RequireUserGestureToControlControlsManager) || m_element.document().processingUserGestureForMedia()) {
349         LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: No user gesture required");
350         return true;
351     }
352
353     if (purpose == PlaybackControlsPurpose::ControlsManager && hasBehaviorRestriction(RequirePlaybackToControlControlsManager) && !m_element.isPlaying()) {
354         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Needs to be playing");
355         return false;
356     }
357
358     if (!m_element.hasEverNotifiedAboutPlaying()) {
359         LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: Hasn't fired playing notification");
360         return false;
361     }
362
363     // Only allow the main content heuristic to forbid videos from showing up if our purpose is the controls manager.
364     if (purpose == PlaybackControlsPurpose::ControlsManager && m_element.isVideo()) {
365         if (!m_element.renderer()) {
366             LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No renderer");
367             return false;
368         }
369
370         if (!m_element.hasVideo() && !m_element.hasEverHadVideo()) {
371             LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No video");
372             return false;
373         }
374
375         if (isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls)) {
376             LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Is main content");
377             return true;
378         }
379     }
380
381     if (purpose == PlaybackControlsPurpose::NowPlaying) {
382         LOG(Media, "MediaElementSession::canShowControlsManager - returning TRUE: Potentially plays audio");
383         return true;
384     }
385
386     LOG(Media, "MediaElementSession::canShowControlsManager - returning FALSE: No user gesture");
387     return false;
388 }
389
390 bool MediaElementSession::isLargeEnoughForMainContent(MediaSessionMainContentPurpose purpose) const
391 {
392     return isElementLargeEnoughForMainContent(m_element, purpose);
393 }
394
395 double MediaElementSession::mostRecentUserInteractionTime() const
396 {
397     return m_mostRecentUserInteractionTime;
398 }
399
400 bool MediaElementSession::wantsToObserveViewportVisibilityForMediaControls() const
401 {
402     return isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls);
403 }
404
405 bool MediaElementSession::wantsToObserveViewportVisibilityForAutoplay() const
406 {
407     if (!m_element.isVideo())
408         return false;
409     return hasBehaviorRestriction(InvisibleAutoplayNotPermitted) || hasBehaviorRestriction(OverrideUserGestureRequirementForMainContent);
410 }
411
412 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
413 void MediaElementSession::showPlaybackTargetPicker(const HTMLMediaElement& element)
414 {
415     LOG(Media, "MediaElementSession::showPlaybackTargetPicker");
416
417     if (m_restrictions & RequireUserGestureToShowPlaybackTargetPicker && !element.document().processingUserGestureForMedia()) {
418         LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because of permissions");
419         return;
420     }
421
422     if (!element.document().page()) {
423         LOG(Media, "MediaElementSession::showingPlaybackTargetPickerPermitted - returning early because page is NULL");
424         return;
425     }
426
427 #if !PLATFORM(IOS)
428     if (element.readyState() < HTMLMediaElementEnums::HAVE_METADATA) {
429         LOG(Media, "MediaElementSession::showPlaybackTargetPicker - returning early because element is not playable");
430         return;
431     }
432 #endif
433
434     element.document().showPlaybackTargetPicker(*this, is<HTMLVideoElement>(element));
435 }
436
437 bool MediaElementSession::hasWirelessPlaybackTargets(const HTMLMediaElement&) const
438 {
439 #if PLATFORM(IOS)
440     // FIXME: consolidate Mac and iOS implementations
441     m_hasPlaybackTargets = PlatformMediaSessionManager::sharedManager().hasWirelessTargetsAvailable();
442 #endif
443
444     LOG(Media, "MediaElementSession::hasWirelessPlaybackTargets - returning %s", m_hasPlaybackTargets ? "TRUE" : "FALSE");
445
446     return m_hasPlaybackTargets;
447 }
448
449 bool MediaElementSession::wirelessVideoPlaybackDisabled(const HTMLMediaElement& element) const
450 {
451     if (!element.document().settings().allowsAirPlayForMediaPlayback()) {
452         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of settings");
453         return true;
454     }
455
456     if (element.hasAttributeWithoutSynchronization(HTMLNames::webkitwirelessvideoplaybackdisabledAttr)) {
457         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of attribute");
458         return true;
459     }
460
461 #if PLATFORM(IOS)
462     auto& legacyAirplayAttributeValue = element.attributeWithoutSynchronization(HTMLNames::webkitairplayAttr);
463     if (equalLettersIgnoringASCIICase(legacyAirplayAttributeValue, "deny")) {
464         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning TRUE because of legacy attribute");
465         return true;
466     }
467     if (equalLettersIgnoringASCIICase(legacyAirplayAttributeValue, "allow")) {
468         LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning FALSE because of legacy attribute");
469         return false;
470     }
471 #endif
472
473     auto player = element.player();
474     if (!player)
475         return true;
476
477     bool disabled = player->wirelessVideoPlaybackDisabled();
478     LOG(Media, "MediaElementSession::wirelessVideoPlaybackDisabled - returning %s because media engine says so", disabled ? "TRUE" : "FALSE");
479     
480     return disabled;
481 }
482
483 void MediaElementSession::setWirelessVideoPlaybackDisabled(const HTMLMediaElement& element, bool disabled)
484 {
485     if (disabled)
486         addBehaviorRestriction(WirelessVideoPlaybackDisabled);
487     else
488         removeBehaviorRestriction(WirelessVideoPlaybackDisabled);
489
490     auto player = element.player();
491     if (!player)
492         return;
493
494     LOG(Media, "MediaElementSession::setWirelessVideoPlaybackDisabled - disabled %s", disabled ? "TRUE" : "FALSE");
495     player->setWirelessVideoPlaybackDisabled(disabled);
496 }
497
498 void MediaElementSession::setHasPlaybackTargetAvailabilityListeners(const HTMLMediaElement& element, bool hasListeners)
499 {
500     LOG(Media, "MediaElementSession::setHasPlaybackTargetAvailabilityListeners - hasListeners %s", hasListeners ? "TRUE" : "FALSE");
501
502 #if PLATFORM(IOS)
503     UNUSED_PARAM(element);
504     m_hasPlaybackTargetAvailabilityListeners = hasListeners;
505     PlatformMediaSessionManager::sharedManager().configureWireLessTargetMonitoring();
506 #else
507     UNUSED_PARAM(hasListeners);
508     element.document().playbackTargetPickerClientStateDidChange(*this, element.mediaState());
509 #endif
510 }
511
512 void MediaElementSession::setPlaybackTarget(Ref<MediaPlaybackTarget>&& device)
513 {
514     m_playbackTarget = WTFMove(device);
515     client().setWirelessPlaybackTarget(*m_playbackTarget.copyRef());
516 }
517
518 void MediaElementSession::targetAvailabilityChangedTimerFired()
519 {
520     client().wirelessRoutesAvailableDidChange();
521 }
522
523 void MediaElementSession::externalOutputDeviceAvailableDidChange(bool hasTargets)
524 {
525     if (m_hasPlaybackTargets == hasTargets)
526         return;
527
528     LOG(Media, "MediaElementSession::externalOutputDeviceAvailableDidChange(%p) - hasTargets %s", this, hasTargets ? "TRUE" : "FALSE");
529
530     m_hasPlaybackTargets = hasTargets;
531     m_targetAvailabilityChangedTimer.startOneShot(0_s);
532 }
533
534 bool MediaElementSession::canPlayToWirelessPlaybackTarget() const
535 {
536 #if !PLATFORM(IOS)
537     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
538         return false;
539 #endif
540
541     return client().canPlayToWirelessPlaybackTarget();
542 }
543
544 bool MediaElementSession::isPlayingToWirelessPlaybackTarget() const
545 {
546 #if !PLATFORM(IOS)
547     if (!m_playbackTarget || !m_playbackTarget->hasActiveRoute())
548         return false;
549 #endif
550
551     return client().isPlayingToWirelessPlaybackTarget();
552 }
553
554 void MediaElementSession::setShouldPlayToPlaybackTarget(bool shouldPlay)
555 {
556     LOG(Media, "MediaElementSession::setShouldPlayToPlaybackTarget - shouldPlay %s", shouldPlay ? "TRUE" : "FALSE");
557     m_shouldPlayToPlaybackTarget = shouldPlay;
558     client().setShouldPlayToPlaybackTarget(shouldPlay);
559 }
560
561 void MediaElementSession::mediaStateDidChange(const HTMLMediaElement& element, MediaProducer::MediaStateFlags state)
562 {
563     element.document().playbackTargetPickerClientStateDidChange(*this, state);
564 }
565 #endif
566
567 MediaPlayer::Preload MediaElementSession::effectivePreloadForElement(const HTMLMediaElement& element) const
568 {
569     MediaPlayer::Preload preload = element.preloadValue();
570
571     if (pageExplicitlyAllowsElementToAutoplayInline(element))
572         return preload;
573
574     if (m_restrictions & MetadataPreloadingNotPermitted)
575         return MediaPlayer::None;
576
577     if (m_restrictions & AutoPreloadingNotPermitted) {
578         if (preload > MediaPlayer::MetaData)
579             return MediaPlayer::MetaData;
580     }
581
582     return preload;
583 }
584
585 bool MediaElementSession::requiresFullscreenForVideoPlayback(const HTMLMediaElement& element) const
586 {
587     if (pageExplicitlyAllowsElementToAutoplayInline(element))
588         return false;
589
590     if (is<HTMLAudioElement>(element))
591         return false;
592
593     if (element.document().isMediaDocument()) {
594         ASSERT(is<HTMLVideoElement>(element));
595         const HTMLVideoElement& videoElement = *downcast<const HTMLVideoElement>(&element);
596         if (element.readyState() < HTMLVideoElement::HAVE_METADATA || !videoElement.hasEverHadVideo())
597             return false;
598     }
599
600     if (element.isTemporarilyAllowingInlinePlaybackAfterFullscreen())
601         return false;
602
603     if (!element.document().settings().allowsInlineMediaPlayback())
604         return true;
605
606     if (!element.document().settings().inlineMediaPlaybackRequiresPlaysInlineAttribute())
607         return false;
608
609 #if PLATFORM(IOS)
610     if (IOSApplication::isIBooks())
611         return !element.hasAttributeWithoutSynchronization(HTMLNames::webkit_playsinlineAttr) && !element.hasAttributeWithoutSynchronization(HTMLNames::playsinlineAttr);
612     if (dyld_get_program_sdk_version() < DYLD_IOS_VERSION_10_0)
613         return !element.hasAttributeWithoutSynchronization(HTMLNames::webkit_playsinlineAttr);
614 #endif
615
616     if (element.document().isMediaDocument() && element.document().ownerElement())
617         return false;
618
619     return !element.hasAttributeWithoutSynchronization(HTMLNames::playsinlineAttr);
620 }
621
622 bool MediaElementSession::allowsAutomaticMediaDataLoading(const HTMLMediaElement& element) const
623 {
624     if (pageExplicitlyAllowsElementToAutoplayInline(element))
625         return true;
626
627     if (element.document().settings().mediaDataLoadsAutomatically())
628         return true;
629
630     return false;
631 }
632
633 void MediaElementSession::mediaEngineUpdated(const HTMLMediaElement& element)
634 {
635     LOG(Media, "MediaElementSession::mediaEngineUpdated");
636
637 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
638     if (m_restrictions & WirelessVideoPlaybackDisabled)
639         setWirelessVideoPlaybackDisabled(element, true);
640     if (m_playbackTarget)
641         client().setWirelessPlaybackTarget(*m_playbackTarget.copyRef());
642     if (m_shouldPlayToPlaybackTarget)
643         client().setShouldPlayToPlaybackTarget(true);
644 #else
645     UNUSED_PARAM(element);
646 #endif
647     
648 }
649
650 void MediaElementSession::resetPlaybackSessionState()
651 {
652     m_mostRecentUserInteractionTime = 0;
653     addBehaviorRestriction(RequireUserGestureToControlControlsManager | RequirePlaybackToControlControlsManager);
654 }
655
656 bool MediaElementSession::allowsPictureInPicture(const HTMLMediaElement& element) const
657 {
658     return element.document().settings().allowsPictureInPictureMediaPlayback() && !element.webkitCurrentPlaybackTargetIsWireless();
659 }
660
661 #if PLATFORM(IOS)
662 bool MediaElementSession::requiresPlaybackTargetRouteMonitoring() const
663 {
664     return m_hasPlaybackTargetAvailabilityListeners && !client().elementIsHidden();
665 }
666 #endif
667
668 #if ENABLE(MEDIA_SOURCE)
669 size_t MediaElementSession::maximumMediaSourceBufferSize(const SourceBuffer& buffer) const
670 {
671     // A good quality 1080p video uses 8,000 kbps and stereo audio uses 384 kbps, so assume 95% for video and 5% for audio.
672     const float bufferBudgetPercentageForVideo = .95;
673     const float bufferBudgetPercentageForAudio = .05;
674
675     size_t maximum = buffer.document().settings().maximumSourceBufferSize();
676
677     // Allow a SourceBuffer to buffer as though it is audio-only even if it doesn't have any active tracks (yet).
678     size_t bufferSize = static_cast<size_t>(maximum * bufferBudgetPercentageForAudio);
679     if (buffer.hasVideo())
680         bufferSize += static_cast<size_t>(maximum * bufferBudgetPercentageForVideo);
681
682     // FIXME: we might want to modify this algorithm to:
683     // - decrease the maximum size for background tabs
684     // - decrease the maximum size allowed for inactive elements when a process has more than one
685     //   element, eg. so a page with many elements which are played one at a time doesn't keep
686     //   everything buffered after an element has finished playing.
687
688     return bufferSize;
689 }
690 #endif
691
692 static bool isMainContentForPurposesOfAutoplay(const HTMLMediaElement& element)
693 {
694     if (!element.hasAudio() || !element.hasVideo())
695         return false;
696
697     // Elements which have not yet been laid out, or which are not yet in the DOM, cannot be main content.
698     auto* renderer = element.renderer();
699     if (!renderer)
700         return false;
701
702     if (!isElementLargeEnoughForMainContent(element, MediaSessionMainContentPurpose::Autoplay))
703         return false;
704
705     // Elements which are hidden by style, or have been scrolled out of view, cannot be main content.
706     // But elements which have audio & video and are already playing should not stop playing because
707     // they are scrolled off the page.
708     if (renderer->style().visibility() != VISIBLE)
709         return false;
710     if (renderer->visibleInViewportState() != VisibleInViewportState::Yes && !element.isPlaying())
711         return false;
712
713     // Main content elements must be in the main frame.
714     Document& document = element.document();
715     if (!document.frame() || !document.frame()->isMainFrame())
716         return false;
717
718     MainFrame& mainFrame = document.frame()->mainFrame();
719     if (!mainFrame.view() || !mainFrame.view()->renderView())
720         return false;
721
722     RenderView& mainRenderView = *mainFrame.view()->renderView();
723
724     // Hit test the area of the main frame where the element appears, to determine if the element is being obscured.
725     IntRect rectRelativeToView = element.clientRect();
726     ScrollPosition scrollPosition = mainFrame.view()->documentScrollPositionRelativeToViewOrigin();
727     IntRect rectRelativeToTopDocument(rectRelativeToView.location() + scrollPosition, rectRelativeToView.size());
728     HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AllowChildFrameContent | HitTestRequest::IgnoreClipping | HitTestRequest::DisallowUserAgentShadowContent);
729     HitTestResult result(rectRelativeToTopDocument.center());
730
731     // Elements which are obscured by other elements cannot be main content.
732     mainRenderView.hitTest(request, result);
733     result.setToNonUserAgentShadowAncestor();
734     RefPtr<Element> hitElement = result.targetElement();
735     if (hitElement != &element)
736         return false;
737
738     return true;
739 }
740
741 static bool isElementRectMostlyInMainFrame(const HTMLMediaElement& element)
742 {
743     if (!element.renderer())
744         return false;
745
746     auto* documentFrame = element.document().frame();
747     if (!documentFrame)
748         return false;
749
750     auto mainFrameView = documentFrame->mainFrame().view();
751     if (!mainFrameView)
752         return false;
753
754     IntRect mainFrameRectAdjustedForScrollPosition = IntRect(-mainFrameView->documentScrollPositionRelativeToViewOrigin(), mainFrameView->contentsSize());
755     IntRect elementRectInMainFrame = element.clientRect();
756     auto totalElementArea = elementRectInMainFrame.area<RecordOverflow>();
757     if (totalElementArea.hasOverflowed())
758         return false;
759
760     elementRectInMainFrame.intersect(mainFrameRectAdjustedForScrollPosition);
761
762     return elementRectInMainFrame.area().unsafeGet() > totalElementArea.unsafeGet() / 2;
763 }
764
765 static bool isElementLargeRelativeToMainFrame(const HTMLMediaElement& element)
766 {
767     static const double minimumPercentageOfMainFrameAreaForMainContent = 0.9;
768     auto* renderer = element.renderer();
769     if (!renderer)
770         return false;
771
772     auto* documentFrame = element.document().frame();
773     if (!documentFrame)
774         return false;
775
776     if (!documentFrame->mainFrame().view())
777         return false;
778
779     auto& mainFrameView = *documentFrame->mainFrame().view();
780     auto maxVisibleClientWidth = std::min(renderer->clientWidth().toInt(), mainFrameView.visibleWidth());
781     auto maxVisibleClientHeight = std::min(renderer->clientHeight().toInt(), mainFrameView.visibleHeight());
782
783     return maxVisibleClientWidth * maxVisibleClientHeight > minimumPercentageOfMainFrameAreaForMainContent * mainFrameView.visibleWidth() * mainFrameView.visibleHeight();
784 }
785
786 static bool isElementLargeEnoughForMainContent(const HTMLMediaElement& element, MediaSessionMainContentPurpose purpose)
787 {
788     static const double elementMainContentAreaMinimum = 400 * 300;
789     static const double maximumAspectRatio = purpose == MediaSessionMainContentPurpose::MediaControls ? 3 : 1.8;
790     static const double minimumAspectRatio = .5; // Slightly smaller than 9:16.
791
792     // Elements which have not yet been laid out, or which are not yet in the DOM, cannot be main content.
793     auto* renderer = element.renderer();
794     if (!renderer)
795         return false;
796
797     double width = renderer->clientWidth();
798     double height = renderer->clientHeight();
799     double area = width * height;
800     double aspectRatio = width / height;
801
802     if (area < elementMainContentAreaMinimum)
803         return false;
804
805     if (aspectRatio >= minimumAspectRatio && aspectRatio <= maximumAspectRatio)
806         return true;
807
808     return isElementLargeRelativeToMainFrame(element);
809 }
810
811 void MediaElementSession::mainContentCheckTimerFired()
812 {
813     if (!hasBehaviorRestriction(OverrideUserGestureRequirementForMainContent))
814         return;
815
816     updateIsMainContent();
817 }
818
819 bool MediaElementSession::updateIsMainContent() const
820 {
821     bool wasMainContent = m_isMainContent;
822     m_isMainContent = isMainContentForPurposesOfAutoplay(m_element);
823
824     if (m_isMainContent != wasMainContent)
825         m_element.updateShouldPlay();
826
827     return m_isMainContent;
828 }
829
830 bool MediaElementSession::allowsNowPlayingControlsVisibility() const
831 {
832     auto page = m_element.document().page();
833     return page && !page->isVisibleAndActive();
834 }
835
836 bool MediaElementSession::allowsPlaybackControlsForAutoplayingAudio() const
837 {
838     auto page = m_element.document().page();
839     return page && page->allowsPlaybackControlsForAutoplayingAudio();
840 }
841
842 bool MediaElementSession::willLog(WTFLogLevel level) const
843 {
844     return m_element.willLog(level);
845 }
846
847 #if !RELEASE_LOG_DISABLED
848 const PAL::Logger& MediaElementSession::logger() const
849 {
850     return m_element.logger();
851 }
852
853 const void* MediaElementSession::logIdentifier() const
854 {
855     return m_element.logIdentifier();
856 }
857
858 WTFLogChannel& MediaElementSession::logChannel() const
859 {
860     return m_element.logChannel();
861 }
862 #endif
863
864 }
865
866 #endif // ENABLE(VIDEO)