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