Bug 696586 - Part 2: Add moz-blob response type and ability to access to the blob in progress events. r=jonas
authorMasatoshi Kimura <VYV03354@nifty.ne.jp>
Mon, 30 Jan 2012 11:33:59 +0100
changeset 86942 027e317d7b5cc475ef326845ff318310c3308ba5
parent 86941 50cc1a5530da3e92e0d4a614d0d4aec353c09196
child 86943 50d035da14ccf72b7ef771376404322eaeeeb77d
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonas
bugs696586
milestone12.0a1
Bug 696586 - Part 2: Add moz-blob response type and ability to access to the blob in progress events. r=jonas
content/base/src/nsDOMBlobBuilder.cpp
content/base/src/nsDOMBlobBuilder.h
content/base/src/nsXMLHttpRequest.cpp
content/base/src/nsXMLHttpRequest.h
--- a/content/base/src/nsDOMBlobBuilder.cpp
+++ b/content/base/src/nsDOMBlobBuilder.cpp
@@ -172,17 +172,17 @@ NS_IMPL_ADDREF(nsDOMBlobBuilder)
 NS_IMPL_RELEASE(nsDOMBlobBuilder)
 NS_INTERFACE_MAP_BEGIN(nsDOMBlobBuilder)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozBlobBuilder)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozBlobBuilder)
 NS_INTERFACE_MAP_END
 
 nsresult
