Bug 1491504 - shortcut blob responses from XHR and fetch when the URL is a blob URL; r=baku
☠☠ backed out by e936e01a2949 ☠ ☠
authorThomas Wisniewski <twisniewski@mozilla.com>
Fri, 05 Oct 2018 20:23:55 +0000
changeset 488218 c5ac0cb2523842abe2fddcf109f78957a09e9cf3
parent 488217 8881fb30547516dfe66827b37c4ee32a5af39b2d
child 488219 e936e01a2949eea1c8d06dc3485b09e531fd151a
push id246
push userfmarier@mozilla.com
push dateSat, 13 Oct 2018 00:15:40 +0000
reviewersbaku
bugs1491504
milestone64.0a1
Bug 1491504 - shortcut blob responses from XHR and fetch when the URL is a blob URL; r=baku shortcut blob responses from XHR and fetch when the URL is a blob URL Differential Revision: https://phabricator.services.mozilla.com/D7253
dom/fetch/Fetch.cpp
dom/fetch/Fetch.h
dom/fetch/FetchConsumer.cpp
dom/fetch/FetchConsumer.h
dom/fetch/FetchDriver.cpp
dom/fetch/InternalRequest.h
dom/fetch/InternalResponse.h
dom/fetch/Request.h
dom/fetch/Response.h
dom/xhr/XMLHttpRequestMainThread.cpp
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -1248,16 +1248,31 @@ template
 void
 FetchBody<Request>::SetMimeType();
 
 template
 void
 FetchBody<Response>::SetMimeType();
 
 template <class Derived>
+const nsACString&
+FetchBody<Derived>::BodyBlobURISpec() const
+{
+  return DerivedClass()->BodyBlobURISpec();
+}
+
+template
+const nsACString&
+FetchBody<Request>::BodyBlobURISpec() const;
+
+template
+const nsACString&
+FetchBody<Response>::BodyBlobURISpec() const;
+
+template <class Derived>
 const nsAString&
 FetchBody<Derived>::BodyLocalPath() const
 {
   return DerivedClass()->BodyLocalPath();
 }
 
 template
 const nsAString&
--- a/dom/fetch/Fetch.h
+++ b/dom/fetch/Fetch.h
@@ -182,16 +182,19 @@ public:
     return ConsumeBody(aCx, CONSUME_TEXT, aRv);
   }
 
   void
   GetBody(JSContext* aCx,
           JS::MutableHandle<JSObject*> aBodyOut,
           ErrorResult& aRv);
 
+  const nsACString&
+  BodyBlobURISpec() const;
+
   const nsAString&
   BodyLocalPath() const;
 
   // If the body contains a ReadableStream body object, this method produces a
   // tee() of it.
   void
   MaybeTeeReadableStreamBody(JSContext* aCx,
                              JS::MutableHandle<JSObject*> aBodyOut,
--- a/dom/fetch/FetchConsumer.cpp
+++ b/dom/fetch/FetchConsumer.cpp
@@ -429,16 +429,17 @@ FetchBodyConsumer<Derived>::FetchBodyCon
                                               FetchConsumeType aType)
   : mTargetThread(NS_GetCurrentThread())
   , mMainThreadEventTarget(aMainThreadEventTarget)
 #ifdef DEBUG
   , mBody(aBody)
 #endif
   , mBodyStream(aBodyStream)
   , mBlobStorageType(MutableBlobStorage::eOnlyInMemory)
