Title: [268893] trunk/Source/WebCore
Revision
268893
Author
[email protected]
Date
2020-10-22 14:56:24 -0700 (Thu, 22 Oct 2020)

Log Message

Web Audio continues to play when navigating off the web page via an iframe
https://bugs.webkit.org/show_bug.cgi?id=218078

Reviewed by Eric Carlson.

The page was suspending playback when clicking the link and then resuming playback in the pagehide
event handler. The issue is that the AudioContext's state gets updated asynchronously when the
page suspends or resume rendering, as per specification. When entering the back/forward cache,
AudioContext::suspend() would be called but would do nothing because the state was "suspended",
despite the script having just called "resume()". To address the issue, AudioContext::suspend()
now early returns only if the context is closed or based on the m_wasSuspendedByScript flag
when gets updated synchronously when the script calls suspend() / resume(). Similarly,
AudioContext::resume() checks those same flags now.

No new tests, this impacts audio rendering on speakers but is not web-observable.

* Modules/webaudio/AudioContext.cpp:
(WebCore::AudioContext::resumeRendering):
Check if the MediaSession is interrupted before asynchronously setting the AudioContext's state
to "running". This is important because the state gets updated asynchronously but the MediaSession
can be interrupted synchronously when AudioContext::suspend() gets called, which would have set
the AudioContext's state to "interrupted". We don't want to incorrectly change the state from
"interrupted" to "running" in this case. Note that AudioContext::suspendRendering() already does
the same thing.

(WebCore::AudioContext::suspend):
(WebCore::AudioContext::resume):
Only early return if the AudioContext is closed or if the m_wasSuspendedByScript flag is set. The
m_wasSuspendedByScript gets updated synchronously when suspendRendering() / resumeRendering() get
called, unlike the AudioContext's state which gets updated asynchronously.

(WebCore::AudioContext::suspendPlayback):
Stop early returning if the state is "suspended". This was wrong since the state gets updated
asynchronously.

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (268892 => 268893)


--- trunk/Source/WebCore/ChangeLog	2020-10-22 21:34:31 UTC (rev 268892)
+++ trunk/Source/WebCore/ChangeLog	2020-10-22 21:56:24 UTC (rev 268893)
@@ -1,3 +1,40 @@
+2020-10-22  Chris Dumez  <[email protected]>
+
+        Web Audio continues to play when navigating off the web page via an iframe
+        https://bugs.webkit.org/show_bug.cgi?id=218078
+
+        Reviewed by Eric Carlson.
+
+        The page was suspending playback when clicking the link and then resuming playback in the pagehide
+        event handler. The issue is that the AudioContext's state gets updated asynchronously when the
+        page suspends or resume rendering, as per specification. When entering the back/forward cache,
+        AudioContext::suspend() would be called but would do nothing because the state was "suspended",
+        despite the script having just called "resume()". To address the issue, AudioContext::suspend()
+        now early returns only if the context is closed or based on the m_wasSuspendedByScript flag
+        when gets updated synchronously when the script calls suspend() / resume(). Similarly,
+        AudioContext::resume() checks those same flags now.
+
+        No new tests, this impacts audio rendering on speakers but is not web-observable.
+
+        * Modules/webaudio/AudioContext.cpp:
+        (WebCore::AudioContext::resumeRendering):
+        Check if the MediaSession is interrupted before asynchronously setting the AudioContext's state
+        to "running". This is important because the state gets updated asynchronously but the MediaSession
+        can be interrupted synchronously when AudioContext::suspend() gets called, which would have set
+        the AudioContext's state to "interrupted". We don't want to incorrectly change the state from
+        "interrupted" to "running" in this case. Note that AudioContext::suspendRendering() already does
+        the same thing.
+
+        (WebCore::AudioContext::suspend):
+        (WebCore::AudioContext::resume):
+        Only early return if the AudioContext is closed or if the m_wasSuspendedByScript flag is set. The
+        m_wasSuspendedByScript gets updated synchronously when suspendRendering() / resumeRendering() get
+        called, unlike the AudioContext's state which gets updated asynchronously.
+
+        (WebCore::AudioContext::suspendPlayback):
+        Stop early returning if the state is "suspended". This was wrong since the state gets updated
+        asynchronously.
+
 2020-10-22  Martin Robinson  <[email protected]>
 
         Rename scroll-snap-margin to scroll-margin

Modified: trunk/Source/WebCore/Modules/webaudio/AudioContext.cpp (268892 => 268893)


--- trunk/Source/WebCore/Modules/webaudio/AudioContext.cpp	2020-10-22 21:34:31 UTC (rev 268892)
+++ trunk/Source/WebCore/Modules/webaudio/AudioContext.cpp	2020-10-22 21:56:24 UTC (rev 268893)
@@ -248,7 +248,11 @@
     lazyInitialize();
 
     destinationNode()->resume([this, protectedThis = makeRef(*this)] {
-        setState(State::Running);
+        // Since we update the state asynchronously, we may have been interrupted after the
+        // call to resume() and before this lambda runs. In this case, we don't want to
+        // reset the state to running.
+        bool interrupted = m_mediaSession->state() == PlatformMediaSession::Interrupted;
+        setState(interrupted ? State::Interrupted : State::Running);
     });
 }
 
@@ -402,18 +406,20 @@
 
 void AudioContext::suspend(ReasonForSuspension)
 {
-    if (state() == State::Running) {
-        m_mediaSession->beginInterruption(PlatformMediaSession::PlaybackSuspended);
-        document()->updateIsPlayingMedia();
-    }
+    if (isClosed() || m_wasSuspendedByScript)
+        return;
+
+    m_mediaSession->beginInterruption(PlatformMediaSession::PlaybackSuspended);
+    document()->updateIsPlayingMedia();
 }
 
 void AudioContext::resume()
 {
-    if (state() == State::Interrupted) {
-        m_mediaSession->endInterruption(PlatformMediaSession::MayResumePlaying);
-        document()->updateIsPlayingMedia();
-    }
+    if (isClosed() || m_wasSuspendedByScript)
+        return;
+
+    m_mediaSession->endInterruption(PlatformMediaSession::MayResumePlaying);
+    document()->updateIsPlayingMedia();
 }
 
 void AudioContext::suspendPlayback()
@@ -421,12 +427,6 @@
     if (!destinationNode() || state() == State::Closed)
         return;
 
-    if (state() == State::Suspended) {
-        if (m_mediaSession->state() == PlatformMediaSession::Interrupted)
-            setState(State::Interrupted);
-        return;
-    }
-
     lazyInitialize();
 
     destinationNode()->suspend([this, protectedThis = makeRef(*this)] {
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to