Bug 1591579 - Retrieve the XHR response values only when needed, r=smaug
☠☠ backed out by 6c358363b281 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 04 Nov 2019 13:31:14 +0000
changeset 500365 9a1cf12c3de167578445f539ac744018536fd5b4
parent 500364 4a228aa6e526336dba96a8a0cb643c4673c671f4
child 500366 acacc410970fe6ba0dec16a7b68813318e0d3d8d
push id114164
push useraiakab@mozilla.com
push dateTue, 05 Nov 2019 10:06:15 +0000
treeherdermozilla-inbound@4d585c7edc76 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1591579
milestone72.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 1591579 - Retrieve the XHR response values only when needed, r=smaug Differential Revision: https://phabricator.services.mozilla.com/D51412
dom/xhr/XMLHttpRequestMainThread.cpp
dom/xhr/XMLHttpRequestMainThread.h
dom/xhr/XMLHttpRequestWorker.cpp
dom/xhr/XMLHttpRequestWorker.h
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -680,17 +680,17 @@ void XMLHttpRequestMainThread::GetRespon
 
     case XMLHttpRequestResponseType::Arraybuffer: {
       if (mState != XMLHttpRequest_Binding::DONE) {
         aResponse.setNull();
         return;
       }
 
       if (!mResultArrayBuffer) {
-        mResultArrayBuffer = mArrayBufferBuilder->GetArrayBuffer(aCx);
+        mResultArrayBuffer = mArrayBufferBuilder->TakeArrayBuffer(aCx);
         if (!mResultArrayBuffer) {
           aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
           return;
         }
       }
       aResponse.setObject(*mResultArrayBuffer);
       return;
     }
@@ -3698,17 +3698,18 @@ nsXMLHttpRequestXPCOMifier::GetInterface
   return mXHR->GetInterface(aIID, aResult);
 }
 
 ArrayBufferBuilder::ArrayBufferBuilder()
     : mMutex("ArrayBufferBuilder"),
       mDataPtr(nullptr),
       mCapacity(0),
       mLength(0),
