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

  Changed paths:
    M LayoutTests/media/ios/short-audio-now-playing-expected.txt
    M LayoutTests/media/ios/short-audio-now-playing.html
    M LayoutTests/media/video-is-playing-audio-after-load-expected.txt
    M LayoutTests/media/video-is-playing-audio-after-load.html
    M LayoutTests/media/video-main-content-allow-then-deny.html
    M LayoutTests/media/video-multiple-concurrent-playback-expected.txt
    M Source/WebCore/html/HTMLMediaElement.cpp
    M Source/WebCore/html/HTMLMediaElement.h
    M Source/WebCore/html/MediaElementSession.cpp
    M Source/WebCore/platform/audio/MediaSessionManagerInterface.cpp
    M Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.cpp

  Log Message:
  -----------
  [site-isolation] media/video-seek-after-end-play.html is a permanent failure
https://bugs.webkit.org/show_bug.cgi?id=321429
rdar://184503400

Reviewed by Eric Carlson.

REGRESSION(317782@main): HTMLMediaElement's internal play steps must run 
synchronously

The test seeks an ended element back to the start and calls play() while that 
seek is still in flight.
It expected play, playing, seeking, seeked, ended; with site isolation enabled 
it got play, seeking,
seeked, playing, ended.

317782@main moved the internal play steps' notify about playing and resolve 
pending play promises out
of playInternal() and into the completion handler of clientWillBeginPlayback(). 
Per spec those steps
are synchronous: HTML 4.8.11.8 "Playing the media resource" has the internal 
play steps queue the play
event (step 3.3) and then, depending on readyState, either queue waiting or 
notify about playing
(step 3.4), all before play() returns. Once the session admission had to cross 
to the UI process, the
seek the element had already enqueued completed first and fired seeking and 
seeked ahead of playing.

The same split, which 304036@main introduced by replacing a synchronous 
clientWillBeginPlayback() check
that already sat ahead of them, also left step 1 (invoke the resource selection 
algorithm), step 2 (seek
to the earliest possible position when playback has ended) and step 5 (clear 
the can autoplay flag)
running from the reply. None of the three depend on whether the session admits 
playback, so they run
synchronously again, in spec order. Only updatePlayState(), which starts the 
player, still waits for the
reply.

To resolve the event order, we are taking a hybrid approach:
the play, playing and waiting events are queued synchronously, as the spec 
states.
A page that needs to know playback has actually started can do what it did 
before play()
returned a promise and wait for playing followed by timeupdate. The play 
promises, on the other hand,
are resolved from the admission reply. That deviates from the spec, which 
resolves them in the same
task that fires playing, but it is hard to observe: promise reactions never run 
synchronously, they run
in a microtask after the promise is resolved, so a playing handler runs before 
the play() promise's
reaction either way. What changes is that the promise settles a task later, and 
therefore its ordering
against unrelated tasks.

scheduleNotifyAboutPlaying() takes a ShouldResolvePlayPromises argument so the 
queued task can fire
playing while leaving the promises pending; the readyState transitions that 
also notify about playing
keep settling them. m_playRequest is renamed m_beginPlaybackRequest, since it 
tracks the
clientWillBeginPlayback() reply rather than play().

We can revert 318932@main, whose shouldSeekToStart parameter carried step 2's 
decision across the reply:
with the step itself back in playInternal() there is nothing to carry, so the 
parameter and its plumbing are removed
as endedPlayback() is false while currentTime is still short of the duration, 
so no seek is
issued at all.

Queueing the events synchronously left three kinds of work on the reply side 
that JS can now observe at
those events, which sixteen media tests caught without site isolation.

1) Eleven of them reported an unhandled AbortError, or NotAllowedError for
    media/video-main-content-allow-then-deny.html: the play promises settle 
from the reply, so
    pauseInternal() rejected a promise the reply was about to resolve.

2) media/video-concurrent-playback.html and 
media/video-multiple-concurrent-playback.html paused the
    wrong element. A page that calls play() from a 'playing' handler leaves two 
admissions in flight, and
    enforceConcurrentPlaybackRestriction() ran from each completion, so the 
earlier admission's
    enforcement paused the element the later one had just started.

3) Four tests read state that updatePlayState() sets from the reply: 
usage.isPlaying and
    userHasPlayedAudioBefore in media/media-usage-state.html, pageMediaState() 