+  , mBodyBlobURISpec(aBody ? aBody->BodyBlobURISpec() : VoidCString())
   , mBodyLocalPath(aBody ? aBody->BodyLocalPath() : VoidString())
   , mGlobal(aGlobalObject)
   , mConsumeType(aType)
   , mConsumePromise(aPromise)
   , mBodyConsumed(false)
   , mShuttingDown(false)
 {
   MOZ_ASSERT(aMainThreadEventTarget);
@@ -592,21 +593,36 @@ FetchBodyConsumer<Derived>::BeginConsume
   AutoFailConsumeBody<Derived> autoReject(this, aWorkerRef);
 
   if (mShuttingDown) {
     // We haven't started yet, but we have been terminated. AutoFailConsumeBody
     // will dispatch a runnable to release resources.
     return;
   }
 
-  // If we're trying to consume a blob, and the request was for a local
-  // file, then generate and return a File blob.
   if (mConsumeType == CONSUME_BLOB) {
+    nsresult rv;
+
+    // If we're trying to consume a blob, and the request was for a blob URI,
+    // then just consume that URI's blob instance.
+    if (!mBodyBlobURISpec.IsEmpty()) {
+      RefPtr<BlobImpl> blobImpl;
+      rv = NS_GetBlobForBlobURISpec(mBodyBlobURISpec, getter_AddRefs(blobImpl));
+      if (NS_WARN_IF(NS_FAILED(rv)) || !blobImpl) {
+        return;
+      }
+      autoReject.DontFail();
+      ContinueConsumeBlobBody(blobImpl);
+      return;
+    }
+
+    // If we're trying to consume a blob, and the request was for a local
+    // file, then generate and return a File blob.
     nsCOMPtr<nsIFile> file;
-    nsresult rv = GetBodyLocalFile(getter_AddRefs(file));
+    rv = GetBodyLocalFile(getter_AddRefs(file));
     if (!NS_WARN_IF(NS_FAILED(rv)) && file) {
       ChromeFilePropertyBag bag;
       bag.mType = NS_ConvertUTF8toUTF16(mBodyMimeType);
 
       ErrorResult error;
       RefPtr<Promise> promise =
         FileCreatorHelper::CreateFile(mGlobal, file, bag, true, error);
       if (NS_WARN_IF(error.Failed())) {
--- a/dom/fetch/FetchConsumer.h
+++ b/dom/fetch/FetchConsumer.h
@@ -97,16 +97,17 @@ private:
 #endif
 
   // This is nullified when the consuming of the body starts.
   nsCOMPtr<nsIInputStream> mBodyStream;
 
   MutableBlobStorage::MutableBlobStorageType mBlobStorageType;
   nsCString mBodyMimeType;
 
+  nsCString mBodyBlobURISpec;
   nsString mBodyLocalPath;
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
 
   // Touched on the main-thread only.
   nsCOMPtr<nsIInputStreamPump> mConsumeBodyPump;
 
   // Only ever set once, always on target thread.
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -43,16 +43,41 @@
 #include "InternalRequest.h"
 #include "InternalResponse.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
+void
+GetBlobURISpecFromChannel(nsIRequest* aRequest, nsCString& aBlobURISpec)
+{
+  MOZ_ASSERT(aRequest);
+
+  aBlobURISpec.SetIsVoid(true);
+
+  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+  if (!channel) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = channel->GetURI(getter_AddRefs(uri));
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  if (!dom::IsBlobURI(uri)) {
+    return;
+  }
+
+  uri->GetSpec(aBlobURISpec);
+}
+
 bool
 ShouldCheckSRI(const InternalRequest* const aRequest,
                const InternalResponse* const aResponse)
 {
   MOZ_DIAGNOSTIC_ASSERT(aRequest);
   MOZ_DIAGNOSTIC_ASSERT(aResponse);
 
   return !aRequest->GetIntegrity().IsEmpty() &&
@@ -957,16 +982,24 @@ FetchDriver::OnStartRequest(nsIRequest* 
   if (fc) {
     nsCOMPtr<nsIFile> file;
     rv = fc->GetFile(getter_AddRefs(file));
     if (!NS_WARN_IF(NS_FAILED(rv))) {
       nsAutoString path;
       file->GetPath(path);
       response->SetBodyLocalPath(path);
     }
+  } else {
+    // If the request is a blob URI, then remember that URI so that we
+    // can later just use that blob instance instead of cloning it.
+    nsCString blobURISpec;
+    GetBlobURISpecFromChannel(aRequest, blobURISpec);
+    if (!blobURISpec.IsVoid()) {
+      response->SetBodyBlobURISpec(blobURISpec);
+    }
   }
 
   response->InitChannelInfo(channel);
 
   nsCOMPtr<nsIURI> channelURI;
   rv = channel->GetURI(getter_AddRefs(channelURI));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError(rv);
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -486,16 +486,28 @@ public:
     s.forget(aStream);
 
     if (aBodyLength) {
       *aBodyLength = mBodyLength;
     }
   }
 
   void
+  SetBodyBlobURISpec(nsACString& aBlobURISpec)
+  {
+    mBodyBlobURISpec = aBlobURISpec;
+  }
+
+  const nsACString&
+  BodyBlobURISpec() const
+  {
+    return mBodyBlobURISpec;
+  }
+
+  void
   SetBodyLocalPath(nsAString& aLocalPath)
   {
     mBodyLocalPath = aLocalPath;
   }
 
   const nsAString&
   BodyLocalPath() const
   {
@@ -603,16 +615,17 @@ private:
     mURLList.LastElement() = aURL;
     mFragment.Assign(aFragment);
   }
 
   nsCString mMethod;
   // mURLList: a list of one or more fetch URLs
   nsTArray<nsCString> mURLList;
   RefPtr<InternalHeaders> mHeaders;
+  nsCString mBodyBlobURISpec;
   nsString mBodyLocalPath;
   nsCOMPtr<nsIInputStream> mBodyStream;
   int64_t mBodyLength;
 
   nsCString mPreferredAlternativeDataType;
 
   nsContentPolicyType mContentPolicyType;
 
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -215,16 +215,31 @@ public:
       }
       return;
     }
 
     GetUnfilteredBody(aStream, aBodySize);
   }
 
   void
+  SetBodyBlobURISpec(nsACString& aBlobURISpec)
+  {
+    mBodyBlobURISpec = aBlobURISpec;
+  }
+
+  const nsACString&
+  BodyBlobURISpec() const
+  {
+    if (mWrappedResponse) {
+      return mWrappedResponse->BodyBlobURISpec();
+    }
+    return mBodyBlobURISpec;
+  }
+
+  void
   SetBodyLocalPath(nsAString& aLocalPath)
   {
     mBodyLocalPath = aLocalPath;
   }
 
   const nsAString&
   BodyLocalPath() const
   {
@@ -383,16 +398,17 @@ private:
   // A response has an associated url list (a list of zero or more fetch URLs).
   // Unless stated otherwise, it is the empty list. The current url is the last
   // element in mURLlist
   nsTArray<nsCString> mURLList;
   const uint16_t mStatus;
   const nsCString mStatusText;
   RefPtr<InternalHeaders> mHeaders;
   nsCOMPtr<nsIInputStream> mBody;
+  nsCString mBodyBlobURISpec;
   nsString mBodyLocalPath;
   int64_t mBodySize;
   // It's used to passed to the CacheResponse to generate padding size. Once, we
   // generate the padding size for resposne, we don't need it anymore.
   Maybe<uint32_t> mPaddingInfo;
   int64_t mPaddingSize;
   nsresult mErrorCode;
 
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -138,16 +138,24 @@ public:
   }
 
   void
   SetBody(nsIInputStream* aStream, int64_t aBodyLength)
   {
     mRequest->SetBody(aStream, aBodyLength);
   }
 
+  using FetchBody::BodyBlobURISpec;
+
+  const nsACString&
+  BodyBlobURISpec() const
+  {
+    return mRequest->BodyBlobURISpec();
+  }
+
   using FetchBody::BodyLocalPath;
 
   const nsAString&
   BodyLocalPath() const
   {
     return mRequest->BodyLocalPath();
   }
 
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -107,16 +107,24 @@ public:
   void
   GetBody(nsIInputStream** aStream, int64_t* aBodyLength = nullptr)
   {
     mInternalResponse->GetBody(aStream, aBodyLength);
   }
 
   using FetchBody::GetBody;
 
+  using FetchBody::BodyBlobURISpec;
+
+  const nsACString&
+  BodyBlobURISpec() const
+  {
+    return mInternalResponse->BodyBlobURISpec();
+  }
+
   using FetchBody::BodyLocalPath;
 
   const nsAString&
   BodyLocalPath() const
   {
     return mInternalResponse->BodyLocalPath();
   }
 
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -8,16 +8,17 @@
 
 #include <algorithm>
 #ifndef XP_WIN
 #include <unistd.h>
 #endif
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/DOMString.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileBinding.h"
 #include "mozilla/dom/FileCreatorHelper.h"
 #include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/dom/MutableBlobStorage.h"
@@ -1180,21 +1181,23 @@ XMLHttpRequestMainThread::GetResponseHea
     // return null and terminate these steps.
     if (mState == XMLHttpRequest_Binding::UNSENT ||
         mState == XMLHttpRequest_Binding::OPENED) {
       return;
     }
 
     // Even non-http channels supply content type and content length.
     // Remember we don't leak header information from denied cross-site
-    // requests.
+    // requests. However, we handle file: and blob: URLs for blob response
+    // types by canceling them with a specific error, so we have to allow
+    // them to pass through this check.
     nsresult status;
     if (!mChannel ||
         NS_FAILED(mChannel->GetStatus(&status)) ||
-        NS_FAILED(status)) {
+        (NS_FAILED(status) && status != NS_ERROR_FILE_ALREADY_EXISTS)) {
       return;
     }
 
     // Content Type:
     if (header.LowerCaseEqualsASCII("content-type")) {
       if (NS_FAILED(mChannel->GetContentType(_retval))) {
         // Means no content type
         _retval.SetIsVoid(true);
@@ -1650,16 +1653,42 @@ XMLHttpRequestMainThread::StreamReaderFu
     *writeCount = 0;
   }
 
   return rv;
 }
 
 namespace {
 
+void
+GetBlobURIFromChannel(nsIRequest* aRequest, nsIURI** aURI)
+{
+  MOZ_ASSERT(aRequest);
+  MOZ_ASSERT(aURI);
+
+  *aURI = nullptr;
+
+  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+  if (!channel) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = channel->GetURI(getter_AddRefs(uri));
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  if (!dom::IsBlobURI(uri)) {
+    return;
+  }
+
+  uri.forget(aURI);
+}
+
 nsresult
 GetLocalFileFromChannel(nsIRequest* aRequest, nsIFile** aFile)
 {
   MOZ_ASSERT(aRequest);
   MOZ_ASSERT(aFile);
 
   *aFile = nullptr;
 
@@ -1766,24 +1795,39 @@ XMLHttpRequestMainThread::OnDataAvailabl
 
   MOZ_ASSERT(mContext.get() == ctxt,"start context different from OnDataAvailable context");
 
   mProgressSinceLastProgressEvent = true;
   XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
 
   nsresult rv;
 
-  nsCOMPtr<nsIFile> localFile;
   if (mResponseType == XMLHttpRequestResponseType::Blob) {
-    rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
+    nsCOMPtr<nsIFile> localFile;
+    nsCOMPtr<nsIURI> blobURI;
+    GetBlobURIFromChannel(request, getter_AddRefs(blobURI));
+    if (blobURI) {
+      RefPtr<BlobImpl> blobImpl;
+      rv = NS_GetBlobForBlobURI(blobURI, getter_AddRefs(blobImpl));
+      if (NS_SUCCEEDED(rv)) {
+        if (blobImpl) {
+          mResponseBlob = Blob::Create(GetOwner(), blobImpl);
+        }
+        if (!mResponseBlob) {
+          rv = NS_ERROR_FILE_NOT_FOUND;
+        }
+      }
+    } else {
+      rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
+    }
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    if (localFile) {
+    if (mResponseBlob || localFile) {
       mBlobStorage = nullptr;
       NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
 
       // The nsIStreamListener contract mandates us to read from the stream
       // before returning.
       uint32_t totalRead;
       rv =
         inStr->ReadSegments(DummyStreamReaderFunc, nullptr, count, &totalRead);
@@ -2136,22 +2180,28 @@ XMLHttpRequestMainThread::OnStopRequest(
   // If window.stop() or other aborts were issued, handle as an abort
   if (status == NS_BINDING_ABORTED) {
     mFlagParseBody = false;
     IgnoredErrorResult rv;
     RequestErrorSteps(ProgressEventType::abort, NS_OK, rv);
     return NS_OK;
   }
 
+  // If we were just reading a blob URL, we're already done
+  if (status == NS_ERROR_FILE_ALREADY_EXISTS && mResponseBlob) {
+    ChangeStateToDone();
+    return NS_OK;
+  }
+
   bool waitingForBlobCreation = false;
 
   // If we have this error, we have to deal with a file: URL + responseType =
   // blob. We have this error because we canceled the channel. The status will
   // be set to NS_OK.
-  if (status == NS_ERROR_FILE_ALREADY_EXISTS &&
+  if (!mResponseBlob && status == NS_ERROR_FILE_ALREADY_EXISTS &&
       mResponseType == XMLHttpRequestResponseType::Blob) {
     nsCOMPtr<nsIFile> file;
     nsresult rv = GetLocalFileFromChannel(request, getter_AddRefs(file));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     if (file) {