Bug 1443942 - Ensure MediaCacheStreams are initialized with the length of the resource, not the length of the byte range response. r?jya
I'm seeing intermittent failures of test_midflight_redirect_blocked. In this
test, our custom server responds to Firefox's 0- HTTP Byte Range request with a
[0,200] response. When Firefox requests 200-, the server responds with a cross
origin redirect, and then the remainder of the resource.
However sometimes while running test_midflight_redirect_blocked the MP4 demuxer
reads through all 200 bytes while trying to parse metadata before the redirect
has occurred and fed more data into the cache, and so the demuxer thinks it's
hit end of stream, and reports a failure. The demuxer thinks it's hit end of
stream, because we initialize the MediaCacheStream length in
ChannelMediaResource::Open() with the value of the Content-Length HTTP header.
But in an HTTP byte range response, the Content-Length header tells you the
length of the range returned, not the length of the entire resource. The length
of the resource is in the Content-Range header, we need to use that if
available.
So to fix this intermittent test failure, we need to also parse the
Content-Range header in ChannelMediaResource::Open(), and use the length from
that if available.
MozReview-Commit-ID: 29pPRsUvxag
--- a/dom/media/ChannelMediaResource.cpp
+++ b/dom/media/ChannelMediaResource.cpp
@@ -317,17 +317,17 @@ ChannelMediaResource::IsTransportSeekabl
MOZ_ASSERT(NS_IsMainThread());
return mIsTransportSeekable;
}
nsresult
ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
int64_t& aRangeStart,
int64_t& aRangeEnd,
- int64_t& aRangeTotal)
+ int64_t& aRangeTotal) const
{
NS_ENSURE_ARG(aHttpChan);
nsAutoCString rangeStr;
nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
rangeStr);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
@@ -446,40 +446,82 @@ ChannelMediaResource::OnDataAvailable(ui
return rv;
NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
count -= read;
}
return NS_OK;
}
+int64_t
+ChannelMediaResource::CalculateStreamLength() const
+{
+ if (!mChannel) {
+ return -1;
+ }
+
+ nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
+ if (!hc) {
+ return -1;
+ }
+
+ bool succeeded = false;
+ Unused << hc->GetRequestSucceeded(&succeeded);
+ if (!succeeded) {
+ return -1;
+ }
+
+ // We can't determine the length of uncompressed payload.
+ const bool isCompressed = IsPayloadCompressed(hc);
+ if (isCompressed) {
+ return -1;
+ }
+
+ int64_t contentLength = -1;
+ if (NS_FAILED(hc->GetContentLength(&contentLength))) {
+ return -1;
+ }
+
+ uint32_t responseStatus = 0;
+ Unused << hc->GetResponseStatus(&responseStatus);
+ if (responseStatus != HTTP_PARTIAL_RESPONSE_CODE) {
+ return contentLength;
+ }
+
+ // We have an HTTP Byte Range response. The Content-Length is the length
+ // of the response, not the resource. We need to parse the Content-Range
+ // header and extract the range total in order to get the stream length.
+ int64_t rangeStart = 0;
+ int64_t rangeEnd = 0;
+ int64_t rangeTotal = 0;
+ bool gotRangeHeader = NS_SUCCEEDED(
+ ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal));
+ if (gotRangeHeader && rangeTotal != -1) {
+ contentLength = std::max(contentLength, rangeTotal);
+ }
+ return contentLength;
+}
+
nsresult
ChannelMediaResource::Open(nsIStreamListener** aStreamListener)
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
MOZ_ASSERT(aStreamListener);
MOZ_ASSERT(mChannel);
- int64_t cl = -1;
- nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
- if (hc && !IsPayloadCompressed(hc)) {
- if (NS_FAILED(hc->GetContentLength(&cl))) {
- cl = -1;
- }
- }
-
- nsresult rv = mCacheStream.Init(cl);
+ int64_t streamLength = CalculateStreamLength();
+ nsresult rv = mCacheStream.Init(streamLength);
if (NS_FAILED(rv)) {
return rv;
}
mSharedInfo = new SharedInfo;
mSharedInfo->mResources.AppendElement(this);
- mIsLiveStream = cl < 0;
+ mIsLiveStream = streamLength < 0;
mListener = new Listener(this, 0, ++mLoadID);
*aStreamListener = mListener;
NS_ADDREF(*aStreamListener);
return NS_OK;
}
nsresult
ChannelMediaResource::OpenChannel(int64_t aOffset)
--- a/dom/media/ChannelMediaResource.h
+++ b/dom/media/ChannelMediaResource.h
@@ -226,17 +226,21 @@ protected:
void UpdatePrincipal();
// Parses 'Content-Range' header and returns results via parameters.
// Returns error if header is not available, values are not parse-able or
// values are out of range.
nsresult ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
int64_t& aRangeStart,
int64_t& aRangeEnd,
- int64_t& aRangeTotal);
+ int64_t& aRangeTotal) const;
+
+ // Calculates the length of the resource using HTTP headers, if this
+ // is an HTTP channel. Returns -1 on failure, or for non HTTP channels.
+ int64_t CalculateStreamLength() const;
struct Closure
{
uint32_t mLoadID;
ChannelMediaResource* mResource;
};
static nsresult CopySegmentToCache(nsIInputStream* aInStream,