Title: [280298] trunk
Revision
280298
Author
[email protected]
Date
2021-07-26 10:51:09 -0700 (Mon, 26 Jul 2021)

Log Message

[iOS] All home screen web apps resume when any home screen web app is foregrounded
https://bugs.webkit.org/show_bug.cgi?id=228246
<rdar://72949281>

Reviewed by Eric Carlson.

Source/WebCore:

Test: media/video-page-visibility-restriction.html

On iOS, home screen web apps all run from the same UIProcess, SafariViewService. So when
one Web App is foregrounded, the SafariViewService itself is foregrounded, and all WKWebViews
(one for each Web App) are foregrounded as well, allowing all Web Apps to resume audio
playback. This is not ideal; ideally, all Web Apps will be allowed to continue to play
audio in the background. But until we can fix that bug, the current behavior of pausing
audio from Web App A when A is backgrounded, and resuming audio from A when Web App B is
foregrounded feels super broken.

Add a new WKPreference/WebPreference/Setting and matching MediaElementSession restriction
that will block playback of audible media elements when the media element's page is not
visible. When adopted by SafariViewService, this would keep multiple Web Apps (and indeed
SafariViewController pages) from starting playback when any other is foregrounded.

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::initializeMediaSession):
(WebCore::HTMLMediaElement::visibilityStateChanged):
* html/MediaElementSession.cpp:
(WebCore::MediaElementSession::visibilityChanged):
(WebCore::MediaElementSession::playbackStateChangePermitted const):
* html/MediaElementSession.h:
* platform/audio/PlatformMediaSession.h:
* testing/Internals.cpp:
(WebCore::Internals::setMediaElementRestrictions):

Source/WebKit:

Add a private WKPreference for setting the new WebPreference.

* UIProcess/API/Cocoa/WKPreferences.mm:
(-[WKPreferences _requiresPageVisibilityToPlayAudio]):
(-[WKPreferences _setRequiresPageVisibilityToPlayAudio:]):
* UIProcess/API/Cocoa/WKPreferencesPrivate.h:

Source/WTF:

* Scripts/Preferences/WebPreferences.yaml:

LayoutTests:

* media/video-page-visibility-restriction-expected.txt: Added.
* media/video-page-visibility-restriction.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (280297 => 280298)


--- trunk/LayoutTests/ChangeLog	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/LayoutTests/ChangeLog	2021-07-26 17:51:09 UTC (rev 280298)
@@ -1,3 +1,14 @@
+2021-07-26  Jer Noble  <[email protected]>
+
+        [iOS] All home screen web apps resume when any home screen web app is foregrounded
+        https://bugs.webkit.org/show_bug.cgi?id=228246
+        <rdar://72949281>
+
+        Reviewed by Eric Carlson.
+
+        * media/video-page-visibility-restriction-expected.txt: Added.
+        * media/video-page-visibility-restriction.html: Added.
+
 2021-07-26  Ziran Sun  <[email protected]>
 
         [css-grid] svg image as grid items should use the overriding logical width/height when defined to compute the logical height/width

Added: trunk/LayoutTests/media/video-page-visibility-restriction-expected.txt (0 => 280298)


--- trunk/LayoutTests/media/video-page-visibility-restriction-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/video-page-visibility-restriction-expected.txt	2021-07-26 17:51:09 UTC (rev 280298)
@@ -0,0 +1,14 @@
+
+RUN(internals.setMediaElementRestrictions(video, "RequirePageVisibilityToPlayAudio"))
+RUN(video.src = "" "content/test"))
+EVENT(canplaythrough)
+RUN(internals.setPageVisibility(false))
+RUN(promise = video.play())
+Promise rejected correctly OK
+RUN(internals.setPageVisibility(true))
+RUN(promise = video.play())
+Promise resolved OK
+RUN(internals.setPageVisibility(false))
+EVENT(pause)
+END OF TEST
+

Added: trunk/LayoutTests/media/video-page-visibility-restriction.html (0 => 280298)


--- trunk/LayoutTests/media/video-page-visibility-restriction.html	                        (rev 0)
+++ trunk/LayoutTests/media/video-page-visibility-restriction.html	2021-07-26 17:51:09 UTC (rev 280298)
@@ -0,0 +1,37 @@
+<html>
+    <head>
+        <script src=""
+        <script src=""
+        <script>
+        window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
+            findMediaElement();
+
+            run('internals.setMediaElementRestrictions(video, "RequirePageVisibilityToPlayAudio")');
+            run('video.src = "" "content/test")');
+            await waitFor(video, 'canplaythrough');
+
+            run('internals.setPageVisibility(false)');
+            run('promise = video.play()');
+            await shouldReject(promise);
+
+            run('internals.setPageVisibility(true)');
+            run('promise = video.play()');
+            await shouldResolve(promise);
+
+            run('internals.setPageVisibility(false)');
+            await waitFor(video, 'pause');
+
+            endTest();
+        });
+        </script>
+    </head>
+
+    <body>
+        <video controls loop></video>
+    </body>
+</html>