-nsDOMBlobBuilder::AppendVoidPtr(void* aData, PRUint32 aLength)
+nsDOMBlobBuilder::AppendVoidPtr(const void* aData, PRUint32 aLength)
 {
   NS_ENSURE_ARG_POINTER(aData);
 
   PRUint64 offset = mDataLen;
 
   if (!ExpandBufferSize(aLength))
     return NS_ERROR_OUT_OF_MEMORY;
 
@@ -221,29 +221,39 @@ nsDOMBlobBuilder::AppendArrayBuffer(JSOb
   return AppendVoidPtr(JS_GetArrayBufferData(aBuffer), JS_GetArrayBufferByteLength(aBuffer));
 }
 
 /* nsIDOMBlob getBlob ([optional] in DOMString contentType); */
 NS_IMETHODIMP
 nsDOMBlobBuilder::GetBlob(const nsAString& aContentType,
                           nsIDOMBlob** aBlob)
 {
+  return GetBlobInternal(aContentType, true, aBlob);
+}
+
+nsresult
+nsDOMBlobBuilder::GetBlobInternal(const nsAString& aContentType,
+                                  bool aClearBuffer,
+                                  nsIDOMBlob** aBlob)
+{
   NS_ENSURE_ARG(aBlob);
 
   Flush();
 
   nsCOMPtr<nsIDOMBlob> blob = new nsDOMMultipartFile(mBlobs,
                                                      aContentType);
   blob.forget(aBlob);
 
   // NB: This is a willful violation of the spec.  The spec says that
   // the existing contents of the BlobBuilder should be included
   // in the next blob produced.  This seems silly and has been raised
   // on the WHATWG listserv.
-  mBlobs.Clear();
+  if (aClearBuffer) {
+    mBlobs.Clear();
+  }
 
   return NS_OK;
 }
 
 /* nsIDOMBlob getFile (in DOMString name, [optional] in DOMString contentType); */
 NS_IMETHODIMP
 nsDOMBlobBuilder::GetFile(const nsAString& aName,
                           const nsAString& aContentType,
--- a/content/base/src/nsDOMBlobBuilder.h
+++ b/content/base/src/nsDOMBlobBuilder.h
@@ -79,18 +79,22 @@ class nsDOMBlobBuilder : public nsIDOMMo
 {
 public:
   nsDOMBlobBuilder()
     : mData(nsnull), mDataLen(0), mDataBufferLen(0)
   {}
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMMOZBLOBBUILDER
+
+  nsresult GetBlobInternal(const nsAString& aContentType,
+                           bool aClearBuffer, nsIDOMBlob** aBlob);
+  nsresult AppendVoidPtr(const void* aData, PRUint32 aLength);
+
 protected:
-  nsresult AppendVoidPtr(void* aData, PRUint32 aLength);
   nsresult AppendString(JSString* aString, JSContext* aCx);
   nsresult AppendBlob(nsIDOMBlob* aBlob);
   nsresult AppendArrayBuffer(JSObject* aBuffer);
 
   bool ExpandBufferSize(PRUint64 aSize)
   {
     if (mDataBufferLen >= mDataLen + aSize) {
       mDataLen += aSize;
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -577,16 +577,18 @@ nsXMLHttpRequest::Initialize(nsISupports
 
 void
 nsXMLHttpRequest::ResetResponse()
 {
   mResponseXML = nsnull;
   mResponseBody.Truncate();
   mResponseText.Truncate();
   mResponseBlob = nsnull;
+  mDOMFile = nsnull;
+  mBuilder = nsnull;
   mResultArrayBuffer = nsnull;
   mResultJSON = JSVAL_VOID;
   mLoadTransferred = 0;
   mResponseBodyDecodedPos = 0;
 }
 
 void
 nsXMLHttpRequest::SetRequestObserver(nsIRequestObserver* aObserver)
@@ -962,16 +964,38 @@ nsXMLHttpRequest::CreateResponseParsedJS
                     (jschar*)mResponseText.get(),
                     mResponseText.Length(), &mResultJSON)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+nsresult
+nsXMLHttpRequest::CreatePartialBlob()
+{
+  if (mDOMFile) {
+    if (mLoadTotal == mLoadTransferred) {
+      mResponseBlob = mDOMFile;
+    } else {
+      mResponseBlob =
+        mDOMFile->CreateSlice(0, mLoadTransferred, EmptyString());
+    }
+    return NS_OK;
+  }
+
+  nsCAutoString contentType;
+  if (mLoadTotal == mLoadTransferred) {
+    mChannel->GetContentType(contentType);
+  }
+
+  return mBuilder->GetBlobInternal(NS_ConvertASCIItoUTF16(contentType),
+                                   false, getter_AddRefs(mResponseBlob));
+}
+
 /* attribute AString responseType; */
 NS_IMETHODIMP nsXMLHttpRequest::GetResponseType(nsAString& aResponseType)
 {
   switch (mResponseType) {
   case XML_HTTP_RESPONSE_TYPE_DEFAULT:
     aResponseType.Truncate();
     break;
   case XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER:
@@ -990,16 +1014,19 @@ NS_IMETHODIMP nsXMLHttpRequest::GetRespo
     aResponseType.AssignLiteral("json");
     break;
   case XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT:
     aResponseType.AssignLiteral("moz-chunked-text");
     break;
   case XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER:
     aResponseType.AssignLiteral("moz-chunked-arraybuffer");
     break;
+  case XML_HTTP_RESPONSE_TYPE_MOZ_BLOB:
+    aResponseType.AssignLiteral("moz-blob");
+    break;
   default:
     NS_ERROR("Should not happen");
   }
 
   return NS_OK;
 }
 
 /* attribute AString responseType; */
@@ -1036,29 +1063,32 @@ NS_IMETHODIMP nsXMLHttpRequest::SetRespo
       return NS_ERROR_DOM_INVALID_STATE_ERR;
     }
     mResponseType = XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT;
   } else if (aResponseType.EqualsLiteral("moz-chunked-arraybuffer")) {
     if (!(mState & XML_HTTP_REQUEST_ASYNC)) {
       return NS_ERROR_DOM_INVALID_STATE_ERR;
     }
     mResponseType = XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER;
+  } else if (aResponseType.EqualsLiteral("moz-blob")) {
+    mResponseType = XML_HTTP_RESPONSE_TYPE_MOZ_BLOB;
   }
   // If the given value is not the empty string, "arraybuffer",
   // "blob", "document", or "text" terminate these steps.
 
   // If the state is OPENED, SetCacheAsFile would have no effect here
   // because the channel hasn't initialized the cache entry yet.
   // SetCacheAsFile will be called from OnStartRequest.
   // If the state is HEADERS_RECEIVED, however, we need to call
   // it immediately because OnStartRequest is already dispatched.
   if (mState & XML_HTTP_REQUEST_HEADERS_RECEIVED) {
     nsCOMPtr<nsICachingChannel> cc(do_QueryInterface(mChannel));
     if (cc) {
-      cc->SetCacheAsFile(mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB);
+      cc->SetCacheAsFile(mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
+                         mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB);
     }
   }
 
   return NS_OK;
 }
 
 /* readonly attribute jsval response; */
 NS_IMETHODIMP nsXMLHttpRequest::GetResponse(JSContext *aCx, jsval *aResult)
@@ -1092,22 +1122,32 @@ NS_IMETHODIMP nsXMLHttpRequest::GetRespo
       }
       *aResult = OBJECT_TO_JSVAL(mResultArrayBuffer);
     } else {
       *aResult = JSVAL_NULL;
     }
     break;
 
   case XML_HTTP_RESPONSE_TYPE_BLOB:
