Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: 013da9aa8cc1a0e26f5f34408efa118956499193
      
https://github.com/WebKit/WebKit/commit/013da9aa8cc1a0e26f5f34408efa118956499193
  Author: Jean-Yves Avenard <[email protected]>
  Date:   2026-04-30 (Thu, 30 Apr 2026)

  Changed paths:
    A LayoutTests/media/video-source-mime-sniff-mp4-expected.txt
    A LayoutTests/media/video-source-mime-sniff-mp4.html
    A LayoutTests/media/video-source-mime-sniff-webm-expected.txt
    A LayoutTests/media/video-source-mime-sniff-webm.html
    M Source/WebCore/html/HTMLMediaElement.cpp
    M Source/WebCore/html/HTMLMediaElement.h
    M Source/WebCore/html/HTMLVideoElement.cpp
    M Source/WebCore/platform/graphics/MediaPlayer.cpp
    M Source/WebCore/platform/graphics/MediaPlayer.h

  Log Message:
  -----------
  citroen.fr: one section fails to display properly after scrolling
https://bugs.webkit.org/show_bug.cgi?id=313652
rdar://166181001

Reviewed by Eric Carlson

When a <video> used child <source> elements and the page-declared `type`
attribute disagreed with what the server actually returned (e.g. an Adobe
Scene7-served URL declared as video/mp4 whose response body was video/webm),
the page never played. WebKit's resource selection algorithm fed the page-
declared type into MediaPlayer engine selection: AVFoundation accepted the
candidate based on type=video/mp4 and rejected the body during decode.
MediaPlayer's engine fallback chain then tried MediaPlayerPrivateWebM, but
WebM aborted early in doPreload() because the contentType it was handed was
still video/mp4 — it never inspected the body. Every alternative engine
rejected the source for the same reason, the source-element resource
selection algorithm gave up, and an error was fired on <source>.

The src-attribute path (LoadingFromSrcAttr) already had a content-sniffer
fallback for this case implemented inside HTMLMediaElement::setNetworkState,
but the source-element path (LoadingFromSourceElement) did not. Rather than
duplicate the fallback logic in HTMLMediaElement for each load-state, the
sniff-and-reload is moved down into MediaPlayer, where it applies uniformly
to any load that reaches the engine layer:

- After an engine reports FormatError or DecodeError before reaching
    HaveMetadata, MediaPlayer first runs its existing engine-fallback chain
    (nextBestMediaEngine / nextMediaEngine) so a correctly-declared type
    still gets its other installed engines a chance.
- When the fallback chain is exhausted and we haven't sniffed yet,
    MediaPlayer starts a MediaResourceSniffer against m_url, fetches the
    first 1445 bytes, and uses the magic bytes to determine the actual
    container type. If the sniffed type differs from the originally-loaded
    m_loadOptions.contentType, MediaPlayer clears its attempted-engine set,
    updates m_loadOptions.contentType to the sniffed value, and re-runs
    engine selection with loadWithNextMediaEngine(nullptr). The correct
    engine is picked based on what the body actually is.
- If the sniffer returns the same type, an empty result, or a non-cancelled
    error, the original engine failure is propagated to the client
    unchanged. NetworkError never triggers the sniff — the loader has
    already failed and retrying through the same loader would just waste a
    request (and break content-blocker tests that count blocked loads).

Because the sniff lives at the engine layer, it is symmetric: a
<source type=video/mp4> pointing at a WebM body reloads into WebM, and a
<source type=video/webm> pointing at an MP4 body reloads into AVFoundation.
The HTMLMediaElement source-selection algorithm, selectNextSourceChild's
strict supportsType filter, and the per-<source> error-event timing are
unchanged — no spec deviation, no delay for the common "first source has
an unsupported declared type" fallback case, and `canPlayType` continues
to return "" for unknown types.

The HTMLMediaElement-side SrcAttr sniff fallback (sniffForContentType,
cancelSniffer, m_sniffer, m_networkErrorOccured, m_lastContentTypeUsed,
SnifferPromise) is removed; MediaPlayer handles the same case
transparently to the client.

