Bug 1491504 - shortcut blob responses from XHR and fetch when the URL is a blob URL; r=baku
authorThomas Wisniewski <twisniewski@mozilla.com>
Sat, 06 Oct 2018 17:34:24 +0000
changeset 495637 8248b734eb889025cc7c1bcf736cb1b75021a42b
parent 495636 48177d0caac4333b93de66fbac6ff1d96c6a893d
child 495650 07c609fc8eb89140f602ef0c838b900c2964287a
child 495667 fa8c1d18270920a5a98263c9836f4c3b240140aa
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1491504
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Fetch.h"
 #include "FetchConsumer.h"
 
 #include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileBinding.h"
 #include "mozilla/dom/FileCreatorHelper.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/WorkerCommon.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRef.h"
 #include "mozilla/dom/WorkerRunnable.h"
@@ -429,16 +430,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 +594,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) {