Video starts playing, but Firefox shows a "A network error caused the media download to fail part-way." overlay
Categories
(Core :: DOM: Networking, defect, P2)
Tracking
()
| Tracking | Status | |
|---|---|---|
| firefox-esr140 | --- | unaffected |
| firefox-esr153 | --- | unaffected |
| firefox153 | --- | unaffected |
| firefox154 | --- | wontfix |
| firefox155 | --- | fixed |
People
(Reporter: marco, Assigned: farre, NeedInfo)
References
(Regression, )
Details
(Keywords: regression)
Attachments
(1 file)
STR:
- Open https://www.ansa.it/sito/notizie/mondo/2026/07/23/francia-oltre-20-mila-evacuati-tra-residenti-e-turisti-per-il-maxi-incendio_e4142791-04b3-4e9a-a956-ab93e81cdaab.html
- Try to play the video
The video starts playing, but there's an error overlay with the text "A network error caused the media download to fail part-way.".
Comment 1•26 days ago
|
||
Strange, if I open the js console looking for an error the issue doesn't reproduce.
Comment 2•26 days ago
|
||
Comment 3•26 days ago
|
||
Bug 2057272 Triage Analysis
Generated: 2026-07-28
Bug URL: https://bugzilla.mozilla.org/show_bug.cgi?id=2057272
Bug Information
- Summary: Video starts playing, but Firefox shows a "A network error caused the media download to fail part-way." overlay
- Reporter: mcastelluccio@mozilla.com (Marco Castelluccio)
- Status: NEW
- Product: Core
- Component: Audio/Video: Playback
- Created: 2026-07-23
- Severity / Priority:
--/--(unset) - Security: No
- STR: Open an ansa.it article with a video and play it; the video plays but an error overlay appears with "A network error caused the media download to fail part-way."
Research Summary and Key Findings
The overlay text "A network error caused the media download to fail part-way." is not a Firefox
string — it is the standard video.js player message for MEDIA_ERR_NETWORK (MediaError code 2).
The site's player is reacting to an error event that Firefox fires on the <video> element even
though playback has started and continues. So this is a Firefox-side spurious/benign network error
being surfaced to the page.
Profiler analysis (share.firefox.dev/3RzvaCi, Firefox 155 / macOS, 5.25 s)
(The profile is format v68; profiler-cli had to be updated past v66 to load it.)
- The media element fires a real error, mid-playback. Marker m-289, type
HTMLMediaElement:Error,
category Media, fires at t = 2.148 s (error message field empty). - The error coincides exactly with the end of the video download. The only request for the video,
https://vs.ansa.it/sito/video_mp4_export/m20260723093626087.mp4(m-237), runs from 1.530 s to
2.148 s and returns HTTP 206, http/1.1, ~32 KB. Theerrorevent fires at the same instant the
load ends (2.148 s). - Playback is genuinely active. The MediaDecoderStateMachine thread shows
MDSM::PushAudio(405),
MDSM::PushVideo(112), and AudioSink activity, and the content main thread shows repeated
timeupdateevents — i.e. audio and video are decoding and the media clock is advancing. This
matches the report: "the video starts playing."
Root cause hypothesis (code-confirmed path)
ChannelMediaDecoder::NotifyDownloadEnded(nsresult aStatus)
(ChannelMediaDecoder.cpp:310-340)
branches on the channel's completion status:
NS_SUCCEEDED(aStatus)orNS_BASE_STREAM_CLOSED-> treated as a normal/suspended-by-cache end (no error).NS_BINDING_ABORTED-> user cancel (LoadAborted).- everything else ->
NetworkError(MediaResult(aStatus, "Download aborted")).
NetworkError -> MediaDecoder::NetworkError -> HTMLMediaElement::NetworkError
(HTMLMediaElement.cpp:6367),
which — because mReadyState != HAVE_NOTHING (we already have data) — calls Error(MEDIA_ERR_NETWORK),
firing the error event that video.js renders as the overlay.
For a progressive HTTP/1.1 206 transfer whose connection is closed/interrupted before the expected
byte range completes (statuses such as NS_ERROR_NET_INTERRUPT, NS_ERROR_NET_PARTIAL_TRANSFER, or
NS_ERROR_NET_RESET), the completion status falls into the else branch and Firefox raises a hard
MEDIA_ERR_NETWORK — even though enough data was buffered for playback to start and continue. That is
exactly the observed symptom.
The Heisenbug noted in comment 2 ("if I open the js console the issue doesn't reproduce") is
consistent with a connection/timing race: with devtools attached, channel timing/keep-alive behavior
shifts and the transfer ends cleanly (success / NS_BASE_STREAM_CLOSED) instead of interrupted.
Related bugs
No existing duplicate found. Searches of Audio/Video: Playback for the overlay text, "Download
aborted", and spurious MEDIA_ERR_NETWORK during playback returned only unrelated media error bugs.
Regression Timeline
Not reported as a regression and no regressed_by set. The NotifyDownloadEnded status-handling logic
is long-standing, so this is more likely a site-behavior + timing interaction than a recent regression.
If desired, the reporter (or QA) could attempt an mozregression once reliable STR/timing is nailed down,
but the intermittency (Heisenbug) makes bisection unreliable; a media log is the better next step.
Assessment
- Suggested Severity: S3
- Suggested Priority: P2
Assessment Reasoning
This is a genuine, reproducible web-compat defect: Firefox surfaces MEDIA_ERR_NETWORK to the page for
a download interruption that does not actually stop playback, so users see a scary "network error"
overlay on top of a video that is playing. It was filed by a Mozilla engineer with clear STR on a
mainstream news site, which raises confidence and priority. However, the video does play, the trigger
depends on server/connection behavior (HTTP/1.1 connection close on a 206 transfer) and is
timing-sensitive/intermittent, and the visible failure is an overlay rather than a hard playback block
— so S3 fits better than S2. Priority P2 reflects that it is a real user-facing correctness
issue in core media loading worth scheduling, not backlog-only.
The core question for a fix: should ChannelMediaDecoder::NotifyDownloadEnded treat
connection-interrupt statuses more leniently when the element already has data / is playing (similar to
the NS_BASE_STREAM_CLOSED path, possibly retrying the range request via the media cache) instead of
escalating straight to MEDIA_ERR_NETWORK? This is not a good-first-bug — it is a subtle
media/networking race with real regression risk (mishandling could hide genuine fatal errors or break
finite-stream end handling).
Codebase Investigation
Relevant Files Examined
- dom/media/ChannelMediaDecoder.cpp:310-340 —
NotifyDownloadEnded; theelsebranch firesNetworkError("Download aborted")for any non-success / non-NS_BASE_STREAM_CLOSED/ non-NS_BINDING_ABORTEDstatus. - dom/media/mediaelement/HTMLMediaElement.cpp:6367 —
NetworkError: firesError(MEDIA_ERR_NETWORK)whenevermReadyState != HAVE_NOTHING. - dom/media/ChannelMediaResource.cpp (lines ~185/218/250/939) and dom/media/MediaDecoder.cpp:887 — the
NotifyNetworkError->MediaDecoder::NetworkErrorplumbing.
Findings
The profile ties the HTMLMediaElement:Error marker to the exact end of the mp4 206/http1.1 transfer
while audio/video decoding and timeupdates continue — a spurious MEDIA_ERR_NETWORK. The most likely
concrete status reaching the else branch is a network-interrupt/partial-transfer result from the
HTTP/1.1 connection closing before the expected bytes arrive.
Suggested Investigation Areas
- Capture the exact
aStatusnsresult atNotifyDownloadEndedvia media logging (about:logging with e.g.MediaResource:5,MediaCache:5,MediaDecoder:5) while reproducing — this confirms whether it isNS_ERROR_NET_INTERRUPT/NS_ERROR_NET_PARTIAL_TRANSFER/NS_ERROR_NET_RESET. - Inspect the ansa.it response headers (HTTP version,
Connection,Content-LengthvsContent-Range) to see how/why the 206 transfer terminates early. - Evaluate handling connection-interrupt statuses in
NotifyDownloadEndedthe wayNS_BASE_STREAM_CLOSEDis handled (suspend/retry via the media cache) when the element already has data, rather than firingMEDIA_ERR_NETWORK.
Requesting Feedback
Already-known items excluded (clear STR and a profile are provided). Most useful next data point:
- A media log (about:logging, modules
MediaResource:5,MediaCache:5,MediaDecoder:5,cache2:5) captured while reproducing, to record the exact channel completion status feedingNotifyDownloadEnded. - Optionally, an HTTP capture / response headers for the mp4 request (HTTP version and how the connection is closed).
Bugzilla Use Tracking
- Total Bugzilla Queries: 5 (fields, comments, history for 2057272; 2 related-bug searches)
- Total Bugs Processed: 1 (plus related-search scan)
- Estimated Download Bandwidth Used: < 1 MB (plus profiler log download)
- Inaccessible Bugs Due to Permissions: 0
Updated•26 days ago
|
Updated•25 days ago
|
Updated•25 days ago
|
Comment 5•25 days ago
|
||
The overlay text is not a Firefox string — it's video.js's message for MEDIA_ERR_NETWORK (MediaError code 2). So the page is reacting to an error event that Firefox fires on the <video> element while playback is actually healthy. Two separate things are going on: ORB is blocking a legitimate media range request (pre-existing), and since bug 2049329 that block is now reported to the page as a fatal network error (the regression).
TL;DR
Playback is fine; the overlay is real but the error behind it is spurious. Two stacked defects:
-
Opaque Response Blocking blocks one of the video's byte-range requests. The video is cross-origin, and
video/mp4is not on ORB's safelist, so each response has to be justified some other way. For a partial (206) response that does not start at byte 0, the only thing that can justify it is a parent-process record that this window has already loaded media from that URL. That record was missing, so ORB rejected a perfectly valid range response. This is pre-existing, and the fix belongs in necko. -
That block is now reported to the page as a fatal error.
ChannelMediaDecoder::NotifyDownloadEndedsilently ignoresNS_BINDING_ABORTEDbut turns every other failure status intoMEDIA_ERR_NETWORK. Bug 2049329 Part 6 (6b37cf099696, landed 2026-07-09 for 154) changed ORB's cancel status fromNS_BINDING_ABORTEDtoNS_ERROR_DOM_NETWORK_ERR, so a block that used to be silent now fires anerrorevent. This is the regression — it did not cause the block, it made it visible.
Note this is not a CORS problem, and the server is well-behaved. Fixing #2 removes the overlay (and also covers bug 2057304, which reaches the same code for an unrelated reason); fixing #1 removes the underlying stalled download. Both are needed.
What the profile shows
From the profile in comment 2 (raw JSON pulled from the profile store; note about:logging wasn't enabled, so there are no media log markers):
The server itself is well-behaved. Probing it directly: Content-Type: video/mp4, Accept-Ranges honoured, and correct partial responses both at the start and mid-file. In the parent process, the mp4 channel ends with STATUS_CANCEL and requestStatus = NS_ERROR_DOM_NETWORK_ERR — Gecko cancelled it, this is not a server-side truncation. And 0.06 ms before that cancel there is an "ORB safelist check" marker with payload "Before sniff":
-618.721 [content] NET START vs.ansa.it/sito/video_mp4_export/m20260723093626087.mp4
-0.657 [parent] ORB safelist check | "Before sniff"
-0.600 [parent] NET CANCEL requestStatus=NS_ERROR_DOM_NETWORK_ERR, code=206
-0.373 [content] MediaCacheStream::NotifyDataStarted
-0.199 [content] MediaCacheStream::NotifyDataEnded
-0.060 [content] ChannelMediaResource::CacheClientNotifyDataEnded
0.000 [content] HTMLMediaElement:Error
NS_ERROR_DOM_NETWORK_ERR is put on a channel by exactly three call sites in netwerk/, and all three are Opaque Response Blocking (nsHttpChannel.cpp:2460, OpaqueResponseUtils.cpp:447, :566). Combined with the "Before sniff" marker, the blocking decision is PerformOpaqueResponseSafelistCheckBeforeSniff returning Block.
Also worth noting: the MDSM thread keeps decoding and timeupdate keeps firing straight through the error, and there is no NS_ERROR_DOM_MEDIA_* marker anywhere in the capture. This is purely a resource-loading problem, not a decode problem.
Why ORB blocks it
The video is on vs.ansa.it while the page is www.ansa.it, so it's a cross-origin no-cors load — LoadTainting::Opaque, and ORB applies.
Probing the server directly, it behaves correctly — Content-Type: video/mp4, Access-Control-Allow-Origin: *, and it honours ranges, returning 206 with a correct Content-Range for both a 0- request and a mid-file request.
The catch is that video/mp4 is not ORB-safelisted in the shipping configuration. browser.opaqueResponseBlocking.mediaExceptionsStrategy defaults to 1 = AllowSome, which safelists only audio/mpeg, audio/aac, audio/aacp and multipart/x-mixed-replace — the blanket video/* allowance is in the AllowAll arm, which is off. So step 3 falls through to default: break and evaluation reaches:
- step 4 — allow if
NoCorsMediaRequestState() == Subsequent - step 5 — block if status is
206andIsFirstPartialResponseis false, i.e.Content-Rangedoesn't start at byte 0
The request that got blocked is a mid-stream range request — the profile shows ChannelMediaResource::Seek running 20 µs before the channel opens. So step 4 was the only thing that could have saved it, and it didn't.
Subsequent is reported only when the URI is in WindowGlobalParent::mNoCorsMediaRequestURIs, a per-window set populated from exactly one place: RecordSubsequentNoCorsRequestState, reached only when the media sniffer runs on an initial media response. A resource that never issues a range-0 request is therefore never recorded.
That's exactly what a cloned media resource does. ChannelMediaResource::CloneData says so in the code:
Initially the clone is treated as suspended by the cache, because we don't have a channel. If the cache needs to read d call CacheClientResume (or CacheClientSeek with aResume true) which will recreate the channel.
Cloning is triggered by HTMLMediaElement::LoadResource when another element in the same process is already loading the same URI withthe same principal and CORS mode — the element table is per-process, not per-window. And MediaCacheStream::InitAsClone does appear in the profile, 21 ms before the suspend/resume/seek sequence that opens the doomed channel.
I can't yet say from the profile why the URI wasn't recorded — the capture starts after the page had already loaded, so the original range-0 request isn't in it. The two candidates are (a) the clone's element is in a different inner window than the element that made
the initial request, so a different WindowGlobalParent is consulted, or (b) the original request predates the capture awas replaced.MOZ_LOG=ORB:5` on a repro prints the blocking reason string and would settle it immediately.
The regression
This ORB block is pre-existing, but until recently it was silent. ChannelMediaDecoder::NotifyDownloadEnded splits three ways on the channel completion status:
- success, or
NS_BASE_STREAM_CLOSED— treated as a normal/suspended-by-cache end. No error. NS_BINDING_ABORTED—owner->LoadAborted(). Silent, no error event reaches the page.- anything else —
NetworkError(MediaResult(aStatus, "Download aborted")), which reachesHTMLMediaElement::NetworkErrorand — becausemReadyState != HAVE_NOTHING, i.e. we already have data — firesError(MEDIA_ERR_NETWORK).
Bug 2049329 Part 6 (D310986, 6b37cf099696 / hg 24369a40b7a6, landed 2026-07-09) changed every ORB block status from NS_BINDING_ABORTED / NS_ERROR_FAILURE to NS_ERROR_DOM_NETWORK_ERR. The relevant hunk:
if (opaqueResponse == OpaqueResponse::Block) {
SetChannelBlockedByOpaqueResponse();
- CancelWithReason(NS_BINDING_ABORTED,
+ CancelWithReason(NS_ERROR_DOM_NETWORK_ERR,
"OpaqueResponseBlocker::BlockResponse"_ns);
return NS_BINDING_ABORTED;
}
So before that patch, an ORB-blocked media channel landed on the NS_BINDING_ABORTED branch and was swallowed by LoadAborted(). After it, the identical block lands on the else branch and fires MEDIA_ERR_NETWORK. The download was already being blocked before — the patch only changed whether the page gets told about it, which is why this surfaces now as a visible overlay rather than as a silent stall.
The commit's rationale ("This is closer to what spec does when #opaque-response-safelist-check returns false, where it returns a network error from #http-fetch") is reasonable for fetch(), but no media-side consumer of the status was audited — outside of necko the patch only touched js/xpconnect/src/xpc.msg and netwerk/test/unit/test_orb_empty_header.js.
This also explains comment 1: with devtools open, caching and channel timing shift enough that the load doesn't end up needing a mid-stream reopen, so ORB never sees a non-first-partial 206 and nothing is blocked.
farre, does the anaylsis for necko side sound right to you? Thanks!
Comment 6•25 days ago
|
||
Kershaw, would you mind help to check the ORB issue analysis in above comment? If that is correct analysis, how hard would be for necko to fix that? Thanks!
Comment 7•25 days ago
|
||
Set release status flags based on info from the regressing bug 2049329
Comment 8•24 days ago
|
||
I think this what happened.
ORB blocks the failing request because it is a 206 Partial Content response for a mid-file range:
Range: bytes=5996229-
Content-Range: bytes 5996229-.../...
In PerformOpaqueResponseSafelistCheckBeforeSniff, this fails the IsFirstPartialResponse() check because the range does not start at byte 0. That means the request can only be allowed if NoCorsMediaRequestState() returns Subsequent.
The problem is that the “this URL was already validated as media” state appears to be scoped to a single inner window, via WindowGlobalParent::mNoCorsMediaRequestURIs.
In this case, the same MP4 is used by two different windows/frames:
- Window A made the initial bytes=0- request, the response was sniffed as video, and the URL was recorded as a valid media request. Later range requests from this window are treated as Subsequent and are allowed.
- Window B only made a mid-file range request for the same URL. Since the initial validated request happened in Window A, Window B does not have the URL in its
mNoCorsMediaRequestURIsset. As a result,NoCorsMediaRequestState()returnsInitial, and ORB blocks the request.
So the range response itself is not invalid. The issue is that the media-validation state is per-window, but the media resource can be shared through the HTTP cache and later used by another window that only issues a mid-file range request. In that case, ORB does not know that the URL was already validated as media, so it incorrectly blocks playback.
| Assignee | ||
Comment 9•19 days ago
|
||
Updated•19 days ago
|
Updated•17 days ago
|
Updated•13 days ago
|
Updated•13 days ago
|
Comment 11•12 days ago
|
||
Comment 12•11 days ago
|
||
| bugherder | ||
| Assignee | ||
Comment 13•11 days ago
|
||
(In reply to Jim Mathies [:jimm] from comment #10)
Should we track this for 154?
I think so, but I think I'd like to see it work on 155 a bit before uplifting.
| Assignee | ||
Updated•11 days ago
|
Comment 14•11 days ago
|
||
The patch landed in nightly and beta is affected.
:farre, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- See https://wiki.mozilla.org/Release_Management/Requesting_an_Uplift for documentation on how to request an uplift.
- If no, please set
status-firefox154towontfix.
For more information, please visit BugBot documentation.
| Assignee | ||
Comment 15•10 days ago
|
||
If this isn't a big webcompat thing, I think we can just as well hold off uplifting.
Updated•5 days ago
|
Description
•