* LayoutTests/media/video-source-mime-sniff-webm.html: Added.
* LayoutTests/media/video-source-mime-sniff-webm-expected.txt: Added.
End-to-end test: a <source> declares video/mp4 but points at a real WebM
file; asserts loadeddata fires (proving MediaPlayer's sniff reloaded into
the WebM engine).

* LayoutTests/media/video-source-mime-sniff-mp4.html: Added.
* LayoutTests/media/video-source-mime-sniff-mp4-expected.txt: Added.
Symmetric end-to-end test: a <source> declares video/webm but points at a
real MP4 file; asserts loadeddata fires (proving MediaPlayer's sniff
reloaded into the AVFoundation engine).

* Source/WebCore/platform/graphics/MediaPlayer.h:
Forward-declare MediaResourceSniffer, add m_sniffer and m_sniffAttempted
members, declare the private attemptSniffAndReload and cancelSniffer
helpers.

* Source/WebCore/platform/graphics/MediaPlayer.cpp:
(WebCore::MediaPlayer::load):
(WebCore::MediaPlayer::load with MediaSourcePrivateClient):
(WebCore::MediaPlayer::load with MediaStreamPrivate):
Cancel any in-flight sniffer and reset m_sniffAttempted on a fresh load.
(WebCore::MediaPlayer::networkStateChanged): Narrow the engine-fallback
trigger from `>= FormatError` to `FormatError || DecodeError` so
NetworkError no longer probes additional engines (sniffer included).
When the fallback chain is exhausted, call attemptSniffAndReload() and
defer reporting the failure to the client if a sniff was started.
(WebCore::MediaPlayer::cancelSniffer): New. Cancels an in-flight sniffer.
(WebCore::MediaPlayer::attemptSniffAndReload): New. Single-shot sniff
against m_url; on a distinct sniffed type, clears m_attemptedEngines,
updates m_loadOptions.contentType, and re-runs engine selection via
loadWithNextMediaEngine(nullptr). On same-type/empty/error, propagates
the original engine failure via mediaPlayerNetworkStateChanged.
(WebCore::MediaPlayer::reset): Cancel any in-flight sniffer and reset
m_sniffAttempted alongside m_attemptedEngines.

* Source/WebCore/html/HTMLMediaElement.h:
* Source/WebCore/html/HTMLMediaElement.cpp:
Remove the HTMLMediaElement-side sniff fallback; MediaPlayer owns it now.
(WebCore::HTMLMediaElement::setNetworkState): Remove the SrcAttr-only
sniff-and-retry block; the engine-level sniff handles both src-attr and
source-element paths.
(WebCore::HTMLMediaElement::loadResource): Inline the completion-handler
lambda into the function body. It was invoked exactly once, synchronously,
at the end of the function, and its captures (url, player, logSiteIdentifier,
this) duplicated locals that were already in scope — it was purely a
lexical group with no async dispatch. Dropping the wrapper also removes a
dead UNUSED_PARAM(logSiteIdentifier) guard that only existed to silence
the unused-capture warning when ENABLE(MEDIA_SOURCE) was off. Also drops
the m_lastContentTypeUsed bookkeeping that belonged to the removed sniff
fallback.
(WebCore::HTMLMediaElement::createMediaPlayer): Drop the
m_networkErrorOccured / m_lastContentTypeUsed / cancelSniffer resets —
the flags, the helper, and the sniffer are all gone.

* Source/WebCore/html/HTMLVideoElement.cpp:
Add `#include <wtf/NativePromise.h>`. 
HTMLVideoElement::bitmapImageForCurrentTime
calls BitmapImagePromise::createAndReject(), which instantiates
NativePromise<Ref<ShareableBitmap>, void>. MediaPlayer.h only declares the
alias and does not include <wtf/NativePromise.h>; on Cocoa/GTK the template
definition reached this translation unit through a transitive include, but
on PlayStation that chain did not resolve and the build failed with
"implicit instantiation of undefined template". Including the header
directly at the point of use makes the build independent of which other
headers happen to pull it in.

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



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

Reply via email to