Modified: trunk/Source/WTF/ChangeLog (280297 => 280298)


--- trunk/Source/WTF/ChangeLog	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WTF/ChangeLog	2021-07-26 17:51:09 UTC (rev 280298)
@@ -1,3 +1,13 @@
+2021-07-26  Jer Noble  <[email protected]>
+
+        [iOS] All home screen web apps resume when any home screen web app is foregrounded
+        https://bugs.webkit.org/show_bug.cgi?id=228246
+        <rdar://72949281>
+
+        Reviewed by Eric Carlson.
+
+        * Scripts/Preferences/WebPreferences.yaml:
+
 2021-07-23  Chris Dumez  <[email protected]>
 
         SharedBuffer::takeData() is a bit dangerous

Modified: trunk/Source/WTF/Scripts/Preferences/WebPreferences.yaml (280297 => 280298)


--- trunk/Source/WTF/Scripts/Preferences/WebPreferences.yaml	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WTF/Scripts/Preferences/WebPreferences.yaml	2021-07-26 17:51:09 UTC (rev 280298)
@@ -1770,6 +1770,16 @@
     WebCore:
       default: true
 
+RequiresPageVisibilityToPlayAudio:
+  type: bool
+  defaultValue:
+    WebKitLegacy:
+      default: false
+    WebKit:
+      default: false
+    WebCore:
+      default: false
+
 RequiresUserGestureForAudioPlayback:
   type: bool
   webKitLegacyPreferenceKey: WebKitAudioPlaybackRequiresUserGesture

Modified: trunk/Source/WebCore/ChangeLog (280297 => 280298)


--- trunk/Source/WebCore/ChangeLog	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WebCore/ChangeLog	2021-07-26 17:51:09 UTC (rev 280298)
@@ -1,3 +1,37 @@
+2021-07-26  Jer Noble  <[email protected]>
+
+        [iOS] All home screen web apps resume when any home screen web app is foregrounded
+        https://bugs.webkit.org/show_bug.cgi?id=228246
+        <rdar://72949281>
+
+        Reviewed by Eric Carlson.
+
+        Test: media/video-page-visibility-restriction.html
+
+        On iOS, home screen web apps all run from the same UIProcess, SafariViewService. So when
+        one Web App is foregrounded, the SafariViewService itself is foregrounded, and all WKWebViews
+        (one for each Web App) are foregrounded as well, allowing all Web Apps to resume audio
+        playback. This is not ideal; ideally, all Web Apps will be allowed to continue to play
+        audio in the background. But until we can fix that bug, the current behavior of pausing
+        audio from Web App A when A is backgrounded, and resuming audio from A when Web App B is
+        foregrounded feels super broken.
+
+        Add a new WKPreference/WebPreference/Setting and matching MediaElementSession restriction
+        that will block playback of audible media elements when the media element's page is not
+        visible. When adopted by SafariViewService, this would keep multiple Web Apps (and indeed
+        SafariViewController pages) from starting playback when any other is foregrounded.
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::initializeMediaSession):
+        (WebCore::HTMLMediaElement::visibilityStateChanged):
+        * html/MediaElementSession.cpp:
+        (WebCore::MediaElementSession::visibilityChanged):
+        (WebCore::MediaElementSession::playbackStateChangePermitted const):
+        * html/MediaElementSession.h:
+        * platform/audio/PlatformMediaSession.h:
+        * testing/Internals.cpp:
+        (WebCore::Internals::setMediaElementRestrictions):
+
 2021-07-26  Philippe Normand  <[email protected]>
 
         [Pipewire] Muting the display capture closes the Portal session

Modified: trunk/Source/WebCore/html/HTMLMediaElement.cpp (280297 => 280298)


--- trunk/Source/WebCore/html/HTMLMediaElement.cpp	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WebCore/html/HTMLMediaElement.cpp	2021-07-26 17:51:09 UTC (rev 280298)
@@ -474,6 +474,9 @@
     if (document.settings().invisibleAutoplayNotPermitted())
         m_mediaSession->addBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted);
 
