Changeset 268893 in webkit


Ignore:
Timestamp:
Oct 22, 2020 2:56:24 PM (21 months ago)
Author:
Chris Dumez
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.

Location:
trunk/Source/WebCore
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r268889 r268893  
     12020-10-22  Chris Dumez  <cdumez@apple.com>
     2
     3        Web Audio continues to play when navigating off the web page via an iframe
     4        https://bugs.webkit.org/show_bug.cgi?id=218078
     5
     6        Reviewed by Eric Carlson.
     7
     8        The page was suspending playback when clicking the link and then resuming playback in the pagehide
     9        event handler. The issue is that the AudioContext's state gets updated asynchronously when the
     10        page suspends or resume rendering, as per specification. When entering the back/forward cache,
     11        AudioContext::suspend() would be called but would do nothing because the state was "suspended",
     12        despite the script having just called "resume()". To address the issue, AudioContext::suspend()
     13        now early returns only if the context is closed or based on the m_wasSuspendedByScript flag
     14        when gets updated synchronously when the script calls suspend() / resume(). Similarly,
     15        AudioContext::resume() checks those same flags now.
     16
     17        No new tests, this impacts audio rendering on speakers but is not web-observable.
     18
     19        * Modules/webaudio/AudioContext.cpp:
     20        (WebCore::AudioContext::resumeRendering):
     21        Check if the MediaSession is interrupted before asynchronously setting the AudioContext's state
     22        to "running". This is important because the state gets updated asynchronously but the MediaSession
     23        can be interrupted synchronously when AudioContext::suspend() gets called, which would have set
     24        the AudioContext's state to "interrupted". We don't want to incorrectly change the state from
     25        "interrupted" to "running" in this case. Note that AudioContext::suspendRendering() already does
     26        the same thing.
     27
     28        (WebCore::AudioContext::suspend):
     29        (WebCore::AudioContext::resume):
     30        Only early return if the AudioContext is closed or if the m_wasSuspendedByScript flag is set. The
     31        m_wasSuspendedByScript gets updated synchronously when suspendRendering() / resumeRendering() get
     32        called, unlike the AudioContext's state which gets updated asynchronously.
     33
     34        (WebCore::AudioContext::suspendPlayback):
     35        Stop early returning if the state is "suspended". This was wrong since the state gets updated
     36        asynchronously.
     37
    1382020-10-22  Martin Robinson  <mrobinson@igalia.com>
    239
  • trunk/Source/WebCore/Modules/webaudio/AudioContext.cpp

    r268812 r268893  
    249249
    250250    destinationNode()->resume([this, protectedThis = makeRef(*this)] {
    251         setState(State::Running);
     251        // Since we update the state asynchronously, we may have been interrupted after the
     252        // call to resume() and before this lambda runs. In this case, we don't want to
     253        // reset the state to running.
     254        bool interrupted = m_mediaSession->state() == PlatformMediaSession::Interrupted;
     255        setState(interrupted ? State::Interrupted : State::Running);
    252256    });
    253257}
     
    403407void AudioContext::suspend(ReasonForSuspension)
    404408{
    405     if (state() == State::Running) {
    406         m_mediaSession->beginInterruption(PlatformMediaSession::PlaybackSuspended);
    407         document()->updateIsPlayingMedia();
    408     }
     409    if (isClosed() || m_wasSuspendedByScript)
     410        return;
     411
     412    m_mediaSession->beginInterruption(PlatformMediaSession::PlaybackSuspended);
     413    document()->updateIsPlayingMedia();
    409414}
    410415
    411416void AudioContext::resume()
    412417{
    413     if (state() == State::Interrupted) {
    414         m_mediaSession->endInterruption(PlatformMediaSession::MayResumePlaying);
    415         document()->updateIsPlayingMedia();
    416     }
     418    if (isClosed() || m_wasSuspendedByScript)
     419        return;
     420
     421    m_mediaSession->endInterruption(PlatformMediaSession::MayResumePlaying);
     422    document()->updateIsPlayingMedia();
    417423}
    418424
     
    421427    if (!destinationNode() || state() == State::Closed)
    422428        return;
    423 
    424     if (state() == State::Suspended) {
    425         if (m_mediaSession->state() == PlatformMediaSession::Interrupted)
    426             setState(State::Interrupted);
    427         return;
    428     }
    429429
    430430    lazyInitialize();
Note: See TracChangeset for help on using the changeset viewer.