Bug 1337016 - XHR should create a Blob in the parent process when run from a file:// URL, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 08 Feb 2017 14:34:42 +0100
changeset 480574 a75a77d1bb99e3238d422572fbf5e8fd102af8bb
parent 480573 596e15a569f847ea337b8917bca1ee84744a062c
child 480575 67160e6118d12e4ac9d6c38686587d2371ebf059
push id44589
push userbmo:tchiovoloni@mozilla.com
push dateWed, 08 Feb 2017 15:54:46 +0000
reviewerssmaug
bugs1337016
milestone54.0a1
Bug 1337016 - XHR should create a Blob in the parent process when run from a file:// URL, r=smaug
dom/file/File.cpp
dom/file/FileCreatorHelper.cpp
dom/file/FileCreatorHelper.h
dom/xhr/XMLHttpRequestMainThread.cpp
dom/xhr/XMLHttpRequestMainThread.h
dom/xhr/moz.build
dom/xhr/tests/browser.ini
dom/xhr/tests/browser_blobFromFile.js
--- a/dom/file/File.cpp
+++ b/dom/file/File.cpp
@@ -587,36 +587,40 @@ File::Constructor(const GlobalObject& aG
 File::CreateFromNsIFile(const GlobalObject& aGlobal,
                         nsIFile* aData,
                         const ChromeFilePropertyBag& aBag,
                         SystemCallerGuarantee aGuarantee,
                         ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
   RefPtr<Promise> promise =
-    FileCreatorHelper::CreateFile(aGlobal, aData, aBag, true, aRv);
+    FileCreatorHelper::CreateFile(global, aData, aBag, true, aRv);
   return promise.forget();
 }
 
 /* static */ already_AddRefed<Promise>
 File::CreateFromFileName(const GlobalObject& aGlobal,
                          const nsAString& aPath,
                          const ChromeFilePropertyBag& aBag,
                          SystemCallerGuarantee aGuarantee,
                          ErrorResult& aRv)
 {
   nsCOMPtr<nsIFile> file;
   aRv = NS_NewLocalFile(aPath, false, getter_AddRefs(file));
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
   RefPtr<Promise> promise =
-    FileCreatorHelper::CreateFile(aGlobal, file, aBag, false, aRv);
+    FileCreatorHelper::CreateFile(global, file, aBag, false, aRv);
   return promise.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////
 // mozilla::dom::BlobImpl implementation
 
 already_AddRefed<BlobImpl>
 BlobImpl::Slice(const Optional<int64_t>& aStart,
--- a/dom/file/FileCreatorHelper.cpp
+++ b/dom/file/FileCreatorHelper.cpp
@@ -20,33 +20,30 @@
 #ifdef CreateFile
 #undef CreateFile
 #endif
 
 namespace mozilla {
 namespace dom {
 
 /* static */ already_AddRefed<Promise>
-FileCreatorHelper::CreateFile(const GlobalObject& aGlobal,
+FileCreatorHelper::CreateFile(nsIGlobalObject* aGlobalObject,
                               nsIFile* aFile,
                               const ChromeFilePropertyBag& aBag,
                               bool aIsFromNsIFile,
                               ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
-  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  RefPtr<Promise> promise = Promise::Create(aGlobalObject, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  nsCOMPtr<nsPIDOMWindowInner> window =
-    do_QueryInterface(aGlobal.GetAsSupports());
+  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobalObject);
 
   // Parent process
 
   if (XRE_IsParentProcess()) {
     RefPtr<File> file =
       CreateFileInternal(window, aFile, aBag, aIsFromNsIFile, aRv);
     if (aRv.Failed()) {
       return nullptr;
--- a/dom/file/FileCreatorHelper.h
+++ b/dom/file/FileCreatorHelper.h
@@ -14,33 +14,33 @@
 
 // Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
 // replaced by FileCreatorHelper#CreateFileW.
 #ifdef CreateFile
 #undef CreateFile
 #endif
 
 class nsIFile;
+class nsIGlobalObject;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 namespace dom {
 
 struct ChromeFilePropertyBag;
-class GlobalObject;
 class Promise;
 class File;
 
 class FileCreatorHelper final
 {
 public:
   NS_INLINE_DECL_REFCOUNTING(FileCreatorHelper);
 
   static already_AddRefed<Promise>
-  CreateFile(const GlobalObject& aGlobal,
+  CreateFile(nsIGlobalObject* aGlobalObject,
              nsIFile* aFile,
              const ChromeFilePropertyBag& aBag,
              bool aIsFromNsIFile,
              ErrorResult& aRv);
 
   void
   ResponseReceived(BlobImpl* aBlobImpl, nsresult aRv);
 
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -11,21 +11,23 @@
 #include <unistd.h>
 #endif
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/BlobSet.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/DOMString.h"
 #include "mozilla/dom/File.h"
+#include "mozilla/dom/FileCreatorHelper.h"
 #include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/dom/MutableBlobStorage.h"
 #include "mozilla/dom/XMLDocument.h"
 #include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/LoadContext.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsIDOMDocument.h"
 #include "mozilla/dom/ProgressEvent.h"
 #include "nsIJARChannel.h"
@@ -84,16 +86,22 @@
 #include "xpcjsid.h"
 #include "nsITimedChannel.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsZipArchive.h"
 #include "mozilla/Preferences.h"
 #include "private/pprio.h"
 #include "XMLHttpRequestUpload.h"
 
+// Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
+// replaced by FileCreatorHelper#CreateFileW.
+#ifdef CreateFile
+#undef CreateFile
+#endif
+
 using namespace mozilla::net;
 
 namespace mozilla {
 namespace dom {
 
 // Maximum size that we'll grow an ArrayBuffer instead of doubling,
 // once doubling reaches this threshold
 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH = 32*1024*1024;
@@ -290,17 +298,16 @@ XMLHttpRequestMainThread::InitParameters
 
 void
 XMLHttpRequestMainThread::ResetResponse()
 {
   mResponseXML = nullptr;
   mResponseBody.Truncate();
   TruncateResponseText();
   mResponseBlob = nullptr;
-  mDOMBlob = nullptr;
   mBlobStorage = nullptr;
   mBlobSet = nullptr;
   mResultArrayBuffer = nullptr;
   mArrayBufferBuilder.reset();
   mResultJSON.setUndefined();
   mDataAvailable = 0;
   mLoadTransferred = 0;
   mResponseBodyDecodedPos = 0;
@@ -318,17 +325,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
                                                   XMLHttpRequestEventTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMBlob)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
@@ -339,17 +345,16 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   tmp->mResultJSON.setUndefined();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseBlob)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMBlob)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationCallbacks)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
@@ -654,29 +659,16 @@ XMLHttpRequestMainThread::CreateResponse
 
   mResultJSON = value;
   return NS_OK;
 }
 
 void
 XMLHttpRequestMainThread::CreatePartialBlob(ErrorResult& aRv)
 {
-  if (mDOMBlob) {
-    // Use progress info to determine whether load is complete, but use
-    // mDataAvailable to ensure a slice is created based on the uncompressed
-    // data count.
-    if (mState == State::done) {
-      mResponseBlob = mDOMBlob;
-    } else {
-      mResponseBlob = mDOMBlob->CreateSlice(0, mDataAvailable,
-                                            EmptyString(), aRv);
-    }
-    return;
-  }
-
   // mBlobSet can be null if the request has been canceled
   if (!mBlobSet) {
     return;
   }
 
   nsAutoCString contentType;
   if (mState == State::done) {
     mChannel->GetContentType(contentType);
@@ -1444,17 +1436,17 @@ XMLHttpRequestMainThread::GetCurrentJARC
   return appChannel.forget();
 }
 
 bool
 XMLHttpRequestMainThread::IsSystemXHR() const
 {
   return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
 }
- 
+
 bool
 XMLHttpRequestMainThread::InUploadPhase() const
 {
   // We're in the upload phase while our state is State::opened.
   return mState == State::opened;
 }
 
 NS_IMETHODIMP
@@ -1647,27 +1639,23 @@ XMLHttpRequestMainThread::StreamReaderFu
   if (!xmlHttpRequest || !writeCount) {
     NS_WARNING("XMLHttpRequest cannot read from stream: no closure or writeCount");
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv = NS_OK;
 
   if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Blob) {
-    if (!xmlHttpRequest->mDOMBlob) {
-      xmlHttpRequest->MaybeCreateBlobStorage();
-      rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
-    }
+    xmlHttpRequest->MaybeCreateBlobStorage();
+    rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
   } else if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Moz_blob) {
-    if (!xmlHttpRequest->mDOMBlob) {
-      if (!xmlHttpRequest->mBlobSet) {
-        xmlHttpRequest->mBlobSet = new BlobSet();
-      }
-      rv = xmlHttpRequest->mBlobSet->AppendVoidPtr(fromRawSegment, count);
+    if (!xmlHttpRequest->mBlobSet) {
+      xmlHttpRequest->mBlobSet = new BlobSet();
     }
+    rv = xmlHttpRequest->mBlobSet->AppendVoidPtr(fromRawSegment, count);
     // Clear the cache so that the blob size is updated.
     xmlHttpRequest->mResponseBlob = nullptr;
   } else if ((xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
               !xmlHttpRequest->mIsMappedArrayBuffer) ||
              xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
     // get the initial capacity to something reasonable to avoid a bunch of reallocs right
     // at the start
     if (xmlHttpRequest->mArrayBufferBuilder.capacity() == 0)
@@ -1718,78 +1706,172 @@ XMLHttpRequestMainThread::StreamReaderFu
     *writeCount = count;
   } else {
     *writeCount = 0;
   }
 
   return rv;
 }
 
-bool XMLHttpRequestMainThread::CreateDOMBlob(nsIRequest *request)
+namespace {
+
+nsresult
+GetLocalFileFromChannel(nsIRequest* aRequest, nsIFile** aFile)
 {
+  MOZ_ASSERT(aRequest);
+  MOZ_ASSERT(aFile);
+
+  *aFile = nullptr;
+
+  nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
+  if (!fc) {
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIFile> file;
-  nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(request);
-  if (fc) {
-    fc->GetFile(getter_AddRefs(file));
-  }
-
-  if (!file)
-    return false;
-
-  nsAutoCString contentType;
-  mChannel->GetContentType(contentType);
-
-  mDOMBlob = File::CreateFromFile(GetOwner(), file, EmptyString(),
-                                  NS_ConvertASCIItoUTF16(contentType));
-
+  nsresult rv = fc->GetFile(getter_AddRefs(file));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  file.forget(aFile);
+  return NS_OK;
+}
+
+nsresult
+DummyStreamReaderFunc(nsIInputStream* aInputStream,
+                      void* aClosure,
+                      const char* aFromRawSegment,
+                      uint32_t aToOffset,
+                      uint32_t aCount,
+                      uint32_t* aWriteCount)
+{
+  *aWriteCount = aCount;
+  return NS_OK;
+}
+
+class FileCreationHandler final : public PromiseNativeHandler
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  static void
+  Create(Promise* aPromise, XMLHttpRequestMainThread* aXHR)
+  {
+    MOZ_ASSERT(aPromise);
+
+    RefPtr<FileCreationHandler> handler = new FileCreationHandler(aXHR);
+    aPromise->AppendNativeHandler(handler);
+  }
+
+  void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    if (NS_WARN_IF(!aValue.isObject())) {
+      mXHR->LocalFileToBlobCompleted(nullptr);
+      return;
+    }
+
+    RefPtr<Blob> blob;
+    if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
+      mXHR->LocalFileToBlobCompleted(nullptr);
+      return;
+    }
+
+    mXHR->LocalFileToBlobCompleted(blob);
+  }
+
+  void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    mXHR->LocalFileToBlobCompleted(nullptr);
+  }
+
+private:
+  explicit FileCreationHandler(XMLHttpRequestMainThread* aXHR)
+    : mXHR(aXHR)
+  {
+    MOZ_ASSERT(aXHR);
+  }
+
+  ~FileCreationHandler() = default;
+
+  RefPtr<XMLHttpRequestMainThread> mXHR;
+};
+
+NS_IMPL_ISUPPORTS0(FileCreationHandler)
+
+} // namespace
+
+void
+XMLHttpRequestMainThread::LocalFileToBlobCompleted(Blob* aBlob)
+{
+  MOZ_ASSERT(mState != State::done);
+
+  mResponseBlob = aBlob;
   mBlobStorage = nullptr;
   mBlobSet = nullptr;
   NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
-  return true;
+
+  ChangeStateToDone();
 }
 
 NS_IMETHODIMP
 XMLHttpRequestMainThread::OnDataAvailable(nsIRequest *request,
                                           nsISupports *ctxt,
                                           nsIInputStream *inStr,
                                           uint64_t sourceOffset,
                                           uint32_t count)
 {
   NS_ENSURE_ARG_POINTER(inStr);
 
   MOZ_ASSERT(mContext.get() == ctxt,"start context different from OnDataAvailable context");
 
   mProgressSinceLastProgressEvent = true;
   XMLHttpRequestBinding::ClearCachedResponseTextValue(this);
 
-  bool cancelable = false;
+  nsresult rv;
+
+  nsCOMPtr<nsIFile> localFile;
   if ((mResponseType == XMLHttpRequestResponseType::Blob ||
-       mResponseType == XMLHttpRequestResponseType::Moz_blob) && !mDOMBlob) {
-    cancelable = CreateDOMBlob(request);
-    // The nsIStreamListener contract mandates us
-    // to read from the stream before returning.
+       mResponseType == XMLHttpRequestResponseType::Moz_blob)) {
+    rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (localFile) {
+      mBlobStorage = nullptr;
+      mBlobSet = nullptr;
+      NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
+
+      // We don't have to read from the local file for the blob response
+      int64_t fileSize;
+      rv = localFile->GetFileSize(&fileSize);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      mDataAvailable = fileSize;
+
+      // The nsIStreamListener contract mandates us to read from the stream
+      // before returning.
+      uint32_t totalRead;
+      rv =
+        inStr->ReadSegments(DummyStreamReaderFunc, nullptr, count, &totalRead);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      ChangeState(State::loading);
+      return request->Cancel(NS_OK);
+    }
   }
 
   uint32_t totalRead;
-  nsresult rv = inStr->ReadSegments(XMLHttpRequestMainThread::StreamReaderFunc,
-                                    (void*)this, count, &totalRead);
+  rv = inStr->ReadSegments(XMLHttpRequestMainThread::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
-    ErrorResult error;
-    mDataAvailable = mDOMBlob->GetSize(error);
-    if (NS_WARN_IF(error.Failed())) {
-      return error.StealNSResult();
-    }
-
-    ChangeState(State::loading);
-    return request->Cancel(NS_OK);
-  }
-
   mDataAvailable += totalRead;
 
   // Fire the first progress event/loading state change
   if (mState != State::loading) {
     ChangeState(State::loading);
     if (!mFlagSynchronous) {
       DispatchProgressEvent(this, ProgressEventType::progress,
                             mLoadTransferred, mLoadTotal);
@@ -2101,24 +2183,46 @@ XMLHttpRequestMainThread::OnStopRequest(
   mXMLParserStreamListener = nullptr;
   mContext = nullptr;
 
   bool waitingForBlobCreation = false;
 
   if (NS_SUCCEEDED(status) &&
       (mResponseType == XMLHttpRequestResponseType::Blob ||
        mResponseType == XMLHttpRequestResponseType::Moz_blob)) {
-    ErrorResult rv;
-    if (!mDOMBlob) {
-      CreateDOMBlob(request);
+    nsCOMPtr<nsIFile> file;
+    nsresult rv = GetLocalFileFromChannel(request, getter_AddRefs(file));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
     }
-    if (mDOMBlob) {
-      mResponseBlob = mDOMBlob;
-      mDOMBlob = nullptr;
+
+    if (file) {
+      nsAutoCString contentType;
+      rv = mChannel->GetContentType(contentType);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      ChromeFilePropertyBag bag;
+      bag.mType = NS_ConvertUTF8toUTF16(contentType);
+
+      nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+
+      ErrorResult error;
+      RefPtr<Promise> promise =
+        FileCreatorHelper::CreateFile(global, file, bag, true, error);
+      if (NS_WARN_IF(error.Failed())) {
+        return error.StealNSResult();
+      }
+
+      FileCreationHandler::Create(promise, this);
+      waitingForBlobCreation = true;
     } else {
+      // No local file.
+
       // Smaller files may be written in cache map instead of separate files.
       // Also, no-store response cannot be written in persistent cache.
       nsAutoCString contentType;
       mChannel->GetContentType(contentType);
 
       if (mResponseType == XMLHttpRequestResponseType::Blob) {
         // mBlobStorage can be null if the channel is non-file non-cacheable
         // and if the response length is zero.
@@ -2127,25 +2231,26 @@ XMLHttpRequestMainThread::OnStopRequest(
         waitingForBlobCreation = true;
       } else {
         // mBlobSet can be null if the channel is non-file non-cacheable
         // and if the response length is zero.
         if (!mBlobSet) {
           mBlobSet = new BlobSet();
         }
 
+        ErrorResult error;
         nsTArray<RefPtr<BlobImpl>> subImpls(mBlobSet->GetBlobImpls());
         RefPtr<BlobImpl> blobImpl =
           MultipartBlobImpl::Create(Move(subImpls),
                                     NS_ConvertASCIItoUTF16(contentType),
-                                    rv);
+                                    error);
         mBlobSet = nullptr;
 
-        if (NS_WARN_IF(rv.Failed())) {
-          return rv.StealNSResult();
+        if (NS_WARN_IF(error.Failed())) {
+          return error.StealNSResult();
         }
 
         mResponseBlob = Blob::Create(GetOwner(), blobImpl);
       }
     }
 
     NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
     NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -525,16 +525,19 @@ public:
 
   virtual void
   SetOriginAttributes(const mozilla::dom::OriginAttributesDictionary& aAttrs) override;
 
   void BlobStoreCompleted(MutableBlobStorage* aBlobStorage,
                           Blob* aBlob,
                           nsresult aResult) override;
 
+  void
+  LocalFileToBlobCompleted(Blob* aBlob);
+
 protected:
   // XHR states are meant to mirror the XHR2 spec:
   //   https://xhr.spec.whatwg.org/#states
   enum class State : uint8_t {
     unsent,           // object has been constructed.
     opened,           // open() has been successfully invoked.
     headers_received, // redirects followed and response headers received.
     loading,          // response body is being received.
@@ -546,17 +549,16 @@ protected:
   static nsresult StreamReaderFunc(nsIInputStream* in,
                                    void* closure,
                                    const char* fromRawSegment,
                                    uint32_t toOffset,
                                    uint32_t count,
                                    uint32_t *writeCount);
   nsresult CreateResponseParsedJSON(JSContext* aCx);
   void CreatePartialBlob(ErrorResult& aRv);
-  bool CreateDOMBlob(nsIRequest *request);
   // Change the state of the object with this. The broadcast argument
   // determines if the onreadystatechange listener should be called.
   nsresult ChangeState(State aState, bool aBroadcast = true);
   already_AddRefed<nsILoadGroup> GetLoadGroup() const;
   nsIURI *GetBaseURI();
 
   already_AddRefed<nsIHttpChannel> GetCurrentHttpChannel();
   already_AddRefed<nsIJARChannel> GetCurrentJARChannel();
@@ -634,24 +636,19 @@ protected:
 
   void MatchCharsetAndDecoderToResponseDocument();
 
   XMLHttpRequestResponseType mResponseType;
 
   // It is either a cached blob-response from the last call to GetResponse,
   // but is also explicitly set in OnStopRequest.
   RefPtr<Blob> mResponseBlob;
-  // Non-null only when we are able to get a os-file representation of the
-  // response, i.e. when loading from a file.
-  RefPtr<Blob> mDOMBlob;
-  // We stream data to mBlobStorage when response type is "blob" and mDOMBlob is
-  // null.
+  // We stream data to mBlobStorage when response type is "blob".
   RefPtr<MutableBlobStorage> mBlobStorage;
-  // We stream data to mBlobStorage when response type is "moz-blob" and
-  // mDOMBlob is null.
+  // We stream data to mBlobSet when response type is "moz-blob".
   nsAutoPtr<BlobSet> mBlobSet;
 
   nsString mOverrideMimeType;
 
   /**
    * The notification callbacks the channel had when Send() was
    * called.  We want to forward things here as needed.
    */
--- a/dom/xhr/moz.build
+++ b/dom/xhr/moz.build
@@ -34,11 +34,13 @@ LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/file',
     '/dom/workers',
     '/netwerk/base',
 ]
 
 MOCHITEST_MANIFESTS += [ 'tests/mochitest.ini' ]
 
+BROWSER_CHROME_MANIFESTS += [ 'tests/browser.ini' ]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/dom/xhr/tests/browser.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files =
+
+[browser_blobFromFile.js]
new file mode 100644
--- /dev/null
+++ b/dom/xhr/tests/browser_blobFromFile.js
@@ -0,0 +1,41 @@
+let { classes: Cc, interfaces: Ci } = Components;
+
+add_task(function* test() {
+  let file = Cc["@mozilla.org/file/directory_service;1"]
+               .getService(Ci.nsIDirectoryService)
+               .QueryInterface(Ci.nsIProperties)
+               .get("ProfD", Ci.nsIFile);
+
+  let fileHandler = Cc["@mozilla.org/network/io-service;1"]
+                      .getService(Ci.nsIIOService)
+                      .getProtocolHandler("file")
+                      .QueryInterface(Ci.nsIFileProtocolHandler);
+
+  let fileURL = fileHandler.getURLSpecFromFile(file);
+
+  info("Opening url: " + fileURL);
+  let tab = gBrowser.addTab(fileURL);
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  let blob = yield ContentTask.spawn(browser, null, function() {
+    return new content.window.Promise(resolve => {
+      let xhr = new content.window.XMLHttpRequest();
+      xhr.responseType = "blob";
+      xhr.open("GET", "prefs.js");
+      xhr.send();
+      xhr.onload = function() {
+        resolve(xhr.response);
+      }
+    });
+  });
+
+  ok(blob instanceof File, "We have a file");
+
+  file.append("prefs.js");
+  is(blob.size, file.fileSize, "The size matches");
+  is(blob.name, "prefs.js", "The name is correct");
+
+  gBrowser.removeTab(tab);
+});