Bug 1285036 - Part 9: Clean up the XHR send() API endpoints, and how nsIVariants are handled. r=baku
☠☠ backed out by 5695eb78dcdc ☠ ☠
authorThomas Wisniewski <wisniewskit@gmail.com>
Wed, 20 Jul 2016 13:02:54 -0400
changeset 331063 8a00db57d77ab0ac59a1707c87da8232eeae2699
parent 331062 abbef296a82f16186245cae60fcaebd7d2325ffe
child 331064 323b479bd91f83549f5cc8949630b4aae8dcb247
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1285036
milestone50.0a1
Bug 1285036 - Part 9: Clean up the XHR send() API endpoints, and how nsIVariants are handled. r=baku
dom/xhr/XMLHttpRequestMainThread.cpp
dom/xhr/XMLHttpRequestMainThread.h
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -2101,16 +2101,177 @@ XMLHttpRequestMainThread::ChangeStateToD
     // By nulling out channel here we make it so that Send() can test
     // for that and throw. Also calling the various status
     // methods/members will not throw.
     // This matches what IE does.
     mChannel = nullptr;
   }
 }
 
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<nsIDocument>::GetAsStream(
+   nsIInputStream** aResult, uint64_t* aContentLength,
+   nsACString& aContentType, nsACString& aCharset) const
+{
+  nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(mBody));
+  NS_ENSURE_STATE(domdoc);
+  aCharset.AssignLiteral("UTF-8");
+
+  nsresult rv;
+  nsCOMPtr<nsIStorageStream> storStream;
+  rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIOutputStream> output;
+  rv = storStream->GetOutputStream(0, getter_AddRefs(output));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mBody->IsHTMLDocument()) {
+    aContentType.AssignLiteral("text/html");
+
+    nsString serialized;
+    if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    NS_ConvertUTF16toUTF8 utf8Serialized(serialized);
+
+    uint32_t written;
+    rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    MOZ_ASSERT(written == utf8Serialized.Length());
+  } else {
+    aContentType.AssignLiteral("application/xml");
+
+    nsCOMPtr<nsIDOMSerializer> serializer =
+      do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Make sure to use the encoding we'll send
+    rv = serializer->SerializeToStream(domdoc, output, aCharset);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  output->Close();
+
+  uint32_t length;
+  rv = storStream->GetLength(&length);
+  NS_ENSURE_SUCCESS(rv, rv);
+  *aContentLength = length;
+
+  rv = storStream->NewInputStream(0, aResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<const nsAString>::GetAsStream(
+   nsIInputStream** aResult, uint64_t* aContentLength,
+   nsACString& aContentType, nsACString& aCharset) const
+{
+  aContentType.AssignLiteral("text/plain");
+  aCharset.AssignLiteral("UTF-8");
+
+  nsCString converted = NS_ConvertUTF16toUTF8(*mBody);
+  *aContentLength = converted.Length();
+  nsresult rv = NS_NewCStringInputStream(aResult, converted);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<nsIInputStream>::GetAsStream(
+   nsIInputStream** aResult, uint64_t* aContentLength,
+   nsACString& aContentType, nsACString& aCharset) const
+{
+  aContentType.AssignLiteral("text/plain");
+  aCharset.Truncate();
+
+  nsresult rv = mBody->Available(aContentLength);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIInputStream> stream(mBody);
+  stream.forget(aResult);
+  return NS_OK;
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<Blob>::GetAsStream(
+   nsIInputStream** aResult, uint64_t* aContentLength,
+   nsACString& aContentType, nsACString& aCharset) const
+{
+  return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<FormData>::GetAsStream(
+   nsIInputStream** aResult, uint64_t* aContentLength,
+   nsACString& aContentType, nsACString& aCharset) const
+{
+  return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<URLSearchParams>::GetAsStream(
+   nsIInputStream** aResult, uint64_t* aContentLength,
+   nsACString& aContentType, nsACString& aCharset) const
+{
+  return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<nsIXHRSendable>::GetAsStream(
+   nsIInputStream** aResult, uint64_t* aContentLength,
+   nsACString& aContentType, nsACString& aCharset) const
+{
+  return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
+}
+
+static nsresult
+GetBufferDataAsStream(const uint8_t* aData, uint32_t aDataLength,
+                      nsIInputStream** aResult, uint64_t* aContentLength,
+                      nsACString& aContentType, nsACString& aCharset)
+{
+  aContentType.SetIsVoid(true);
+  aCharset.Truncate();
+
+  *aContentLength = aDataLength;
+  const char* data = reinterpret_cast<const char*>(aData);
+
+  nsCOMPtr<nsIInputStream> stream;
+  nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength,
+                                      NS_ASSIGNMENT_COPY);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  stream.forget(aResult);
+
+  return NS_OK;
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<const ArrayBuffer>::GetAsStream(
+   nsIInputStream** aResult, uint64_t* aContentLength,
+   nsACString& aContentType, nsACString& aCharset) const
+{
+  mBody->ComputeLengthAndData();
+  return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
+                               aResult, aContentLength, aContentType, aCharset);
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<const ArrayBufferView>::GetAsStream(
+   nsIInputStream** aResult, uint64_t* aContentLength,
+   nsACString& aContentType, nsACString& aCharset) const
+{
+  mBody->ComputeLengthAndData();
+  return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
+                               aResult, aContentLength, aContentType, aCharset);
+}
+
+
 nsresult
 XMLHttpRequestMainThread::InitChannel()
 {
   // When we are called from JS we can find the load group for the page,
   // and add ourselves to it. This way any pending requests
   // will be automatically aborted if the user leaves the page.
   nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
 
@@ -2176,318 +2337,98 @@ XMLHttpRequestMainThread::InitChannel()
     if (timedChannel) {
       timedChannel->SetInitiatorType(NS_LITERAL_STRING("xmlhttprequest"));
     }
   }
 
   return NS_OK;
 }
 
-static nsresult
-GetRequestBodyInternal(nsIDOMDocument* aDoc, nsIInputStream** aResult,
-                       uint64_t* aContentLength, nsACString& aContentType,
-                       nsACString& aCharset)
-{
-  nsCOMPtr<nsIDocument> doc(do_QueryInterface(aDoc));
-  NS_ENSURE_STATE(doc);
-  aCharset.AssignLiteral("UTF-8");
-
-  nsresult rv;
-  nsCOMPtr<nsIStorageStream> storStream;
-  rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIOutputStream> output;
-  rv = storStream->GetOutputStream(0, getter_AddRefs(output));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (doc->IsHTMLDocument()) {
-    aContentType.AssignLiteral("text/html");
-
-    nsString serialized;
-    if (!nsContentUtils::SerializeNodeToMarkup(doc, true, serialized)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-    NS_ConvertUTF16toUTF8 utf8Serialized(serialized);
-
-    uint32_t written;
-    rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    MOZ_ASSERT(written == utf8Serialized.Length());
-  } else {
-    aContentType.AssignLiteral("application/xml");
-
-    nsCOMPtr<nsIDOMSerializer> serializer =
-      do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Make sure to use the encoding we'll send
-    rv = serializer->SerializeToStream(aDoc, output, aCharset);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-  }
-
-  output->Close();
-
-  uint32_t length;
-  rv = storStream->GetLength(&length);
-  NS_ENSURE_SUCCESS(rv, rv);
-  *aContentLength = length;
-
-  return storStream->NewInputStream(0, aResult);
-}
-
-static nsresult
-GetRequestBodyInternal(const nsAString& aString, nsIInputStream** aResult,
-                       uint64_t* aContentLength, nsACString& aContentType,
-                       nsACString& aCharset)
+NS_IMETHODIMP
+XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
 {
-  aContentType.AssignLiteral("text/plain");
-  aCharset.AssignLiteral("UTF-8");
-
-  nsCString converted = NS_ConvertUTF16toUTF8(aString);
-  *aContentLength = converted.Length();
-  return NS_NewCStringInputStream(aResult, converted);
-}
-
-static nsresult
-GetRequestBodyInternal(nsIInputStream* aStream, nsIInputStream** aResult,
-                       uint64_t* aContentLength, nsACString& aContentType,
-                       nsACString& aCharset)
-{
-  aContentType.AssignLiteral("text/plain");
-  aCharset.Truncate();
-
-  nsresult rv = aStream->Available(aContentLength);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  NS_ADDREF(*aResult = aStream);
-
-  return NS_OK;
-}
-
-static nsresult
-GetRequestBodyInternal(URLSearchParams* aURLSearchParams,
-                       nsIInputStream** aResult, uint64_t* aContentLength,
-                       nsACString& aContentType, nsACString& aCharset)
-{
-  return aURLSearchParams->GetSendInfo(aResult, aContentLength,
-                                       aContentType, aCharset);
-}
-
-static nsresult
-GetRequestBodyInternal(nsIXHRSendable* aSendable, nsIInputStream** aResult,
-                       uint64_t* aContentLength, nsACString& aContentType,
-                       nsACString& aCharset)
-{
-  return aSendable->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
-}
-
-// Used for array buffers and array buffer views
-static nsresult
-GetRequestBodyInternal(const uint8_t* aData, uint32_t aDataLength,
-                       nsIInputStream** aResult, uint64_t* aContentLength,
-                       nsACString& aContentType, nsACString& aCharset)
-{
-  aContentType.SetIsVoid(true);
-  aCharset.Truncate();
-
-  *aContentLength = aDataLength;
-  const char* data = reinterpret_cast<const char*>(aData);
-
-  nsCOMPtr<nsIInputStream> stream;
-  nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength,
-                                      NS_ASSIGNMENT_COPY);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  stream.forget(aResult);
-
-  return NS_OK;
-}
-
-static nsresult
-GetRequestBodyInternal(nsIVariant* aBody, nsIInputStream** aResult,
-                       uint64_t* aContentLength, nsACString& aContentType,
-                       nsACString& aCharset)
-{
-  *aResult = nullptr;
+  if (!aVariant) {
+    return SendInternal(nullptr);
+  }
 
   uint16_t dataType;
-  nsresult rv = aBody->GetDataType(&dataType);
+  nsresult rv = aVariant->GetDataType(&dataType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (dataType == nsIDataType::VTYPE_INTERFACE ||
       dataType == nsIDataType::VTYPE_INTERFACE_IS) {
     nsCOMPtr<nsISupports> supports;
     nsID *iid;
-    rv = aBody->GetAsInterface(&iid, getter_AddRefs(supports));
+    rv = aVariant->GetAsInterface(&iid, getter_AddRefs(supports));
     NS_ENSURE_SUCCESS(rv, rv);
 
     free(iid);
 
     // document?
-    nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(supports);
+    nsCOMPtr<nsIDocument> doc = do_QueryInterface(supports);
     if (doc) {
-      return GetRequestBodyInternal(doc, aResult, aContentLength, aContentType,
-                                    aCharset);
+      RequestBody<nsIDocument> body(doc);
+      return SendInternal(&body);
     }
 
     // nsISupportsString?
     nsCOMPtr<nsISupportsString> wstr = do_QueryInterface(supports);
     if (wstr) {
       nsAutoString string;
       wstr->GetData(string);
-
-      return GetRequestBodyInternal(string, aResult, aContentLength,
-                                    aContentType, aCharset);
+      RequestBody<const nsAString> body(&string);
+      return SendInternal(&body);
     }
 
     // nsIInputStream?
     nsCOMPtr<nsIInputStream> stream = do_QueryInterface(supports);
     if (stream) {
-      return GetRequestBodyInternal(stream, aResult, aContentLength,
-                                    aContentType, aCharset);
+      RequestBody<nsIInputStream> body(stream);
+      return SendInternal(&body);
     }
 
     // nsIXHRSendable?
     nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(supports);
     if (sendable) {
-      return GetRequestBodyInternal(sendable, aResult, aContentLength,
-                                    aContentType, aCharset);
+      RequestBody<nsIXHRSendable> body(sendable);
+      return SendInternal(&body);
     }
 
     // ArrayBuffer?
     JSContext* rootingCx = nsContentUtils::RootingCx();
     JS::Rooted<JS::Value> realVal(rootingCx);
 
-    nsresult rv = aBody->GetAsJSVal(&realVal);
+    nsresult rv = aVariant->GetAsJSVal(&realVal);
     if (NS_SUCCEEDED(rv) && !realVal.isPrimitive()) {
       JS::Rooted<JSObject*> obj(rootingCx, realVal.toObjectOrNull());
       RootedTypedArray<ArrayBuffer> buf(rootingCx);
       if (buf.Init(obj)) {
-          buf.ComputeLengthAndData();
-          return GetRequestBodyInternal(buf.Data(), buf.Length(), aResult,
-                                        aContentLength, aContentType, aCharset);
+        RequestBody<const ArrayBuffer> body(&buf);
+        return SendInternal(&body);
       }
     }
-  }
-  else if (dataType == nsIDataType::VTYPE_VOID ||
+  } else if (dataType == nsIDataType::VTYPE_VOID ||
            dataType == nsIDataType::VTYPE_EMPTY) {
-    // Makes us act as if !aBody, don't upload anything
-    aContentType.AssignLiteral("text/plain");
-    aCharset.AssignLiteral("UTF-8");
-    *aContentLength = 0;
-
-    return NS_OK;
+    return SendInternal(nullptr);
   }
 
   char16_t* data = nullptr;
   uint32_t len = 0;
-  rv = aBody->GetAsWStringWithSize(&len, &data);
+  rv = aVariant->GetAsWStringWithSize(&len, &data);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsString string;
   string.Adopt(data, len);
 
-  return GetRequestBodyInternal(string, aResult, aContentLength, aContentType,
-                                aCharset);
-}
-
-/* static */
-nsresult
-XMLHttpRequestMainThread::GetRequestBody(nsIVariant* aVariant,
-                                         const Nullable<RequestBody>& aBody,
-                                         nsIInputStream** aResult,
-                                         uint64_t* aContentLength,
-                                         nsACString& aContentType,
-                                         nsACString& aCharset)
-{
-  // null the content type and charset by default, as per XHR spec step 4
-  aContentType.SetIsVoid(true);
-  aCharset.SetIsVoid(true);
-
-  if (aVariant) {
-    return GetRequestBodyInternal(aVariant, aResult, aContentLength,
-                                  aContentType, aCharset);
-  }
-
-  const RequestBody& body = aBody.Value();
-  RequestBody::Value value = body.GetValue();
-  switch (body.GetType()) {
-    case XMLHttpRequestMainThread::RequestBody::eArrayBuffer:
-    {
-      const ArrayBuffer* buffer = value.mArrayBuffer;
-      buffer->ComputeLengthAndData();
-      return GetRequestBodyInternal(buffer->Data(), buffer->Length(), aResult,
-                                    aContentLength, aContentType, aCharset);
-    }
-    case XMLHttpRequestMainThread::RequestBody::eArrayBufferView:
-    {
-      const ArrayBufferView* view = value.mArrayBufferView;
-      view->ComputeLengthAndData();
-      return GetRequestBodyInternal(view->Data(), view->Length(), aResult,
-                                    aContentLength, aContentType, aCharset);
-    }
-    case XMLHttpRequestMainThread::RequestBody::eBlob:
-    {
-      nsresult rv;
-      nsCOMPtr<nsIDOMBlob> blob = value.mBlob;
-      nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(blob, &rv);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      return GetRequestBodyInternal(sendable, aResult, aContentLength,
-                                    aContentType, aCharset);
-    }
-    case XMLHttpRequestMainThread::RequestBody::eDocument:
-    {
-      nsCOMPtr<nsIDOMDocument> document = do_QueryInterface(value.mDocument);
-      return GetRequestBodyInternal(document, aResult, aContentLength,
-                                    aContentType, aCharset);
-    }
-    case XMLHttpRequestMainThread::RequestBody::eDOMString:
-    {
-      return GetRequestBodyInternal(*value.mString, aResult, aContentLength,
-                                    aContentType, aCharset);
-    }
-    case XMLHttpRequestMainThread::RequestBody::eFormData:
-    {
-      MOZ_ASSERT(value.mFormData);
-      return GetRequestBodyInternal(value.mFormData, aResult, aContentLength,
-                                    aContentType, aCharset);
-    }
-    case XMLHttpRequestMainThread::RequestBody::eURLSearchParams:
-    {
-      MOZ_ASSERT(value.mURLSearchParams);
-      return GetRequestBodyInternal(value.mURLSearchParams, aResult,
-                                    aContentLength, aContentType, aCharset);
-    }
-    case XMLHttpRequestMainThread::RequestBody::eInputStream:
-    {
-      return GetRequestBodyInternal(value.mStream, aResult, aContentLength,
-                                    aContentType, aCharset);
-    }
-    default:
-    {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  NS_NOTREACHED("Default cases exist for a reason");
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-XMLHttpRequestMainThread::Send(nsIVariant *aBody)
-{
-  return Send(aBody, Nullable<RequestBody>());
+  RequestBody<const nsAString> body(&string);
+  return SendInternal(&body);
 }
 
 nsresult
-XMLHttpRequestMainThread::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
+XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
 {
   NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
 
   PopulateNetworkInterfaceId();
 
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
@@ -2556,35 +2497,35 @@ XMLHttpRequestMainThread::Send(nsIVarian
 
   mUploadTransferred = 0;
   mUploadTotal = 0;
   // By default we don't have any upload, so mark upload complete.
   mUploadComplete = true;
   mErrorLoad = false;
   mLoadLengthComputable = false;
   mLoadTotal = 0;
-  if ((aVariant || !aBody.IsNull()) && httpChannel &&
+  if (aBody && httpChannel &&
       !method.LowerCaseEqualsLiteral("get") &&
       !method.LowerCaseEqualsLiteral("head")) {
 
     nsAutoCString charset;
     nsAutoCString defaultContentType;
     nsCOMPtr<nsIInputStream> postDataStream;
 
     uint64_t size_u64;
-    rv = GetRequestBody(aVariant, aBody, getter_AddRefs(postDataStream),
-                        &size_u64, defaultContentType, charset);
+    rv = aBody->GetAsStream(getter_AddRefs(postDataStream),
+                            &size_u64, defaultContentType, charset);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // make sure it fits within js MAX_SAFE_INTEGER
     mUploadTotal =
       net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
 
     if (postDataStream) {
-      // If author set no Content-Type, use the default from GetRequestBody().
+      // If author set no Content-Type, use the default from GetAsStream().
       nsAutoCString contentType;
 
       GetAuthorRequestHeaderValue("content-type", contentType);
       if (contentType.IsVoid()) {
         contentType = defaultContentType;
 
         if (!charset.IsEmpty()) {
           // If we are providing the default content type, then we also need to
@@ -2828,17 +2769,16 @@ XMLHttpRequestMainThread::Send(nsIVarian
     mChannel->SetNotificationCallbacks(mNotificationCallbacks);
     mChannel = nullptr;
 
     return rv;
   }
 
   mWaitingForOnStopRequest = true;
 
-  // Step 8
   mFlagSend = true;
 
   // If we're synchronous, spin an event loop here and wait
   if (mFlagSynchronous) {
     mFlagSyncLooping = true;
 
     nsCOMPtr<nsIDocument> suspendedDoc;
     nsCOMPtr<nsIRunnable> resumeTimeoutRunnable;
@@ -2887,18 +2827,17 @@ XMLHttpRequestMainThread::Send(nsIVarian
     if (mProgressNotifier) {
       mProgressTimerIsActive = false;
       mProgressNotifier->Cancel();
     }
 
     if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
       StartProgressEventTimer();
     }
-    DispatchProgressEvent(this, ProgressEventType::loadstart, false,
-                          0, 0);
+    DispatchProgressEvent(this, ProgressEventType::loadstart, false, 0, 0);
     if (mUpload && !mUploadComplete) {
       DispatchProgressEvent(mUpload, ProgressEventType::loadstart, true,
                             0, mUploadTotal);
     }
   }
 
   if (!mChannel) {
     return NS_ERROR_FAILURE;
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -233,173 +233,113 @@ public:
   SetWithCredentials(bool aWithCredentials, ErrorResult& aRv) override;
 
   virtual XMLHttpRequestUpload*
   GetUpload(ErrorResult& aRv) override;
 
 private:
   virtual ~XMLHttpRequestMainThread();
 
-  class RequestBody
+  class RequestBodyBase
   {
   public:
-    RequestBody() : mType(eUninitialized)
+    virtual nsresult GetAsStream(nsIInputStream** aResult,
+                                 uint64_t* aContentLength,
+                                 nsACString& aContentType,
+                                 nsACString& aCharset) const
+    {
+      NS_ASSERTION(false, "RequestBodyBase should not be used directly.");
+      return NS_ERROR_FAILURE;
+    }
+  };
+
+  template<typename Type>
+  class RequestBody final : public RequestBodyBase
+  {
+    Type* mBody;
+  public:
+    RequestBody(Type* aBody) : mBody(aBody)
     {
     }
-    explicit RequestBody(const ArrayBuffer* aArrayBuffer) : mType(eArrayBuffer)
-    {
-      mValue.mArrayBuffer = aArrayBuffer;
-    }
-    explicit RequestBody(const ArrayBufferView* aArrayBufferView) : mType(eArrayBufferView)
-    {
-      mValue.mArrayBufferView = aArrayBufferView;
-    }
-    explicit RequestBody(Blob& aBlob) : mType(eBlob)
-    {
-      mValue.mBlob = &aBlob;
-    }
-    explicit RequestBody(mozilla::dom::URLSearchParams& aURLSearchParams) :
-      mType(eURLSearchParams)
-    {
-      mValue.mURLSearchParams = &aURLSearchParams;
-    }
-    explicit RequestBody(nsIDocument* aDocument) : mType(eDocument)
-    {
-      mValue.mDocument = aDocument;
-    }
-    explicit RequestBody(const nsAString& aString) : mType(eDOMString)
-    {
-      mValue.mString = &aString;
-    }
-    explicit RequestBody(FormData& aFormData) : mType(eFormData)
-    {
-      mValue.mFormData = &aFormData;
-    }
-    explicit RequestBody(nsIInputStream* aStream) : mType(eInputStream)
-    {
-      mValue.mStream = aStream;
-    }
-
-    enum Type {
-      eUninitialized,
-      eArrayBuffer,
-      eArrayBufferView,
-      eBlob,
-      eDocument,
-      eDOMString,
-      eFormData,
-      eInputStream,
-      eURLSearchParams
-    };
-    union Value {
-      const ArrayBuffer* mArrayBuffer;
-      const ArrayBufferView* mArrayBufferView;
-      Blob* mBlob;
-      nsIDocument* mDocument;
-      const nsAString* mString;
-      FormData* mFormData;
-      nsIInputStream* mStream;
-      URLSearchParams* mURLSearchParams;
-    };
-
-    Type GetType() const
-    {
-      MOZ_ASSERT(mType != eUninitialized);
-      return mType;
-    }
-    Value GetValue() const
-    {
-      MOZ_ASSERT(mType != eUninitialized);
-      return mValue;
-    }
-
-  private:
-    Type mType;
-    Value mValue;
+    nsresult GetAsStream(nsIInputStream** aResult,
+                         uint64_t* aContentLength,
+                         nsACString& aContentType,
+                         nsACString& aCharset) const override;
   };
 
-  static nsresult GetRequestBody(nsIVariant* aVariant,
-                                 const Nullable<RequestBody>& aBody,
-                                 nsIInputStream** aResult,
-                                 uint64_t* aContentLength,
-                                 nsACString& aContentType,
-                                 nsACString& aCharset);
-
-  nsresult Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody);
-  nsresult Send(const Nullable<RequestBody>& aBody)
-  {
-    return Send(nullptr, aBody);
-  }
-  nsresult Send(const RequestBody& aBody)
-  {
-    return Send(Nullable<RequestBody>(aBody));
-  }
+  nsresult SendInternal(const RequestBodyBase* aBody);
 
   bool IsCrossSiteCORSRequest() const;
   bool IsDeniedCrossSiteCORSRequest();
 
   // Tell our channel what network interface ID we were told to use.
   // If it's an HTTP channel and we were told to use a non-default
   // interface ID.
   void PopulateNetworkInterfaceId();
 
 public:
   virtual void
   Send(JSContext* /*aCx*/, ErrorResult& aRv) override
   {
-    aRv = Send(Nullable<RequestBody>());
+    aRv = SendInternal(nullptr);
   }
 
   virtual void
   Send(JSContext* /*aCx*/, const ArrayBuffer& aArrayBuffer,
        ErrorResult& aRv) override
   {
-    aRv = Send(RequestBody(&aArrayBuffer));
+    RequestBody<const ArrayBuffer> body(&aArrayBuffer);
+    aRv = SendInternal(&body);
   }
 
   virtual void
   Send(JSContext* /*aCx*/, const ArrayBufferView& aArrayBufferView,
        ErrorResult& aRv) override
   {
-    aRv = Send(RequestBody(&aArrayBufferView));
+    RequestBody<const ArrayBufferView> body(&aArrayBufferView);
+    aRv = SendInternal(&body);
   }
 
   virtual void
   Send(JSContext* /*aCx*/, Blob& aBlob, ErrorResult& aRv) override
   {
-    aRv = Send(RequestBody(aBlob));
+    RequestBody<Blob> body(&aBlob);
+    aRv = SendInternal(&body);
   }
 
   virtual void Send(JSContext* /*aCx*/, URLSearchParams& aURLSearchParams,
                     ErrorResult& aRv) override
   {
-    aRv = Send(RequestBody(aURLSearchParams));
+    RequestBody<URLSearchParams> body(&aURLSearchParams);
+    aRv = SendInternal(&body);
   }
 
   virtual void
   Send(JSContext* /*aCx*/, nsIDocument& aDoc, ErrorResult& aRv) override
   {
-    aRv = Send(RequestBody(&aDoc));
+    RequestBody<nsIDocument> body(&aDoc);
+    aRv = SendInternal(&body);
   }
 
   virtual void
   Send(JSContext* aCx, const nsAString& aString, ErrorResult& aRv) override
   {
     if (DOMStringIsNull(aString)) {
       Send(aCx, aRv);
-    }
-    else {
-      aRv = Send(RequestBody(aString));
+    } else {
+      RequestBody<const nsAString> body(&aString);
+      aRv = SendInternal(&body);
     }
   }
 
   virtual void
   Send(JSContext* /*aCx*/, FormData& aFormData, ErrorResult& aRv) override
   {
-    aRv = Send(RequestBody(aFormData));
+    RequestBody<FormData> body(&aFormData);
+    aRv = SendInternal(&body);
   }
 
   virtual void
   Send(JSContext* aCx, nsIInputStream* aStream, ErrorResult& aRv) override
   {
     NS_ASSERTION(aStream, "Null should go to string version");
     nsCOMPtr<nsIXPConnectWrappedJS> wjs = do_QueryInterface(aStream);
     if (wjs) {
@@ -413,17 +353,18 @@ public:
       if (ConvertJSValueToString(aCx, dataAsValue, eNull,
                                  eNull, dataAsString)) {
         Send(aCx, dataAsString, aRv);
       } else {
         aRv.Throw(NS_ERROR_FAILURE);
       }
       return;
     }
-    aRv = Send(RequestBody(aStream));
+    RequestBody<nsIInputStream> body(aStream);
+    aRv = SendInternal(&body);
   }
 
   void
   Abort() {
     ErrorResult rv;
     Abort(rv);
     MOZ_ASSERT(!rv.Failed());
   }