-      mMapPtr(nullptr) {}
+      mMapPtr(nullptr),
+      mNeutered(false) {}
 
 ArrayBufferBuilder::~ArrayBufferBuilder() {
   if (mDataPtr) {
     JS_free(nullptr, mDataPtr);
   }
 
   if (mMapPtr) {
     JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
@@ -3722,16 +3723,17 @@ ArrayBufferBuilder::~ArrayBufferBuilder(
 bool ArrayBufferBuilder::SetCapacity(uint32_t aNewCap) {
   MutexAutoLock lock(mMutex);
   return SetCapacityInternal(aNewCap, lock);
 }
 
 bool ArrayBufferBuilder::SetCapacityInternal(
     uint32_t aNewCap, const MutexAutoLock& aProofOfLock) {
   MOZ_ASSERT(!mMapPtr);
+  MOZ_ASSERT(!mNeutered);
 
   // To ensure that realloc won't free mDataPtr, use a size of 1
   // instead of 0.
   uint8_t* newdata = (uint8_t*)js_realloc(mDataPtr, aNewCap ? aNewCap : 1);
 
   if (!newdata) {
     return false;
   }
@@ -3748,16 +3750,17 @@ bool ArrayBufferBuilder::SetCapacityInte
 
   return true;
 }
 
 bool ArrayBufferBuilder::Append(const uint8_t* aNewData, uint32_t aDataLen,
                                 uint32_t aMaxGrowth) {
   MutexAutoLock lock(mMutex);
   MOZ_ASSERT(!mMapPtr);
+  MOZ_ASSERT(!mNeutered);
 
   CheckedUint32 neededCapacity = mLength;
   neededCapacity += aDataLen;
   if (!neededCapacity.isValid()) {
     return false;
   }
   if (mLength + aDataLen > mCapacity) {
     CheckedUint32 newcap = mCapacity;
@@ -3789,63 +3792,69 @@ bool ArrayBufferBuilder::Append(const ui
   memcpy(mDataPtr + mLength, aNewData, aDataLen);
   mLength += aDataLen;
 
   return true;
 }
 
 uint32_t ArrayBufferBuilder::Length() {
   MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(!mNeutered);
   return mLength;
 }
 
 uint32_t ArrayBufferBuilder::Capacity() {
   MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(!mNeutered);
   return mCapacity;
 }
 
-JSObject* ArrayBufferBuilder::GetArrayBuffer(JSContext* aCx) {
+JSObject* ArrayBufferBuilder::TakeArrayBuffer(JSContext* aCx) {
   MutexAutoLock lock(mMutex);
+  MOZ_DIAGNOSTIC_ASSERT(!mNeutered);
 
   if (mMapPtr) {
-    MOZ_ASSERT(NS_IsMainThread());
-
     JSObject* obj = JS::NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
     if (!obj) {
       JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
     }
+
     mMapPtr = nullptr;
+    mNeutered = true;
 
     // The memory-mapped contents will be released when the ArrayBuffer becomes
     // detached or is GC'd.
     return obj;
   }
 
   // we need to check for mLength == 0, because nothing may have been
   // added
   if (mCapacity > mLength || mLength == 0) {
     if (!SetCapacityInternal(mLength, lock)) {
       return nullptr;
     }
   }
 
-  JSObject* obj = JS::NewExternalArrayBuffer(
-      aCx, mLength, mDataPtr, ArrayBufferBuilder::FreeBuffer, this);
+  JSObject* obj = JS::NewArrayBufferWithContents(aCx, mLength, mDataPtr);
   if (!obj) {
     return nullptr;
   }
 
-  NS_ADDREF(this);
+  mDataPtr = nullptr;
+  mCapacity = mLength = 0;
+
+  mNeutered = true;
   return obj;
 }
 
 nsresult ArrayBufferBuilder::MapToFileInPackage(const nsCString& aFile,
                                                 nsIFile* aJarFile) {
   MutexAutoLock lock(mMutex);
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mNeutered);
 
   nsresult rv;
 
   // Open Jar file to get related attributes of target file.
   RefPtr<nsZipArchive> zip = new nsZipArchive();
   rv = zip->OpenArchive(aJarFile);
   if (NS_FAILED(rv)) {
     return rv;
@@ -3884,24 +3893,16 @@ bool ArrayBufferBuilder::AreOverlappingR
   const uint8_t* end2 = aStart2 + aLength2;
 
   const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2;
   const uint8_t* min_end = end1 < end2 ? end1 : end2;
 
   return max_start < min_end;
 }
 
-/* static */
-void ArrayBufferBuilder::FreeBuffer(void* aContents, void* aSelf) {
-  RefPtr<ArrayBufferBuilder> builder =
-      dont_AddRef(static_cast<ArrayBufferBuilder*>(aSelf));
-  // Nothing to do here. If this was the last reference, the builder will free
-  // the buffer.
-}
-
 RequestHeaders::RequestHeader* RequestHeaders::Find(const nsACString& aName) {
   const nsCaseInsensitiveCStringComparator ignoreCase;
   for (RequestHeaders::RequestHeader& header : mHeaders) {
     if (header.mName.Equals(aName, ignoreCase)) {
       return &header;
     }
   }
   return nullptr;
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -93,17 +93,17 @@ class ArrayBufferBuilder {
   // The data parameter must not overlap with anything beyond the
   // builder's current valid contents [0..length)
   bool Append(const uint8_t* aNewData, uint32_t aDataLen,
               uint32_t aMaxGrowth = 0);
 
   uint32_t Length();
   uint32_t Capacity();
 
-  JSObject* GetArrayBuffer(JSContext* aCx);
+  JSObject* TakeArrayBuffer(JSContext* aCx);
 
   // Memory mapping to starting position of file(aFile) in the zip
   // package(aJarFile).
   //
   // The file in the zip package has to be uncompressed and the starting
   // position of the file must be aligned according to array buffer settings
   // in JS engine.
   nsresult MapToFileInPackage(const nsCString& aFile, nsIFile* aJarFile);
@@ -115,25 +115,26 @@ class ArrayBufferBuilder {
   ArrayBufferBuilder& operator=(const ArrayBufferBuilder&) = delete;
   ArrayBufferBuilder& operator=(const ArrayBufferBuilder&&) = delete;
 
   bool SetCapacityInternal(uint32_t aNewCap, const MutexAutoLock& aProofOfLock);
 
   static bool AreOverlappingRegions(const uint8_t* aStart1, uint32_t aLength1,
                                     const uint8_t* aStart2, uint32_t aLength2);
 
-  static void FreeBuffer(void* aContents, void* aSelf);
-
   Mutex mMutex;
 
   // All of these are protected by mMutex.
   uint8_t* mDataPtr;
   uint32_t mCapacity;
   uint32_t mLength;
   void* mMapPtr;
+
+  // This is used in assertions only.
+  bool mNeutered;
 };
 
 class nsXMLHttpRequestXPCOMifier;
 
 class RequestHeaders {
   struct RequestHeader {
     nsCString mName;
     nsCString mValue;
--- a/dom/xhr/XMLHttpRequestWorker.cpp
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -39,48 +39,31 @@
 
 #include "XMLHttpRequestUpload.h"
 
 #include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 namespace dom {
 
-void XMLHttpRequestWorker::StateData::Unlink() {
-  MOZ_DIAGNOSTIC_ASSERT(mResponseData);
-  mResponseData->Unlink();
-}
-
 void XMLHttpRequestWorker::ResponseData::Unlink() {
   mResponseBlobImpl = nullptr;
   mResponseBlob = nullptr;
   mResponseArrayBufferBuilder = nullptr;
   mResponseArrayBufferValue = nullptr;
   mResponseJSONValue.setUndefined();
 }
 
-void XMLHttpRequestWorker::StateData::Trace(const TraceCallbacks& aCallbacks,
-                                            void* aClosure) {
-  MOZ_DIAGNOSTIC_ASSERT(mResponseData);
-  mResponseData->Trace(aCallbacks, aClosure);
-}
-
 void XMLHttpRequestWorker::ResponseData::Trace(const TraceCallbacks& aCallbacks,
                                                void* aClosure) {
   ResponseData* tmp = this;
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResponseArrayBufferValue)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResponseJSONValue)
 }
 
-void XMLHttpRequestWorker::StateData::Traverse(
-    nsCycleCollectionTraversalCallback& cb) {
-  MOZ_DIAGNOSTIC_ASSERT(mResponseData);
-  mResponseData->Traverse(cb);
-}
-
 void XMLHttpRequestWorker::ResponseData::Traverse(
     nsCycleCollectionTraversalCallback& cb) {
   ResponseData* tmp = this;
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
 }
 
 /**
  *  XMLHttpRequest in workers
@@ -1037,47 +1020,51 @@ bool EventRunnable::PreDispatch(WorkerPr
   JS::Rooted<JSObject*> scopeObj(cx, mScopeObj);
   // And reset mScopeObj now, before we have a chance to run its destructor on
   // some background thread.
   mScopeObj.reset();
 
   RefPtr<XMLHttpRequestMainThread>& xhr = mProxy->mXHR;
   MOZ_ASSERT(xhr);
 
-  mResponseData->mResponseType = xhr->ResponseType();
+  ErrorResult rv;
+
+  XMLHttpRequestResponseType type = xhr->ResponseType();
 
-  ErrorResult rv;
-  switch (mResponseData->mResponseType) {
-    case XMLHttpRequestResponseType::_empty:
-    case XMLHttpRequestResponseType::Text: {
-      xhr->GetResponseText(mResponseData->mResponseText, rv);
-      mResponseData->mResponseResult = rv.StealNSResult();
-      break;
-    }
-
-    case XMLHttpRequestResponseType::Blob: {
-      mResponseData->mResponseBlobImpl = xhr->GetResponseBlobImpl();
-      break;
-    }
+  // We want to take the result data only if this is available.
+  if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) {
+    switch (type) {
+      case XMLHttpRequestResponseType::_empty:
+      case XMLHttpRequestResponseType::Text: {
+        xhr->GetResponseText(mResponseData->mResponseText, rv);
+        mResponseData->mResponseResult = rv.StealNSResult();
+        break;
+      }
 
-    case XMLHttpRequestResponseType::Arraybuffer: {
-      mResponseData->mResponseArrayBufferBuilder =
-          xhr->GetResponseArrayBufferBuilder();
-      break;
-    }
+      case XMLHttpRequestResponseType::Blob: {
+        mResponseData->mResponseBlobImpl = xhr->GetResponseBlobImpl();
+        break;
+      }
+
+      case XMLHttpRequestResponseType::Arraybuffer: {
+        mResponseData->mResponseArrayBufferBuilder =
+            xhr->GetResponseArrayBufferBuilder();
+        break;
+      }
 
-    case XMLHttpRequestResponseType::Json: {
-      mResponseData->mResponseResult =
-          xhr->GetResponseTextForJSON(mResponseData->mResponseJSON);
-      break;
+      case XMLHttpRequestResponseType::Json: {
+        mResponseData->mResponseResult =
+            xhr->GetResponseTextForJSON(mResponseData->mResponseJSON);
+        break;
+      }
+
+      default:
+        MOZ_CRASH("Invalid response type");
+        break;
     }
-
-    default:
-      MOZ_CRASH("Invalid response type");
-      break;
   }
 
   mStatus = xhr->GetStatus(rv);
   mStatusResult = rv.StealNSResult();
 
   xhr->GetStatusText(mStatusText, rv);
   MOZ_ASSERT(!rv.Failed());
 
@@ -1134,29 +1121,30 @@ bool EventRunnable::WorkerRun(JSContext*
       mProxy->mLastLoaded = mLoaded;
       mProxy->mLastTotal = mTotal;
     }
   }
 
   UniquePtr<XMLHttpRequestWorker::StateData> state(
       new XMLHttpRequestWorker::StateData());
 
-  state->mResponseData = std::move(mResponseData);
-
   state->mStatusResult = mStatusResult;
   state->mStatus = mStatus;
 
   state->mStatusText = mStatusText;
 
   state->mReadyState = mReadyState;
 
   state->mResponseURL = mResponseURL;
 
   XMLHttpRequestWorker* xhr = mProxy->mXMLHttpRequestPrivate;
-  xhr->UpdateState(std::move(state));
+  xhr->UpdateState(std::move(state),
+                   mType.EqualsASCII(sEventStrings[STRING_readystatechange])
+                       ? std::move(mResponseData)
+                       : nullptr);
 
   if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) {
     return true;
   }
 
   XMLHttpRequestEventTarget* target;
   if (mUploadEvent) {
     target = xhr->GetUploadObjectNoCreate();
@@ -1388,16 +1376,17 @@ void SendRunnable::RunOnMainThread(Error
       }
     }
   }
 }
 
 XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate)
     : mWorkerPrivate(aWorkerPrivate),
       mResponseType(XMLHttpRequestResponseType::_empty),
+      mResponseData(new ResponseData()),
       mStateData(new StateData()),
       mTimeout(0),
       mBackgroundRequest(false),
       mWithCredentials(false),
       mCanceled(false),
       mMozAnon(false),
       mMozSystem(false),
       mMimeTypeOverride(VoidString()) {
@@ -1422,29 +1411,29 @@ NS_IMPL_RELEASE_INHERITED(XMLHttpRequest
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XMLHttpRequestWorker)
 NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestWorker)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestWorker,
                                                   XMLHttpRequestEventTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
-  tmp->mStateData->Traverse(cb);
+  tmp->mResponseData->Traverse(cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestWorker,
                                                 XMLHttpRequestEventTarget)
   tmp->ReleaseProxy(XHRIsGoingAway);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
-  tmp->mStateData->Unlink();
+  tmp->mResponseData->Unlink();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestWorker,
                                                XMLHttpRequestEventTarget)
-  tmp->mStateData->Trace(aCallbacks, aClosure);
+  tmp->mResponseData->Trace(aCallbacks, aClosure);
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 /* static */
 already_AddRefed<XMLHttpRequest> XMLHttpRequestWorker::Construct(
     const GlobalObject& aGlobal, const MozXMLHttpRequestParameters& aParams,
     ErrorResult& aRv) {
   JSContext* cx = aGlobal.Context();
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
@@ -2161,135 +2150,137 @@ void XMLHttpRequestWorker::SetResponseTy
   }
 
   mResponseType = runnable->ResponseType();
 }
 
 void XMLHttpRequestWorker::GetResponse(JSContext* aCx,
                                        JS::MutableHandle<JS::Value> aResponse,
                                        ErrorResult& aRv) {
-  if (NS_FAILED(mStateData->mResponseData->mResponseResult)) {
-    aRv.Throw(mStateData->mResponseData->mResponseResult);
+  if (NS_FAILED(mResponseData->mResponseResult)) {
+    aRv.Throw(mResponseData->mResponseResult);
     return;
   }
 
-  switch (mStateData->mResponseData->mResponseType) {
+  switch (mResponseType) {
     case XMLHttpRequestResponseType::_empty:
     case XMLHttpRequestResponseType::Text: {
       JSString* str;
 
-      if (mStateData->mResponseData->mResponseText.IsEmpty()) {
+      if (mResponseData->mResponseText.IsEmpty()) {
         aResponse.set(JS_GetEmptyStringValue(aCx));
         return;
       }
 
       XMLHttpRequestStringSnapshotReaderHelper helper(
-          mStateData->mResponseData->mResponseText);
+          mResponseData->mResponseText);
 
       str = JS_NewUCStringCopyN(aCx, helper.Buffer(), helper.Length());
       if (!str) {
         aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
         return;
       }
 
       aResponse.setString(str);
       return;
     }
 
     case XMLHttpRequestResponseType::Arraybuffer: {
-      if (!mStateData->mResponseData->mResponseArrayBufferBuilder) {
+      if (!mResponseData->mResponseArrayBufferBuilder) {
         aResponse.setNull();
         return;
       }
 
-      if (!mStateData->mResponseData->mResponseArrayBufferValue) {
-        mStateData->mResponseData->mResponseArrayBufferValue =
-            mStateData->mResponseData->mResponseArrayBufferBuilder
-                ->GetArrayBuffer(aCx);
-        if (!mStateData->mResponseData->mResponseArrayBufferValue) {
+      if (!mResponseData->mResponseArrayBufferValue) {
+        mResponseData->mResponseArrayBufferValue =
+            mResponseData->mResponseArrayBufferBuilder->TakeArrayBuffer(aCx);
+        if (!mResponseData->mResponseArrayBufferValue) {
           aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
           return;
         }
       }
 
-      aResponse.setObject(
-          *mStateData->mResponseData->mResponseArrayBufferValue);
+      aResponse.setObject(*mResponseData->mResponseArrayBufferValue);
       return;
     }
 
     case XMLHttpRequestResponseType::Blob: {
-      if (!mStateData->mResponseData->mResponseBlobImpl) {
+      if (!mResponseData->mResponseBlobImpl) {
         aResponse.setNull();
         return;
       }
 
-      if (!mStateData->mResponseData->mResponseBlob) {
-        mStateData->mResponseData->mResponseBlob = Blob::Create(
-            GetOwnerGlobal(), mStateData->mResponseData->mResponseBlobImpl);
+      if (!mResponseData->mResponseBlob) {
+        mResponseData->mResponseBlob =
+            Blob::Create(GetOwnerGlobal(), mResponseData->mResponseBlobImpl);
       }
 
-      if (!GetOrCreateDOMReflector(
-              aCx, mStateData->mResponseData->mResponseBlob, aResponse)) {
+      if (!GetOrCreateDOMReflector(aCx, mResponseData->mResponseBlob,
+                                   aResponse)) {
         aResponse.setNull();
       }
 
       return;
     }
 
     case XMLHttpRequestResponseType::Json: {
-      if (mStateData->mResponseData->mResponseJSON.IsVoid()) {
+      if (mResponseData->mResponseJSON.IsVoid()) {
         aResponse.setNull();
         return;
       }
 
-      if (mStateData->mResponseData->mResponseJSONValue.isUndefined()) {
+      if (mResponseData->mResponseJSONValue.isUndefined()) {
         // The Unicode converter has already zapped the BOM if there was one
         JS::Rooted<JS::Value> value(aCx);
-        if (!JS_ParseJSON(
-                aCx, mStateData->mResponseData->mResponseJSON.BeginReading(),
-                mStateData->mResponseData->mResponseJSON.Length(), &value)) {
+        if (!JS_ParseJSON(aCx, mResponseData->mResponseJSON.BeginReading(),
+                          mResponseData->mResponseJSON.Length(), &value)) {
           JS_ClearPendingException(aCx);
-          mStateData->mResponseData->mResponseJSONValue.setNull();
+          mResponseData->mResponseJSONValue.setNull();
         } else {
-          mStateData->mResponseData->mResponseJSONValue = value;
+          mResponseData->mResponseJSONValue = value;
         }
 
-        mStateData->mResponseData->mResponseJSON.Truncate();
+        mResponseData->mResponseJSON.Truncate();
       }
 
-      aResponse.set(mStateData->mResponseData->mResponseJSONValue);
+      aResponse.set(mResponseData->mResponseJSONValue);
       return;
     }
 
     default:
       MOZ_CRASH("Invalid type");
       aResponse.setNull();
       return;
   }
 }
 
 void XMLHttpRequestWorker::GetResponseText(DOMString& aResponseText,
                                            ErrorResult& aRv) {
-  MOZ_DIAGNOSTIC_ASSERT(mStateData->mResponseData);
+  MOZ_DIAGNOSTIC_ASSERT(mResponseData);
 
-  if (mStateData->mResponseData->mResponseType !=
-          XMLHttpRequestResponseType::_empty &&
-      mStateData->mResponseData->mResponseType !=
-          XMLHttpRequestResponseType::Text) {
+  if (mResponseType != XMLHttpRequestResponseType::_empty &&
+      mResponseType != XMLHttpRequestResponseType::Text) {
     aRv.Throw(
         NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSETEXT);
     return;
   }
 
-  if (!mStateData->mResponseData->mResponseText.GetAsString(aResponseText)) {
+  if (!mResponseData->mResponseText.GetAsString(aResponseText)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 }
 
-void XMLHttpRequestWorker::UpdateState(UniquePtr<StateData>&& aStateData) {
-  mStateData->Unlink();
+void XMLHttpRequestWorker::UpdateState(
+    UniquePtr<StateData>&& aStateData,
+    UniquePtr<ResponseData>&& aResponseData) {
   mStateData = std::move(aStateData);
+
+  UniquePtr<ResponseData> responseData = std::move(aResponseData);
+  if (responseData) {
+    mResponseData = std::move(responseData);
+  }
+
   XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/xhr/XMLHttpRequestWorker.h
+++ b/dom/xhr/XMLHttpRequestWorker.h
@@ -19,17 +19,16 @@ class DOMString;
 class SendRunnable;
 class StrongWorkerRef;
 class WorkerPrivate;
 
 class XMLHttpRequestWorker final : public XMLHttpRequest {
  public:
   // This defines the xhr.response value.
   struct ResponseData {
-    XMLHttpRequestResponseType mResponseType;
     nsresult mResponseResult;
 
     // responseType is empty or text.
     XMLHttpRequestStringSnapshot mResponseText;
 
     // responseType is blob
     RefPtr<BlobImpl> mResponseBlobImpl;
     RefPtr<Blob> mResponseBlob;
@@ -38,54 +37,46 @@ class XMLHttpRequestWorker final : publi
     RefPtr<ArrayBufferBuilder> mResponseArrayBufferBuilder;
     JS::Heap<JSObject*> mResponseArrayBufferValue;
 
     // responseType is json
     nsString mResponseJSON;
     JS::Heap<JS::Value> mResponseJSONValue;
 
     ResponseData()
-        : mResponseType(XMLHttpRequestResponseType::_empty),
-          mResponseResult(NS_OK),
+        : mResponseResult(NS_OK),
           mResponseArrayBufferValue(nullptr),
           mResponseJSONValue(JS::UndefinedValue()) {}
 
     void Unlink();
     void Trace(const TraceCallbacks& aCallbacks, void* aClosure);
     void Traverse(nsCycleCollectionTraversalCallback& aCb);
   };
 
   struct StateData {
-    UniquePtr<ResponseData> mResponseData;
     nsString mResponseURL;
     uint32_t mStatus;
     nsCString mStatusText;
     uint16_t mReadyState;
     bool mFlagSend;
     nsresult mStatusResult;
 
     StateData()
-        : mResponseData(new ResponseData()),
-          mStatus(0),
-          mReadyState(0),
-          mFlagSend(false),
-          mStatusResult(NS_OK) {}
-
-    void Unlink();
-    void Trace(const TraceCallbacks& aCallbacks, void* aClosure);
-    void Traverse(nsCycleCollectionTraversalCallback& aCb);
+        : mStatus(0), mReadyState(0), mFlagSend(false), mStatusResult(NS_OK) {}
   };
 
  private:
   RefPtr<XMLHttpRequestUpload> mUpload;
   WorkerPrivate* mWorkerPrivate;
   RefPtr<StrongWorkerRef> mWorkerRef;
   RefPtr<Proxy> mProxy;
+
   XMLHttpRequestResponseType mResponseType;
 
+  UniquePtr<ResponseData> mResponseData;
   UniquePtr<StateData> mStateData;
 
   uint32_t mTimeout;
 
   bool mBackgroundRequest;
   bool mWithCredentials;
   bool mCanceled;
 
@@ -219,17 +210,18 @@ class XMLHttpRequestWorker final : publi
 
   virtual void SetOriginAttributes(
       const mozilla::dom::OriginAttributesDictionary& aAttrs) override {
     MOZ_CRASH("This method cannot be called on workers.");
   }
 
   XMLHttpRequestUpload* GetUploadObjectNoCreate() const { return mUpload; }
 
-  void UpdateState(UniquePtr<StateData>&& aStateData);
+  void UpdateState(UniquePtr<StateData>&& aStateData,
+                   UniquePtr<ResponseData>&& aResponseData);
 
   virtual uint16_t ErrorCode() const override {
     return 0;  // eOK
   }
 
   virtual bool MozAnon() const override { return mMozAnon; }
 
   virtual bool MozSystem() const override { return mMozSystem; }