in
    media/video-is-playing-audio-after-load.html, and 
webkitDisplayingFullscreen in
    media/video-playsinline.html and media/video-fullscreen-only-playback.html.

For 1), 317907@main's m_playPromiseSettlementGuaranteed stays: pauseInternal() 
must not reject a promise
the reply will settle. For 2), a session enforces the restriction on completion 
only while it is still
the current session, and a session whose own admission is in flight counts as 
playing so the current one
can pause it. For 3), notifyAboutPlaying() reports the element as playing, 
playInternal() enters
fullscreen when playback requires it, and the media usage snapshot reads the 
element's playing state
rather than the session's, as the two neighbouring fields in that snapshot 
already do.

This fixes media/video-seek-after-end-play.html and
platform/mac/media/audio-session-category-video-track-change.html under site 
isolation; the latter's
assert runs on the playing event, which no longer waits for the category the 
admission reply applies.

media/video-multiple-concurrent-playback.html's baseline dates from 
webkit.org/b/162366 and omitted the
fourth video's playing event, which recorded the behaviour of the time: another 
element's concurrency
pause was queued ahead of the element's own playing event. The baseline gains 
that event.

media/video-is-playing-audio-after-load.html and 
media/video-main-content-allow-then-deny.html now catch
their play promise. A load() that tears the element down and a policy denial 
both reject a promise that
stays pending until the media session admits playback.

media/ios/short-audio-now-playing.html reported an unhandled AbortError on iOS. 
It
called play() without awaiting the promise and read the now-playing registration
at the 'playing' event, which is now queued during the internal play steps. The
promise was therefore still pending when the test assigned the second source, 
and
load() rejected it through prepareForLoad()'s cancelPendingEventsAndCallbacks().
The test now awaits play(), which settles where the 'playing' event used to and
after MediaSessionManagerCocoa::sessionWillBeginPlayback() has scheduled the
session status update that registers the application.

media/video-concurrent-playback.html still fails with site isolation enabled 
and is left for a follow-up:
its assertion runs in the second element's 'playing' handler, one round trip 
before the UI process can
pause the first.

Fly-by fix: RemoteMediaSessionManagerProxy::setCurrentSession() called
PlatformMediaSessionManager::setCurrentSession() directly, which only reorders 
the
session list. It now calls the base class, so the supported remote-control 
commands
are refreshed for the session that has become current, as they are for an
in-process manager.

* LayoutTests/media/ios/short-audio-now-playing-expected.txt:
* LayoutTests/media/ios/short-audio-now-playing.html:
* LayoutTests/media/video-is-playing-audio-after-load-expected.txt:
* LayoutTests/media/video-is-playing-audio-after-load.html:
* LayoutTests/media/video-main-content-allow-then-deny.html:
* LayoutTests/media/video-multiple-concurrent-playback-expected.txt:
* Source/WebCore/html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::~HTMLMediaElement):
(WebCore::HTMLMediaElement::scheduleNotifyAboutPlaying): Optionally leave the 
pending play promises for
the caller to settle.
(WebCore::HTMLMediaElement::notifyAboutPlaying): Report the element as playing.
(WebCore::HTMLMediaElement::completePlayInternal): Keep only what needs the 
admission reply.
(WebCore::HTMLMediaElement::playInternal): Run the resource selection, the seek 
to the earliest possible
position, the readyState-dependent event and the can autoplay flag 
synchronously; enter fullscreen when
playback requires it; settle the play promises from the admission reply.
(WebCore::HTMLMediaElement::pauseInternal): Don't reject a promise the reply 
will settle.
* Source/WebCore/html/HTMLMediaElement.h:
* Source/WebCore/html/MediaElementSession.cpp:
(WebCore::MediaElementSession::updateMediaUsageIfChanged): Report the element's 
playing state.
* Source/WebCore/platform/audio/MediaSessionManagerInterface.cpp:
(WebCore::MediaSessionManagerInterface::sessionWillBeginPlayback): Enforce the 
concurrent playback
restriction only while this session is the current one.
(WebCore::MediaSessionManagerInterface::enforceConcurrentPlaybackRestriction): 
Treat a session whose
admission is in flight as playing.
* Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.cpp:
(WebKit::RemoteMediaSessionManagerProxy::setCurrentSession): Call the base 
cocoa class.

Canonical link: https://commits.webkit.org/319099@main



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

Reply via email to