+    if (document.settings().requiresPageVisibilityToPlayAudio())
+        m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageVisibilityToPlayAudio);
+
     if (document.ownerElement() || !document.isMediaDocument()) {
         if (m_shouldVideoPlaybackRequireUserGesture) {
             m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoRateChange);
@@ -5774,17 +5777,6 @@
     mediaSession().visibilityChanged();
     if (m_player)
         m_player->setVisible(!m_elementIsHidden);
-
-    bool isPlayingAudio = isPlaying() && hasAudio() && !muted() && volume();
-    if (!isPlayingAudio) {
-        if (m_elementIsHidden) {
-            ALWAYS_LOG(LOGIDENTIFIER, "Suspending playback after going to the background");
-            mediaSession().beginInterruption(PlatformMediaSession::EnteringBackground);
-        } else {
-            ALWAYS_LOG(LOGIDENTIFIER, "Resuming playback after entering foreground");
-            mediaSession().endInterruption(PlatformMediaSession::MayResumePlaying);
-        }
-    }
 }
 
 bool HTMLMediaElement::requiresTextTrackRepresentation() const

Modified: trunk/Source/WebCore/html/MediaElementSession.cpp (280297 => 280298)


--- trunk/Source/WebCore/html/MediaElementSession.cpp	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WebCore/html/MediaElementSession.cpp	2021-07-26 17:51:09 UTC (rev 280298)
@@ -249,10 +249,34 @@
 {
     scheduleClientDataBufferingCheck();
 
-    if (m_element.elementIsHidden() && !m_element.isFullscreen())
+    bool elementIsHidden = m_element.elementIsHidden();
+
+    if (elementIsHidden && !m_element.isFullscreen())
         m_elementIsHiddenUntilVisibleInViewport = true;
     else if (m_element.isVisibleInViewport())
         m_elementIsHiddenUntilVisibleInViewport = false;
+
+    bool isPlayingAudio = m_element.isPlaying() && m_element.hasAudio() && !m_element.muted() && m_element.volume();
+    if (!isPlayingAudio) {
+        if (elementIsHidden) {
+            ALWAYS_LOG(LOGIDENTIFIER, "Suspending silent playback after page visibility: hidden");
+            beginInterruption(PlatformMediaSession::EnteringBackground);
+        } else {
+            ALWAYS_LOG(LOGIDENTIFIER, "Resuming silent playback after page visibility: showing");
+            endInterruption(PlatformMediaSession::MayResumePlaying);
+        }
+        return;
+    }
+
+    if (hasBehaviorRestriction(RequirePageVisibilityToPlayAudio)) {
+        if (elementIsHidden) {
+            ALWAYS_LOG(LOGIDENTIFIER, "Suspending audible playback after page visibility: hidden");
+            beginInterruption(PlatformMediaSession::EnteringBackground);
+        } else {
+            ALWAYS_LOG(LOGIDENTIFIER, "Resuming audible playback after page visibility: showing");
+            endInterruption(PlatformMediaSession::MayResumePlaying);
+        }
+    }
 }
 
 void MediaElementSession::isVisibleInViewportChanged()
@@ -391,6 +415,11 @@
         return makeUnexpected(MediaPlaybackDenialReason::UserGestureRequired);
     }
 
+    if (m_restrictions & RequirePageVisibilityToPlayAudio && (!m_element.isVideo() || m_element.hasAudio()) && !m_element.muted() && m_element.volume() && m_element.elementIsHidden()) {
+        ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because page visibility required for audio rate change restriction");
+        return makeUnexpected(MediaPlaybackDenialReason::UserGestureRequired);
+    }
+
     if (m_restrictions & RequireUserGestureForVideoDueToLowPowerMode && m_element.isVideo() && !document.processingUserGestureForMedia()) {
         ALWAYS_LOG(LOGIDENTIFIER, "Returning FALSE because of video low power mode restriction");
         return makeUnexpected(MediaPlaybackDenialReason::UserGestureRequired);

Modified: trunk/Source/WebCore/html/MediaElementSession.h (280297 => 280298)


--- trunk/Source/WebCore/html/MediaElementSession.h	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WebCore/html/MediaElementSession.h	2021-07-26 17:51:09 UTC (rev 280298)
@@ -129,6 +129,7 @@
         RequireUserGestureToControlControlsManager = 1 << 13,
         RequirePlaybackToControlControlsManager = 1 << 14,
         RequireUserGestureForVideoDueToLowPowerMode = 1 << 15,
+        RequirePageVisibilityToPlayAudio = 1 << 16,
         AllRestrictions = ~NoRestrictions,
     };
     typedef unsigned BehaviorRestrictions;

Modified: trunk/Source/WebCore/platform/audio/PlatformMediaSession.h (280297 => 280298)


--- trunk/Source/WebCore/platform/audio/PlatformMediaSession.h	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WebCore/platform/audio/PlatformMediaSession.h	2021-07-26 17:51:09 UTC (rev 280298)
@@ -93,6 +93,7 @@
         InvisibleAutoplay,
         ProcessInactive,
         PlaybackSuspended,