-    if (mState & XML_HTTP_REQUEST_DONE && mResponseBlob) {
+  case XML_HTTP_RESPONSE_TYPE_MOZ_BLOB:
+    *aResult = JSVAL_NULL;
+    if (mState & XML_HTTP_REQUEST_DONE) {
+      // do nothing here
+    } else if (mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) {
+      if (!mResponseBlob) {
+        rv = CreatePartialBlob();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+    } else {
+      return rv;
+    }
+    if (mResponseBlob) {
       JSObject* scope = JS_GetGlobalForScopeChain(aCx);
       rv = nsContentUtils::WrapNative(aCx, scope, mResponseBlob, aResult,
                                       nsnull, true);
-    } else {
-      *aResult = JSVAL_NULL;
     }
     break;
 
   case XML_HTTP_RESPONSE_TYPE_DOCUMENT:
     if (mState & XML_HTTP_REQUEST_DONE && mResponseXML) {
       JSObject* scope = JS_GetGlobalForScopeChain(aCx);
       rv = nsContentUtils::WrapNative(aCx, scope, mResponseXML, aResult,
                                       nsnull, true);
@@ -1707,44 +1747,55 @@ nsXMLHttpRequest::StreamReaderFunc(nsIIn
                                    PRUint32 *writeCount)
 {
   nsXMLHttpRequest* xmlHttpRequest = static_cast<nsXMLHttpRequest*>(closure);
   if (!xmlHttpRequest || !writeCount) {
     NS_WARNING("XMLHttpRequest cannot read from stream: no closure or writeCount");
     return NS_ERROR_FAILURE;
   }
 
-  if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB &&
-      xmlHttpRequest->mResponseBlob) {
-    *writeCount = count;
-    return NS_OK;
+  nsresult rv = NS_OK;
+
+  if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
+      xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) {
+    if (!xmlHttpRequest->mDOMFile) {
+      if (!xmlHttpRequest->mBuilder) {
+        xmlHttpRequest->mBuilder = new nsDOMBlobBuilder();
+      }
+      rv = xmlHttpRequest->mBuilder->AppendVoidPtr(fromRawSegment, count);
+    }
+    // Clear the cache so that the blob size is updated.
+    if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) {
+      xmlHttpRequest->mResponseBlob = nsnull;
+    }
+    if (NS_SUCCEEDED(rv)) {
+      *writeCount = count;
+    }
+    return rv;
   }
 
   if ((xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_DEFAULT &&
        xmlHttpRequest->mResponseXML) ||
       xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER ||
-      xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
       xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER) {
     // Copy for our own use
     PRUint32 previousLength = xmlHttpRequest->mResponseBody.Length();
     xmlHttpRequest->mResponseBody.Append(fromRawSegment,count);
     if (count > 0 && xmlHttpRequest->mResponseBody.Length() == previousLength) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
   } else if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_DEFAULT ||
              xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_TEXT ||
              xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_JSON ||
              xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT) {
     NS_ASSERTION(!xmlHttpRequest->mResponseXML,
                  "We shouldn't be parsing a doc here");
     xmlHttpRequest->AppendToResponseText(fromRawSegment, count);
   }
 
-  nsresult rv = NS_OK;
-
   if (xmlHttpRequest->mState & XML_HTTP_REQUEST_PARSEBODY) {
     // Give the same data to the parser.
 
     // We need to wrap the data in a new lightweight stream and pass that
     // to the parser, because calling ReadSegments() recursively on the same
     // stream is not supported.
     nsCOMPtr<nsIInputStream> copyStream;
     rv = NS_NewByteInputStream(getter_AddRefs(copyStream), fromRawSegment, count);
@@ -1768,17 +1819,17 @@ nsXMLHttpRequest::StreamReaderFunc(nsIIn
     *writeCount = count;
   } else {
     *writeCount = 0;
   }
 
   return rv;
 }
 
-bool nsXMLHttpRequest::CreateResponseBlob(nsIRequest *request)
+bool nsXMLHttpRequest::CreateDOMFile(nsIRequest *request)
 {
   nsCOMPtr<nsIFile> file;
   nsCOMPtr<nsICachingChannel> cc(do_QueryInterface(request));
   if (cc) {
     cc->GetCacheFile(getter_AddRefs(file));
   } else {
     nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(request);
     if (fc) {
@@ -1796,19 +1847,20 @@ bool nsXMLHttpRequest::CreateResponseBlo
       // fully cached (i.e. whether we can skip reading the response).
       cc->IsFromCache(&fromFile);
     } else {
       // If the response is coming from the local resource, we can skip
       // reading the response unconditionally.
       fromFile = true;
     }
 
-    mResponseBlob =
+    mDOMFile =
       new nsDOMFileFile(file, NS_ConvertASCIItoUTF16(contentType), cacheToken);
-    mResponseBody.Truncate();
+    mBuilder = nsnull;
+    NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
   }
   return fromFile;
 }
 
 NS_IMETHODIMP
 nsXMLHttpRequest::OnDataAvailable(nsIRequest *request,
                                   nsISupports *ctxt,
                                   nsIInputStream *inStr,
@@ -1817,30 +1869,31 @@ nsXMLHttpRequest::OnDataAvailable(nsIReq
 {
   NS_ENSURE_ARG_POINTER(inStr);
 
   NS_ABORT_IF_FALSE(mContext.get() == ctxt,"start context different from OnDataAvailable context");
 
   mProgressSinceLastProgressEvent = true;
 
   bool cancelable = false;
-  if (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB && !mResponseBlob) {
-    cancelable = CreateResponseBlob(request);
+  if ((mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
+       mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) && !mDOMFile) {
+    cancelable = CreateDOMFile(request);
     // The nsIStreamListener contract mandates us
     // to read from the stream before returning.
   }
 
   PRUint32 totalRead;
   nsresult rv = inStr->ReadSegments(nsXMLHttpRequest::StreamReaderFunc,
                                     (void*)this, count, &totalRead);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (cancelable) {
     // We don't have to read from the local file for the blob response
-    mResponseBlob->GetSize(&mLoadTransferred);
+    mDOMFile->GetSize(&mLoadTransferred);
     ChangeState(XML_HTTP_REQUEST_LOADING);
     return request->Cancel(NS_OK);
   }
 
   mLoadTransferred += totalRead;
 
   ChangeState(XML_HTTP_REQUEST_LOADING);
   
@@ -1931,17 +1984,18 @@ nsXMLHttpRequest::OnStartRequest(nsIRequ
   }
 
   mReadRequest = request;
   mContext = ctxt;
   mState |= XML_HTTP_REQUEST_PARSEBODY;
   mState &= ~XML_HTTP_REQUEST_MPART_HEADERS;
   ChangeState(XML_HTTP_REQUEST_HEADERS_RECEIVED);
 
-  if (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB) {
+  if (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
+      mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) {
     nsCOMPtr<nsICachingChannel> cc(do_QueryInterface(mChannel));
     if (cc) {
       cc->SetCacheAsFile(true);
     }
   }
 
   ResetResponse();
 
@@ -2127,44 +2181,41 @@ nsXMLHttpRequest::OnStopRequest(nsIReque
 
   // If we're received data since the last progress event, make sure to fire
   // an event for it, except in the HTML case, defer the last progress event
   // until the parser is done.
   if (!mIsHtml) {
     MaybeDispatchProgressEvents(true);
   }
 
-  nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
-  NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
-
-  if (NS_SUCCEEDED(status) && mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB) {
-    if (!mResponseBlob) {
-      CreateResponseBlob(request);
+  if (NS_SUCCEEDED(status) &&
+      (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
+       mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB)) {
+    if (!mDOMFile) {
+      CreateDOMFile(request);
     }
-    if (!mResponseBlob) {
+    if (mDOMFile) {
+      mResponseBlob = mDOMFile;
+      mDOMFile = nsnull;
+    } else {
       // Smaller files may be written in cache map instead of separate files.
       // Also, no-store response cannot be written in persistent cache.
       nsCAutoString contentType;
       mChannel->GetContentType(contentType);
-      // XXX We should change mResponseBody to be a raw malloc'ed buffer
-      //     to avoid copying the data.
-      PRUint32 blobLen = mResponseBody.Length();
-      void *blobData = PR_Malloc(blobLen);
-      if (blobData) {
-        memcpy(blobData, mResponseBody.BeginReading(), blobLen);
-
-        mResponseBlob =
-          new nsDOMMemoryFile(blobData, blobLen,
-                              NS_ConvertASCIItoUTF16(contentType));
-        mResponseBody.Truncate();
-      }
-      NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
+      mBuilder->GetBlobInternal(NS_ConvertASCIItoUTF16(contentType),
+                                false, getter_AddRefs(mResponseBlob));
+      mBuilder = nsnull;
     }
+    NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
+    NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
   }
 
+  nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
+  NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
+
   channel->SetNotificationCallbacks(nsnull);
   mNotificationCallbacks = nsnull;
   mChannelEventSink = nsnull;
   mProgressEventSink = nsnull;
 
   mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
 
   if (NS_FAILED(status)) {
--- a/content/base/src/nsXMLHttpRequest.h
+++ b/content/base/src/nsXMLHttpRequest.h
@@ -60,16 +60,18 @@
 #include "nsIJSNativeInitializer.h"
 #include "nsIDOMLSProgressEvent.h"
 #include "nsIDOMNSEvent.h"
 #include "nsITimer.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsDOMProgressEvent.h"
 #include "nsDOMEventTargetWrapperCache.h"
 #include "nsContentUtils.h"
+#include "nsDOMFile.h"
+#include "nsDOMBlobBuilder.h"
 
 class nsILoadGroup;
 class AsyncVerifyRedirectCallbackForwarder;
 class nsIUnicodeDecoder;
 
 class nsXHREventTarget : public nsDOMEventTargetWrapperCache,
                          public nsIXMLHttpRequestEventTarget
 {
@@ -212,17 +214,18 @@ protected:
   nsresult AppendToResponseText(const char * aBuffer, PRUint32 aBufferLen);
   static NS_METHOD StreamReaderFunc(nsIInputStream* in,
                 void* closure,
                 const char* fromRawSegment,
                 PRUint32 toOffset,
                 PRUint32 count,
                 PRUint32 *writeCount);
   nsresult CreateResponseParsedJSON(JSContext* aCx);
-  bool CreateResponseBlob(nsIRequest *request);
+  nsresult CreatePartialBlob(void);
+  bool CreateDOMFile(nsIRequest *request);
   // Change the state of the object with this. The broadcast argument
   // determines if the onreadystatechange listener should be called.
   nsresult ChangeState(PRUint32 aState, bool aBroadcast = true);
   already_AddRefed<nsILoadGroup> GetLoadGroup() const;
   nsIURI *GetBaseURI();
 
   nsresult RemoveAddEventListener(const nsAString& aType,
                                   nsRefPtr<nsDOMEventListenerWrapper>& aCurrent,
@@ -304,20 +307,30 @@ protected:
   enum {
     XML_HTTP_RESPONSE_TYPE_DEFAULT,
     XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER,
     XML_HTTP_RESPONSE_TYPE_BLOB,
     XML_HTTP_RESPONSE_TYPE_DOCUMENT,
     XML_HTTP_RESPONSE_TYPE_TEXT,
     XML_HTTP_RESPONSE_TYPE_JSON,
     XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT,
-    XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER
+    XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER,
+    XML_HTTP_RESPONSE_TYPE_MOZ_BLOB
   } mResponseType;
 
+  // It is either a cached blob-response from the last call to GetResponse,
+  // but is also explicitly set in OnStopRequest.
   nsCOMPtr<nsIDOMBlob> mResponseBlob;
+  // Non-null only when we are able to get a os-file representation of the
+  // response, i.e. when loading from a file, or when the http-stream
+  // caches into a file or is reading from a cached file.
+  nsRefPtr<nsDOMFileBase> mDOMFile;
+  // We stream data to mBuilder when response type is "blob" or "moz-blob"
+  // and mDOMFile is null.
+  nsRefPtr<nsDOMBlobBuilder> mBuilder;
 
   nsCString mOverrideMimeType;
 
   /**
    * The notification callbacks the channel had when Send() was
    * called.  We want to forward things here as needed.
    */
   nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks;