Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: b989a866564b70d8e8507f2d773c8f8df6d342f4
      
https://github.com/WebKit/WebKit/commit/b989a866564b70d8e8507f2d773c8f8df6d342f4
  Author: Jean-Yves Avenard <[email protected]>
  Date:   2026-08-19 (Wed, 19 Aug 2026)

  Changed paths:
    M LayoutTests/media/video-playback-quality-webm-expected.txt
    M LayoutTests/media/video-playback-quality-webm.html
    M LayoutTests/platform/ios/TestExpectations
    M LayoutTests/platform/mac/TestExpectations
    M Source/WebCore/Modules/mediasession/MediaSession.cpp
    M Source/WebCore/Modules/webaudio/AudioContext.cpp
    M Source/WebCore/Modules/webaudio/AudioContext.h
    M Source/WebCore/html/HTMLMediaElement.cpp
    M Source/WebCore/html/MediaElementSession.cpp
    M Source/WebCore/html/MediaElementSession.h
    M Source/WebCore/platform/audio/MediaSessionManagerInterface.cpp
    M Source/WebCore/platform/audio/MediaSessionManagerInterface.h
    M
Source/WebCore/platform/audio/PlatformMediaSession.cpp
    M Source/WebCore/platform/audio/PlatformMediaSession.h
    M Source/WebCore/platform/audio/PlatformMediaSessionInterface.h
    M Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.h
    M Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm
    M Source/WebCore/platform/audio/glib/MediaSessionManagerGLib.cpp
    M Source/WebCore/platform/audio/glib/MediaSessionManagerGLib.h
    M Source/WebCore/platform/audio/ios/MediaSessionManagerIOS.h
    M Source/WebCore/platform/audio/ios/MediaSessionManagerIOS.mm
    M Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.cpp
    M Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.cpp
    M Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.h
    M Source/WebKit/WebProcess/Media/RemoteMediaSessionManager.cpp
    M Source/WebKit/WebProcess/Media/RemoteMediaSessionManager.h

  Log Message:
  -----------
 
PlatformMediaSession::clientWillBeginPlayback() should return a GenericPromise
https://bugs.webkit.org/show_bug.cgi?id=321871
rdar://185043324

Reviewed by Eric Carlson.

PlatformMediaSession::clientWillBeginPlayback() took a completion handler that
MediaSessionManagerInterface::sessionWillBeginPlayback() could call immediately,
inline, when admission needed no further work (the audio session was already
active), or only later, once AudioSession activation settled asynchronously.
Nothing guaranteed that two admissions requested close together completed in the
order they were requested: a video needing to activate the audio session stayed
pending while a second video, needing no activation, was admitted right away and
could evict the first video before the first video's own admission had even
finished. That inversion produced the flaky failure in bug 321819.

clientWillBeginPlayback() and the whole call chain down to
sessionWillBeginPlayback() now return a
GenericPromise instead of taking a
completion handler, and each manager chains every new admission onto the promise
of whichever admission is currently outstanding: a request's own work does not
start until the previous one has fully settled, so completion always follows
request order and the inversion above cannot happen.
enforceConcurrentPlaybackRestriction()'s eviction predicate no longer treats a
session whose own admission is preparingToPlay() as already playing: that let a
session which was still only queued, not yet given its turn, get evicted before
it ever started, which serialization makes both unnecessary and wrong.

Serializing admission this way also settles each one at least a run-loop turn
later than the completion-handler path did when no AudioSession activation was
needed. That wider window exposed two existing races: updateShouldPlay() and
cancelPendingEventsAndCallbacks() each rejected pending play promises
unconditionally, never checking whether an
in-flight admission already promised
to settle them, producing an unhandled rejection in
media/audio-playback-restriction-play-muted.html and
imported/w3c/web-platform-tests/css/selectors/media/media-playback-state.html.
A session paused while its own admission sat queued behind another one's could
also still have setCurrentSession() called on its behalf, since nothing
revalidated its intent to play first.

Both rejection sites now defer to the same m_playPromiseSettlementGuaranteed
guard pauseInternal() already uses. PlatformMediaSessionInterface gains
commitPlaybackAdmission(), called by the manager right before
enforceConcurrentPlaybackRestriction(), and sessionWillBeginPlayback() gains an
earlier admissionStillValid() check (true by construction for
RemoteMediaSessionProxy, whose admission was already decided by the WebContent
process) so a paused, queued session never reaches setCurrentSession(). The
per-subclass sessionWillBeginPlayback() overrides are replaced
by
sessionDidCompleteAdmission(), called only once commitPlaybackAdmission()
succeeds, keeping post-admission side effects inside the serialized region and
limited to a session that actually claimed Playing. updatePlayState() also
stopped requesting a second, redundant admission when one was already in flight
for the same session. commitPlaybackAdmission() takes the state captured at
admission start as a parameter rather than reading it from a member: a second,
overlapping admission for the same session would otherwise overwrite that member
before the first admission's commit reads it.

AudioContext hit a variant of the same ordering problem: suspendRendering()
calls clientWillPausePlayback(), which stays synchronous, while
resumeRendering() calls the now-always-asynchronous clientWillBeginPlayback().
Calling resume() then immediately suspend() could let suspend()'s promise settle
first, in violation of the
ordering
imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audiocontext-interface/suspend-after-construct.html
checks, which surfaced there as an unhandled rejection. resumeRendering() and
suspendRendering() now chain onto a new m_currentRenderingOperation the same way
sessionWillBeginPlayback() chains onto m_currentPlaybackAdmission, so a later
call's promise cannot settle before an earlier one already has; both now assert
they run on the main thread, since that chain is main-thread-only and nothing
enforced that before.

* LayoutTests/media/video-playback-quality-webm-expected.txt:
* LayoutTests/media/video-playback-quality-webm.html: Test was waiting on the 
playing event to check if playback quality has changed; this doesn't guarantee 
playback progressed.
* LayoutTests/platform/ios/TestExpectations: This commit resolved 
imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audiocontext-interface/suspend-after-construct.html
*
LayoutTests/platform/mac/TestExpectations: This commit resolved 
imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audiocontext-interface/suspend-after-construct.html
* Source/WebCore/Modules/mediasession/MediaSession.cpp:
(WebCore::MediaSession::setPlaybackState): Register the session synchronously
via setActive(true), the same way HTMLMediaElement::playInternal() already
does, so beginInterruption() can find it before its (now async) admission
settles.
* Source/WebCore/Modules/webaudio/AudioContext.cpp:
(WebCore::AudioContext::suspendRendering): Chain onto
m_currentRenderingOperation and assert the main thread.
(WebCore::AudioContext::resumeRendering): Ditto.
(WebCore::AudioContext::willBeginPlayback):
* Source/WebCore/Modules/webaudio/AudioContext.h:
* Source/WebCore/html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::cancelPendingEventsAndCallbacks): Ditto.
(WebCore::HTMLMediaElement::playInternal): Consume the promise directly; the
hand-built
GenericPromise::Producer bridge is no longer needed now that
clientWillBeginPlayback() returns one.
(WebCore::HTMLMediaElement::updatePlayState): Skip re-requesting admission if
one is already in flight for the session.
(WebCore::HTMLMediaElement::updateShouldPlay): Let pauseInternal()'s own
guarded rejection handle the pending play promise.
* Source/WebCore/html/MediaElementSession.cpp:
(WebCore::MediaElementSession::clientWillBeginPlayback):
* Source/WebCore/html/MediaElementSession.h:
* Source/WebCore/platform/audio/MediaSessionManagerInterface.cpp:
(WebCore::MediaSessionManagerInterface::sessionWillBeginPlayback): Assert the
main thread. Chain the request onto m_currentPlaybackAdmission and return a
GenericPromise. Resolve without starting the admission when
admissionStillValid() is false.
(WebCore::MediaSessionManagerInterface::sessionDidCompleteAdmission):
(WebCore::MediaSessionManagerInterface::startSessionAdmission): The former
body of
sessionWillBeginPlayback(), returning a GenericPromise and taking the
caller's captured start state; call commitPlaybackAdmission() and only
enforce the restriction and call sessionDidCompleteAdmission() when it
returns true.
(WebCore::MediaSessionManagerInterface::enforceConcurrentPlaybackRestriction):
Drop the preparingToPlay() case from the eviction predicate.
(WebCore::MediaSessionManagerInterface::sessionWillEndPlayback): Use
isPlayingOrPreparingToPlay().
* Source/WebCore/platform/audio/MediaSessionManagerInterface.h:
* Source/WebCore/platform/audio/PlatformMediaSession.cpp:
(WebCore::PlatformMediaSession::clientWillBeginPlayback): Capture the state
at admission start in a local, passed to commitPlaybackAdmission() as a
parameter rather than stored on the session.
(WebCore::PlatformMediaSession::commitPlaybackAdmission): Added.
* Source/WebCore/platform/audio/PlatformMediaSession.h:
*
Source/WebCore/platform/audio/PlatformMediaSessionInterface.h:
(WebCore::PlatformMediaSessionInterface::admissionStillValid const):
(WebCore::PlatformMediaSessionInterface::isPlayingOrPreparingToPlay const):
* Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.h:
* Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm:
(WebCore::MediaSessionManagerCocoa::sessionDidCompleteAdmission): Replaces the
sessionWillBeginPlayback() override.
(WebCore::MediaSessionManagerCocoa::sessionWillBeginPlayback): Deleted.
* Source/WebCore/platform/audio/glib/MediaSessionManagerGLib.cpp:
(WebCore::MediaSessionManagerGLib::sessionDidCompleteAdmission): Ditto.
(WebCore::MediaSessionManagerGLib::sessionWillBeginPlayback): Deleted.
* Source/WebCore/platform/audio/glib/MediaSessionManagerGLib.h:
* Source/WebCore/platform/audio/ios/MediaSessionManagerIOS.h:
*
Source/WebCore/platform/audio/ios/MediaSessionManagerIOS.mm:
(WebCore::MediaSessionManageriOS::sessionDidCompleteAdmission): Ditto, keeping
the AirPlay playback target assignment.
(WebCore::MediaSessionManageriOS::sessionWillBeginPlayback): Deleted.
* Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.cpp:
(WebKit::RemoteMediaSessionManagerProxy::mediaSessionWillBeginPlayback):
* Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.cpp:
(WebKit::RemoteMediaSessionProxy::commitPlaybackAdmission): Added.
* Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.h:
* Source/WebKit/WebProcess/Media/RemoteMediaSessionManager.cpp:
(WebKit::RemoteMediaSessionManager::sessionDidCompleteAdmission): Replaces the
sessionWillBeginPlayback() override; the session-state snapshot is now taken
after commitPlaybackAdmission() has run.
(WebKit::RemoteMediaSessionManager::sessionWillBeginPlayback): Deleted.
* Source/WebKit/WebProcess/Media/RemoteMediaSessionManager.h:

Canonical
link: 
https://flagged.apple.com:443/proxy?t2=DI0f4W6sn0&o=aHR0cHM6Ly9jb21taXRzLndlYmtpdC5vcmcvMzE5NDU1QG1haW4=&emid=5c6de5de-fa44-439a-b8bf-3599e0091618&c=11



To unsubscribe from these emails, change your notification settings at 
https://github.com/WebKit/WebKit/settings/notifications

Reply via email to