+        PageNotVisible,
     };
     InterruptionType interruptionType() const { return m_interruptionType; }
 
@@ -367,7 +368,8 @@
     WebCore::PlatformMediaSession::InterruptionType::SuspendedUnderLock,
     WebCore::PlatformMediaSession::InterruptionType::InvisibleAutoplay,
     WebCore::PlatformMediaSession::InterruptionType::ProcessInactive,
-    WebCore::PlatformMediaSession::InterruptionType::PlaybackSuspended
+    WebCore::PlatformMediaSession::InterruptionType::PlaybackSuspended,
+    WebCore::PlatformMediaSession::InterruptionType::PageNotVisible
     >;
 };
 

Modified: trunk/Source/WebCore/testing/Internals.cpp (280297 => 280298)


--- trunk/Source/WebCore/testing/Internals.cpp	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WebCore/testing/Internals.cpp	2021-07-26 17:51:09 UTC (rev 280298)
@@ -4366,6 +4366,8 @@
             restrictions |= MediaElementSession::RequirePlaybackToControlControlsManager;
         if (equalLettersIgnoringASCIICase(restrictionString, "requireusergestureforvideoduetolowpowermode"))
             restrictions |= MediaElementSession::RequireUserGestureForVideoDueToLowPowerMode;
+        if (equalLettersIgnoringASCIICase(restrictionString, "requirepagevisibilitytoplayaudio"))
+            restrictions |= MediaElementSession::RequirePageVisibilityToPlayAudio;
     }
     element.mediaSession().addBehaviorRestriction(restrictions);
 }

Modified: trunk/Source/WebKit/ChangeLog (280297 => 280298)


--- trunk/Source/WebKit/ChangeLog	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WebKit/ChangeLog	2021-07-26 17:51:09 UTC (rev 280298)
@@ -1,3 +1,18 @@
+2021-07-26  Jer Noble  <[email protected]>
+
+        [iOS] All home screen web apps resume when any home screen web app is foregrounded
+        https://bugs.webkit.org/show_bug.cgi?id=228246
+        <rdar://72949281>
+
+        Reviewed by Eric Carlson.
+
+        Add a private WKPreference for setting the new WebPreference.
+
+        * UIProcess/API/Cocoa/WKPreferences.mm:
+        (-[WKPreferences _requiresPageVisibilityToPlayAudio]):
+        (-[WKPreferences _setRequiresPageVisibilityToPlayAudio:]):
+        * UIProcess/API/Cocoa/WKPreferencesPrivate.h:
+
 2021-07-26  Philippe Normand  <[email protected]>
 
         [GTK] MiniBrowser crashes when closed while capturing desktop

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm (280297 => 280298)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm	2021-07-26 17:51:09 UTC (rev 280298)
@@ -1507,6 +1507,16 @@
 #endif
 }
 
+- (BOOL)_requiresPageVisibilityToPlayAudio
+{
+    return _preferences->requiresPageVisibilityToPlayAudio();
+}
+
+- (void)_setRequiresPageVisibilityToPlayAudio:(BOOL)requires
+{
+    _preferences->setRequiresPageVisibilityToPlayAudio(requires);
+}
+
 @end
 
 

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h (280297 => 280298)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h	2021-07-26 17:24:09 UTC (rev 280297)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h	2021-07-26 17:51:09 UTC (rev 280298)
@@ -172,8 +172,8 @@
 @property (nonatomic, setter=_setPitchCorrectionAlgorithm:) _WKPitchCorrectionAlgorithm _pitchCorrectionAlgorithm WK_API_AVAILABLE(macos(12.0), ios(15.0));
 @property (nonatomic, setter=_setMediaSessionEnabled:) BOOL _mediaSessionEnabled WK_API_AVAILABLE(macos(12.0), ios(15.0));
 @property (nonatomic, getter=_isExtensibleSSOEnabled, setter=_setExtensibleSSOEnabled:) BOOL _extensibleSSOEnabled WK_API_AVAILABLE(macos(12.0), ios(15.0));
+@property (nonatomic, setter=_setRequiresPageVisibilityToPlayAudio:) BOOL _requiresPageVisibilityToPlayAudio WK_API_AVAILABLE(macos(12.0), ios(15.0));
 
-
 #if !TARGET_OS_IPHONE
 @property (nonatomic, setter=_setWebGLEnabled:) BOOL _webGLEnabled WK_API_AVAILABLE(macos(10.13.4));
 @property (nonatomic, setter=_setJavaEnabledForLocalFiles:) BOOL _javaEnabledForLocalFiles WK_API_AVAILABLE(macos(10.13.4));
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to