Backed out 3 changesets (bug 1285036) for browser_net_resend.js failures on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 21 Jul 2016 14:56:01 -0400
changeset 306215 b2508db54956f6ab4e1a5b91cb46db6ab9e2ca2c
parent 306214 5910a5ced959527375eea77c194bb0ff81d8ee6b
child 306216 3e6ee8c6b56b84368c3a464c86067af78971ada0
push id30480
push usercbook@mozilla.com
push dateFri, 22 Jul 2016 09:58:20 +0000
treeherdermozilla-central@e0bc88708ffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1285036
milestone50.0a1
backs out1fd18c03c696d8c7789851b61844219fae3b8e95
6462cd2ea24987384812287c7346e2c397e570ff
6b51b69f723ee6e543d15b4a8f55323fa2d71c45
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
Backed out 3 changesets (bug 1285036) for browser_net_resend.js failures on a CLOSED TREE. Backed out changeset 1fd18c03c696 (bug 1285036) Backed out changeset 6462cd2ea249 (bug 1285036) Backed out changeset 6b51b69f723e (bug 1285036)
dom/base/nsContentUtils.cpp
dom/xhr/XMLHttpRequestMainThread.cpp
dom/xhr/XMLHttpRequestMainThread.h
dom/xhr/XMLHttpRequestWorker.cpp
dom/xhr/tests/test_xhr_forbidden_headers.html
testing/web-platform/meta/XMLHttpRequest/setrequestheader-case-insensitive.htm.ini
testing/web-platform/meta/XMLHttpRequest/setrequestheader-header-allowed.htm.ini
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7062,18 +7062,19 @@ nsContentUtils::IsForbiddenRequestHeader
 
 // static
 bool
 nsContentUtils::IsForbiddenSystemRequestHeader(const nsACString& aHeader)
 {
   static const char *kInvalidHeaders[] = {
     "accept-charset", "accept-encoding", "access-control-request-headers",
     "access-control-request-method", "connection", "content-length",
-    "cookie", "cookie2", "date", "dnt", "expect", "host", "keep-alive",
-    "origin", "referer", "te", "trailer", "transfer-encoding", "upgrade", "via"
+    "cookie", "cookie2", "content-transfer-encoding", "date", "dnt",
+    "expect", "host", "keep-alive", "origin", "referer", "te", "trailer",
+    "transfer-encoding", "upgrade", "via"
   };
   for (uint32_t i = 0; i < ArrayLength(kInvalidHeaders); ++i) {
     if (aHeader.LowerCaseEqualsASCII(kInvalidHeaders[i])) {
       return true;
     }
   }
   return false;
 }
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -1345,175 +1345,208 @@ XMLHttpRequestMainThread::GetCurrentJARC
 
 bool
 XMLHttpRequestMainThread::IsSystemXHR() const
 {
   return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
 }
 
 NS_IMETHODIMP
-XMLHttpRequestMainThread::Open(const nsACString& aMethod, const nsACString& aUrl,
-                               bool aAsync, const nsAString& aUsername,
-                               const nsAString& aPassword, uint8_t optional_argc)
+XMLHttpRequestMainThread::Open(const nsACString& method, const nsACString& url,
+                               bool async, const nsAString& user,
+                               const nsAString& password, uint8_t optional_argc)
 {
-  Optional<bool> async;
   if (!optional_argc) {
     // No optional arguments were passed in. Default async to true.
-    async.Construct() = true;
-  } else {
-    async.Construct() = aAsync;
+    async = true;
   }
-  Optional<nsAString> username;
+  Optional<nsAString> realUser;
   if (optional_argc > 1) {
-    username = &aUsername;
-  }
-  Optional<nsAString> password;
-  if (optional_argc > 2) {
-    password = &aPassword;
+    realUser = &user;
   }
-  return OpenInternal(aMethod, aUrl, async, username, password);
-}
-
-// This case is hit when the async parameter is outright omitted, which
-// should set it to true (and the username and password to null).
-void
-XMLHttpRequestMainThread::Open(const nsACString& aMethod, const nsAString& aUrl,
-                               ErrorResult& aRv)
-{
-  aRv = OpenInternal(aMethod, NS_ConvertUTF16toUTF8(aUrl), Optional<bool>(true),
-                     Optional<nsAString>(), Optional<nsAString>());
-}
-
-// This case is hit when the async parameter is specified, even if the
-// JS value was "undefined" (which due to legacy reasons should be
-// treated as true, which is how it will already be passed in here).
-void
-XMLHttpRequestMainThread::Open(const nsACString& aMethod,
-                               const nsAString& aUrl,
-                               bool aAsync,
-                               const Optional<nsAString>& aUsername,
-                               const Optional<nsAString>& aPassword,
-                               ErrorResult& aRv)
-{
-  aRv = OpenInternal(aMethod, NS_ConvertUTF16toUTF8(aUrl),
-                     Optional<bool>(aAsync), aUsername, aPassword);
+  Optional<nsAString> realPassword;
+  if (optional_argc > 2) {
+    realPassword = &password;
+  }
+  return Open(method, url, async, realUser, realPassword);
 }
 
 nsresult
-XMLHttpRequestMainThread::OpenInternal(const nsACString& aMethod,
-                                       const nsACString& aUrl,
-                                       const Optional<bool>& aAsync,
-                                       const Optional<nsAString>& aUsername,
-                                       const Optional<nsAString>& aPassword)
+XMLHttpRequestMainThread::Open(const nsACString& inMethod, const nsACString& url,
+                               bool async, const Optional<nsAString>& user,
+                               const Optional<nsAString>& password)
 {
-  bool async = aAsync.WasPassed() ? aAsync.Value() : true;
-
-  // Gecko-specific
+  if (inMethod.IsEmpty()) {
+    return NS_ERROR_DOM_SYNTAX_ERR;
+  }
+
   if (!async && !DontWarnAboutSyncXHR() && GetOwner() &&
       GetOwner()->GetExtantDoc()) {
     GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSyncXMLHttpRequest);
   }
 
-  Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC, async ? 0 : 1);
-
-  // Step 1
-  nsCOMPtr<nsIDocument> responsibleDocument = GetDocumentIfCurrent();
-  if (!responsibleDocument) {
-    // This could be because we're no longer current or because we're in some
-    // non-window context...
-    nsresult rv = CheckInnerWindowCorrectness();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return NS_ERROR_DOM_INVALID_STATE_ERR;
-    }
-  }
+  Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
+                        async ? 0 : 1);
+
   NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
 
-  // Steps 2-4
   nsAutoCString method;
-  nsresult rv = FetchUtil::GetValidRequestMethod(aMethod, method);
+  nsresult rv = FetchUtil::GetValidRequestMethod(inMethod, method);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  // Steps 5-6
-  nsCOMPtr<nsIURI> baseURI;
-  if (mBaseURI) {
-    baseURI = mBaseURI;
-  } else if (responsibleDocument) {
-    baseURI = responsibleDocument->GetBaseURI();
-  }
-  nsCOMPtr<nsIURI> parsedURL;
-  rv = NS_NewURI(getter_AddRefs(parsedURL), aUrl, nullptr, baseURI);
-  if (NS_FAILED(rv)) {
-    if (rv ==  NS_ERROR_MALFORMED_URI) {
-      return NS_ERROR_DOM_SYNTAX_ERR;
-    }
-    return rv;
-  }
-  if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
-    return NS_ERROR_DOM_INVALID_STATE_ERR;
-  }
-
-  // Step 7 is already done above.
-  // Note that the username and password are already passed in as null by Open()
-  // if the async parameter is omitted, so there's no need check again here.
-
-  // Step 8
-  if (aAsync.WasPassed()) {
-    nsAutoCString host;
-    parsedURL->GetHost(host);
-    if (!host.IsEmpty()) {
-      nsAutoCString userpass;
-      if (aUsername.WasPassed()) {
-        CopyUTF16toUTF8(aUsername.Value(), userpass);
-      }
-      userpass.AppendLiteral(":");
-      if (aPassword.WasPassed()) {
-        AppendUTF16toUTF8(aPassword.Value(), userpass);
-      }
-      parsedURL->SetUserPass(userpass);
-    }
-  }
-
-  // Step 9
-  if (!async && HasOrHasHadOwner() && (mTimeoutMilliseconds ||
+  // sync request is not allowed to use responseType or timeout
+  // in window context
+  if (!async && HasOrHasHadOwner() &&
+      (mTimeoutMilliseconds ||
        mResponseType != XMLHttpRequestResponseType::_empty)) {
     if (mTimeoutMilliseconds) {
       LogMessage("TimeoutSyncXHRWarning", GetOwner());
     }
     if (mResponseType != XMLHttpRequestResponseType::_empty) {
       LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
     }
     return NS_ERROR_DOM_INVALID_ACCESS_ERR;
   }
 
-  // Step 10
-  CloseRequest();
-
-  // Step 11
-  // timeouts are handled without a flag
+  nsCOMPtr<nsIURI> uri;
+
+  CloseRequest(); // spec step 10
+  ResetResponse(); // (part of) spec step 11
+
   mFlagSend = false;
-  mRequestMethod.Assign(method);
-  mRequestURL = parsedURL;
-  mFlagSynchronous = !async;
-  mAuthorRequestHeaders.Clear();
-  ResetResponse();
-
-  // Gecko-specific
-  mFlagHadUploadListenersOnSend = false;
+
+  // Unset any pre-existing aborted and timed-out flags.
   mFlagAborted = false;
   mFlagTimedOut = false;
 
-  rv = InitChannel();
+  mFlagSynchronous = !async;
+
+  nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+  if (!doc) {
+    // This could be because we're no longer current or because we're in some
+    // non-window context...
+    if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
+      return NS_ERROR_DOM_INVALID_STATE_ERR;
+    }
+  }
+
+  nsCOMPtr<nsIURI> baseURI;
+  if (mBaseURI) {
+    baseURI = mBaseURI;
+  }
+  else if (doc) {
+    baseURI = doc->GetBaseURI();
+  }
+
+  rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, baseURI);
+
+  if (NS_FAILED(rv)) {
+    if (rv ==  NS_ERROR_MALFORMED_URI) {
+      return NS_ERROR_DOM_SYNTAX_ERR;
+    }
+    return rv;
+  }
+
+  if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  // XXXbz this is wrong: we should only be looking at whether
+  // user/password were passed, not at the values!  See bug 759624.
+  if (user.WasPassed() && !user.Value().IsEmpty()) {
+    nsAutoCString userpass;
+    CopyUTF16toUTF8(user.Value(), userpass);
+    if (password.WasPassed() && !password.Value().IsEmpty()) {
+      userpass.Append(':');
+      AppendUTF16toUTF8(password.Value(), userpass);
+    }
+    uri->SetUserPass(userpass);
+  }
+
+  // Clear our record of previously set headers so future header set
+  // operations will merge/override correctly.
+  mAlreadySetHeaders.Clear();
+
+  // 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();
+
+  nsSecurityFlags secFlags;
+  nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND |
+    nsIChannel::LOAD_CLASSIFY_URI;
+  if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
+    // When chrome is loading we want to make sure to sandbox any potential
+    // result document. We also want to allow cross-origin loads.
+    secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL |
+               nsILoadInfo::SEC_SANDBOXED;
+  }
+  else if (IsSystemXHR()) {
+    // For pages that have appropriate permissions, we want to still allow
+    // cross-origin loads, but make sure that the any potential result
+    // documents get the same principal as the loader.
+    secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+               nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+    loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+  }
+  else {
+    // Otherwise use CORS. Again, make sure that potential result documents
+    // use the same principal as the loader.
+    secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS |
+               nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+  }
+
+  if (mIsAnon) {
+    secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
+  }
+
+  // If we have the document, use it. Unfortunately, for dedicated workers
+  // 'doc' ends up being the parent document, which is not the document
+  // that we want to use. So make sure to avoid using 'doc' in that situation.
+  if (doc && doc->NodePrincipal() == mPrincipal) {
+    rv = NS_NewChannel(getter_AddRefs(mChannel),
+                       uri,
+                       doc,
+                       secFlags,
+                       nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
+                       loadGroup,
+                       nullptr,   // aCallbacks
+                       loadFlags);
+  } else {
+    //otherwise use the principal
+    rv = NS_NewChannel(getter_AddRefs(mChannel),
+                       uri,
+                       mPrincipal,
+                       secFlags,
+                       nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
+                       loadGroup,
+                       nullptr,   // aCallbacks
+                       loadFlags);
+  }
+
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Step 12
+  mFlagHadUploadListenersOnSend = false;
+
+  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
+  if (httpChannel) {
+    rv = httpChannel->SetRequestMethod(method);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Set the initiator type
+    nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
+    if (timedChannel) {
+      timedChannel->SetInitiatorType(kLiteralString_xmlhttprequest);
+    }
+  }
+
   if (mState != State::opened) {
-    mState = State::opened;
-    FireReadystatechangeEvent();
+    ChangeState(State::opened);
   }
 
   return NS_OK;
 }
 
 void
 XMLHttpRequestMainThread::PopulateNetworkInterfaceId()
 {
@@ -2101,39 +2134,39 @@ 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
+static nsresult
+GetRequestBodyInternal(nsIDOMDocument* aDoc, nsIInputStream** aResult,
+                       uint64_t* aContentLength, nsACString& aContentType,
+                       nsACString& aCharset)
 {
-  nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(mBody));
-  NS_ENSURE_STATE(domdoc);
+  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 (mBody->IsHTMLDocument()) {
+  if (doc->IsHTMLDocument()) {
     aContentType.AssignLiteral("text/html");
 
     nsString serialized;
-    if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, 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);
 
@@ -2141,99 +2174,82 @@ XMLHttpRequestMainThread::RequestBody<ns
   } 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);
+    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;
 
-  rv = storStream->NewInputStream(0, aResult);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return NS_OK;
+  return storStream->NewInputStream(0, aResult);
 }
 
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<const nsAString>::GetAsStream(
-   nsIInputStream** aResult, uint64_t* aContentLength,
-   nsACString& aContentType, nsACString& aCharset) const
+static nsresult
+GetRequestBodyInternal(const nsAString& aString, nsIInputStream** aResult,
+                       uint64_t* aContentLength, nsACString& aContentType,
+                       nsACString& aCharset)
 {
   aContentType.AssignLiteral("text/plain");
   aCharset.AssignLiteral("UTF-8");
 
-  nsCString converted = NS_ConvertUTF16toUTF8(*mBody);
+  nsCString converted = NS_ConvertUTF16toUTF8(aString);
   *aContentLength = converted.Length();
-  nsresult rv = NS_NewCStringInputStream(aResult, converted);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return NS_OK;
+  return NS_NewCStringInputStream(aResult, converted);
 }
 
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<nsIInputStream>::GetAsStream(
-   nsIInputStream** aResult, uint64_t* aContentLength,
-   nsACString& aContentType, nsACString& aCharset) const
+static nsresult
+GetRequestBodyInternal(nsIInputStream* aStream, nsIInputStream** aResult,
+                       uint64_t* aContentLength, nsACString& aContentType,
+                       nsACString& aCharset)
 {
   aContentType.AssignLiteral("text/plain");
   aCharset.Truncate();
 
-  nsresult rv = mBody->Available(aContentLength);
+  nsresult rv = aStream->Available(aContentLength);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIInputStream> stream(mBody);
-  stream.forget(aResult);
+  NS_ADDREF(*aResult = aStream);
+
   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)
+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;
@@ -2241,194 +2257,195 @@ GetBufferDataAsStream(const uint8_t* aDa
                                       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()
+static nsresult
+GetRequestBodyInternal(nsIVariant* aBody, nsIInputStream** aResult,
+                       uint64_t* aContentLength, nsACString& aContentType,
+                       nsACString& aCharset)
 {
-  // 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();
-
-  nsSecurityFlags secFlags;
-  nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND |
-                          nsIChannel::LOAD_CLASSIFY_URI;
-  if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
-    // When chrome is loading we want to make sure to sandbox any potential
-    // result document. We also want to allow cross-origin loads.
-    secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL |
-               nsILoadInfo::SEC_SANDBOXED;
-  } else if (IsSystemXHR()) {
-    // For pages that have appropriate permissions, we want to still allow
-    // cross-origin loads, but make sure that the any potential result
-    // documents get the same principal as the loader.
-    secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
-               nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
-    loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
-  } else {
-    // Otherwise use CORS. Again, make sure that potential result documents
-    // use the same principal as the loader.
-    secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS |
-               nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
-  }
-
-  if (mIsAnon) {
-    secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
-  }
-
-  // Use the responsibleDocument if we have it, except for dedicated workers
-  // where it will be the parent document, which is not the one we want to use.
-  nsresult rv;
-  nsCOMPtr<nsIDocument> responsibleDocument = GetDocumentIfCurrent();
-  if (responsibleDocument && responsibleDocument->NodePrincipal() == mPrincipal) {
-    rv = NS_NewChannel(getter_AddRefs(mChannel),
-                       mRequestURL,
-                       responsibleDocument,
-                       secFlags,
-                       nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
-                       loadGroup,
-                       nullptr,   // aCallbacks
-                       loadFlags);
-  } else {
-    // Otherwise use the principal.
-    rv = NS_NewChannel(getter_AddRefs(mChannel),
-                       mRequestURL,
-                       mPrincipal,
-                       secFlags,
-                       nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
-                       loadGroup,
-                       nullptr,   // aCallbacks
-                       loadFlags);
-  }
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
-  if (httpChannel) {
-    rv = httpChannel->SetRequestMethod(mRequestMethod);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Set the initiator type
-    nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
-    if (timedChannel) {
-      timedChannel->SetInitiatorType(NS_LITERAL_STRING("xmlhttprequest"));
-    }
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
-{
-  if (!aVariant) {
-    return SendInternal(nullptr);
-  }
+  *aResult = nullptr;
 
   uint16_t dataType;
-  nsresult rv = aVariant->GetDataType(&dataType);
+  nsresult rv = aBody->GetDataType(&dataType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (dataType == nsIDataType::VTYPE_INTERFACE ||
       dataType == nsIDataType::VTYPE_INTERFACE_IS) {
     nsCOMPtr<nsISupports> supports;
     nsID *iid;
-    rv = aVariant->GetAsInterface(&iid, getter_AddRefs(supports));
+    rv = aBody->GetAsInterface(&iid, getter_AddRefs(supports));
     NS_ENSURE_SUCCESS(rv, rv);
 
     free(iid);
 
     // document?
-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(supports);
+    nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(supports);
     if (doc) {
-      RequestBody<nsIDocument> body(doc);
-      return SendInternal(&body);
+      return GetRequestBodyInternal(doc, aResult, aContentLength, aContentType,
+                                    aCharset);
     }
 
     // nsISupportsString?
     nsCOMPtr<nsISupportsString> wstr = do_QueryInterface(supports);
     if (wstr) {
       nsAutoString string;
       wstr->GetData(string);
-      RequestBody<const nsAString> body(&string);
-      return SendInternal(&body);
+
+      return GetRequestBodyInternal(string, aResult, aContentLength,
+                                    aContentType, aCharset);
     }
 
     // nsIInputStream?
     nsCOMPtr<nsIInputStream> stream = do_QueryInterface(supports);
     if (stream) {
-      RequestBody<nsIInputStream> body(stream);
-      return SendInternal(&body);
+      return GetRequestBodyInternal(stream, aResult, aContentLength,
+                                    aContentType, aCharset);
     }
 
     // nsIXHRSendable?
     nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(supports);
     if (sendable) {
-      RequestBody<nsIXHRSendable> body(sendable);
-      return SendInternal(&body);
+      return GetRequestBodyInternal(sendable, aResult, aContentLength,
+                                    aContentType, aCharset);
     }
 
     // ArrayBuffer?
     JSContext* rootingCx = nsContentUtils::RootingCx();
     JS::Rooted<JS::Value> realVal(rootingCx);
 
-    nsresult rv = aVariant->GetAsJSVal(&realVal);
+    nsresult rv = aBody->GetAsJSVal(&realVal);
     if (NS_SUCCEEDED(rv) && !realVal.isPrimitive()) {
       JS::Rooted<JSObject*> obj(rootingCx, realVal.toObjectOrNull());
       RootedTypedArray<ArrayBuffer> buf(rootingCx);
       if (buf.Init(obj)) {
-        RequestBody<const ArrayBuffer> body(&buf);
-        return SendInternal(&body);
+          buf.ComputeLengthAndData();
+          return GetRequestBodyInternal(buf.Data(), buf.Length(), aResult,
+                                        aContentLength, aContentType, aCharset);
       }
     }
-  } else if (dataType == nsIDataType::VTYPE_VOID ||
+  }
+  else if (dataType == nsIDataType::VTYPE_VOID ||
            dataType == nsIDataType::VTYPE_EMPTY) {
-    return SendInternal(nullptr);
+    // Makes us act as if !aBody, don't upload anything
+    aContentType.AssignLiteral("text/plain");
+    aCharset.AssignLiteral("UTF-8");
+    *aContentLength = 0;
+
+    return NS_OK;
   }
 
   char16_t* data = nullptr;
   uint32_t len = 0;
-  rv = aVariant->GetAsWStringWithSize(&len, &data);
+  rv = aBody->GetAsWStringWithSize(&len, &data);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsString string;
   string.Adopt(data, len);
 
-  RequestBody<const nsAString> body(&string);
-  return SendInternal(&body);
+  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>());
 }
 
 nsresult
-XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
+XMLHttpRequestMainThread::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
 {
   NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
 
   PopulateNetworkInterfaceId();
 
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
@@ -2458,19 +2475,16 @@ XMLHttpRequestMainThread::SendInternal(c
   //     an asynchronous call.
 
   // Ignore argument if method is GET, there is no point in trying to
   // upload anything
   nsAutoCString method;
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
 
   if (httpChannel) {
-    // Spec step 5
-    SetAuthorRequestHeadersOnChannel(httpChannel);
-
     httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase
 
     if (!IsSystemXHR()) {
       nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
       nsCOMPtr<nsIDocument> doc = owner ? owner->GetExtantDoc() : nullptr;
       nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal, doc,
                                                     httpChannel, mozilla::net::RP_Default);
     }
@@ -2497,42 +2511,43 @@ XMLHttpRequestMainThread::SendInternal(c
 
   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 (aBody && httpChannel &&
+  if ((aVariant || !aBody.IsNull()) && httpChannel &&
       !method.LowerCaseEqualsLiteral("get") &&
       !method.LowerCaseEqualsLiteral("head")) {
 
     nsAutoCString charset;
     nsAutoCString defaultContentType;
     nsCOMPtr<nsIInputStream> postDataStream;
 
     uint64_t size_u64;
-    rv = aBody->GetAsStream(getter_AddRefs(postDataStream),
-                            &size_u64, defaultContentType, charset);
+    rv = GetRequestBody(aVariant, aBody, 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 GetAsStream().
+      // If no content type header was set by the client, we set it to
+      // application/xml.
       nsAutoCString contentType;
-
-      GetAuthorRequestHeaderValue("content-type", contentType);
-      if (contentType.IsVoid()) {
+      if (NS_FAILED(httpChannel->
+                      GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"),
+                                       contentType))) {
         contentType = defaultContentType;
 
-        if (!charset.IsEmpty()) {
+        if (!charset.IsEmpty() && !contentType.IsVoid()) {
           // If we are providing the default content type, then we also need to
           // provide a charset declaration.
           contentType.Append(NS_LITERAL_CSTRING(";charset="));
           contentType.Append(charset);
         }
       }
 
       // We don't want to set a charset for streams.
@@ -2700,36 +2715,18 @@ XMLHttpRequestMainThread::SendInternal(c
 
   // Check if we should enabled cross-origin upload listeners.
   if (mUpload && mUpload->HasListeners()) {
     mFlagHadUploadListenersOnSend = true;
   }
 
   // Set up the preflight if needed
   if (!IsSystemXHR()) {
-    nsTArray<nsCString> CORSUnsafeHeaders;
-    const char *kCrossOriginSafeHeaders[] = {
-      "accept", "accept-language", "content-language", "content-type",
-      "last-event-id"
-    };
-    for (RequestHeader& header : mAuthorRequestHeaders) {
-      bool safe = false;
-      for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
-        if (header.name.EqualsASCII(kCrossOriginSafeHeaders[i])) {
-          safe = true;
-          break;
-        }
-      }
-      if (!safe) {
-        CORSUnsafeHeaders.AppendElement(header.name);
-      }
-    }
-
     nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
-    loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
+    loadInfo->SetCorsPreflightInfo(mCORSUnsafeHeaders,
                                    mFlagHadUploadListenersOnSend);
   }
 
   mIsMappedArrayBuffer = false;
   if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
       Preferences::GetBool("dom.mapped_arraybuffer.enabled", true)) {
     nsCOMPtr<nsIURI> uri;
     nsAutoCString scheme;
@@ -2769,16 +2766,17 @@ XMLHttpRequestMainThread::SendInternal(c
     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;
@@ -2827,88 +2825,126 @@ XMLHttpRequestMainThread::SendInternal(c
     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;
   }
 
   return rv;
 }
 
 // http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
 NS_IMETHODIMP
-XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
-                                           const nsACString& aValue)
+XMLHttpRequestMainThread::SetRequestHeader(const nsACString& header,
+                                           const nsACString& value)
 {
   // Steps 1 and 2
   if (mState != State::opened || mFlagSend) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
+  NS_ASSERTION(mChannel, "mChannel must be valid if we're OPENED.");
 
   // Step 3
-  nsAutoCString value(aValue);
-  static const char kHTTPWhitespace[] = "\n\t\r ";
-  value.Trim(kHTTPWhitespace);
-
-  // Step 4
-  if (!NS_IsValidHTTPToken(aName) || !NS_IsReasonableHTTPHeaderValue(value)) {
+  // Make sure we don't store an invalid header name in mCORSUnsafeHeaders
+  if (!NS_IsValidHTTPToken(header)) {
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
 
-  // Step 5
-  bool isPrivilegedCaller = IsSystemXHR();
-  bool isForbiddenHeader = nsContentUtils::IsForbiddenRequestHeader(aName);
-  if (!isPrivilegedCaller && isForbiddenHeader) {
-    NS_WARNING("refusing to set request header");
+  if (!mChannel)             // open() initializes mChannel, and open()
+    return NS_ERROR_FAILURE; // must be called before first setRequestHeader()
+
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+  if (!httpChannel) {
     return NS_OK;
   }
 
-  // Step 6.1
-  nsAutoCString lowercaseName;
-  nsContentUtils::ASCIIToLower(aName, lowercaseName);
-
-  // Step 6.2
-  bool notAlreadySet = true;
-  for (RequestHeader& header : mAuthorRequestHeaders) {
-    if (header.name.Equals(lowercaseName)) {
-      // Gecko-specific: invalid headers can be set by privileged
-      //                 callers, but will not merge.
-      if (isPrivilegedCaller && isForbiddenHeader) {
-        header.value.Assign(value);
-      } else {
-        header.value.AppendLiteral(", ");
-        header.value.Append(value);
+  // We will merge XHR headers, per the spec (secion 4.6.2) unless:
+  // 1 - The caller is privileged and setting an invalid header,
+  // or
+  // 2 - we have not yet explicitly set that header; this allows web
+  //     content to override default headers the first time they set them.
+  bool mergeHeaders = true;
+
+  if (!IsSystemXHR()) {
+    // Step 5: Check for dangerous headers.
+    // Prevent modification to certain HTTP headers (see bug 302263), unless
+    // the executing script is privileged.
+    if (nsContentUtils::IsForbiddenRequestHeader(header)) {
+      NS_WARNING("refusing to set request header");
+      return NS_OK;
+    }
+
+    // Check for dangerous cross-site headers
+    bool safeHeader = IsSystemXHR();
+    if (!safeHeader) {
+      // Content-Type isn't always safe, but we'll deal with it in Send()
+      const char *kCrossOriginSafeHeaders[] = {
+        "accept", "accept-language", "content-language", "content-type",
+        "last-event-id"
+      };
+      for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
+        if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
+          safeHeader = true;
+          break;
+        }
       }
-      notAlreadySet = false;
-      break;
+    }
+
+    if (!safeHeader) {
+      if (!mCORSUnsafeHeaders.Contains(header, nsCaseInsensitiveCStringArrayComparator())) {
+        mCORSUnsafeHeaders.AppendElement(header);
+      }
+    }
+  } else {
+    // Case 1 above
+    if (nsContentUtils::IsForbiddenSystemRequestHeader(header)) {
+      mergeHeaders = false;
     }
   }
 
-  // Step 6.3
-  if (notAlreadySet) {
-    RequestHeader newHeader = {
-      nsCString(lowercaseName), nsCString(value)
+  if (!mAlreadySetHeaders.Contains(header)) {
+    // Case 2 above
+    mergeHeaders = false;
+  }
+
+  nsresult rv;
+  if (value.IsEmpty()) {
+    rv = httpChannel->SetEmptyRequestHeader(header);
+  } else {
+    // Merge headers depending on what we decided above.
+    rv = httpChannel->SetRequestHeader(header, value, mergeHeaders);
+  }
+  if (rv == NS_ERROR_INVALID_ARG) {
+    return NS_ERROR_DOM_SYNTAX_ERR;
+  }
+  if (NS_SUCCEEDED(rv)) {
+    // Remember that we've set this header, so subsequent set operations will merge values.
+    mAlreadySetHeaders.PutEntry(nsCString(header));
+
+    // We'll want to duplicate this header for any replacement channels (eg. on redirect)
+    RequestHeader reqHeader = {
+      nsCString(header), nsCString(value)
     };
-    mAuthorRequestHeaders.AppendElement(newHeader);
+    mModifiedRequestHeaders.AppendElement(reqHeader);
   }
-
-  return NS_OK;
+  return rv;
 }
 
 NS_IMETHODIMP
 XMLHttpRequestMainThread::GetTimeout(uint32_t *aTimeout)
 {
   *aTimeout = Timeout();
   return NS_OK;
 }
@@ -3138,55 +3174,37 @@ XMLHttpRequestMainThread::AsyncOnChannel
         mNewRedirectChannel = nullptr;
     }
     return rv;
   }
   OnRedirectVerifyCallback(NS_OK);
   return NS_OK;
 }
 
-void
-XMLHttpRequestMainThread::GetAuthorRequestHeaderValue(const char* aName,
-                                                      nsACString& outValue)
-{
-  for (RequestHeader& header : mAuthorRequestHeaders) {
-    if (header.name.Equals(aName)) {
-      outValue.Assign(header.value);
-      return;
-    }
-  }
-  outValue.SetIsVoid(true);
-}
-
-void
-XMLHttpRequestMainThread::SetAuthorRequestHeadersOnChannel(
-  nsCOMPtr<nsIHttpChannel> aHttpChannel)
-{
-  for (RequestHeader& header : mAuthorRequestHeaders) {
-    if (header.value.IsEmpty()) {
-      aHttpChannel->SetEmptyRequestHeader(header.name);
-    } else {
-      aHttpChannel->SetRequestHeader(header.name, header.value, false);
-    }
-  }
-}
-
 nsresult
 XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result)
 {
   NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
   NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
 
   if (NS_SUCCEEDED(result)) {
     mChannel = mNewRedirectChannel;
 
     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
     if (httpChannel) {
       // Ensure all original headers are duplicated for the new channel (bug #553888)
-      SetAuthorRequestHeadersOnChannel(httpChannel);
+      for (RequestHeader& requestHeader : mModifiedRequestHeaders) {
+        if (requestHeader.value.IsEmpty()) {
+          httpChannel->SetEmptyRequestHeader(requestHeader.header);
+        } else {
+          httpChannel->SetRequestHeader(requestHeader.header,
+                                        requestHeader.value,
+                                        false);
+        }
+      }
     }
   } else {
     mErrorLoad = true;
   }
 
   mNewRedirectChannel = nullptr;
 
   mRedirectCallback->OnRedirectVerifyCallback(result);
@@ -3492,18 +3510,19 @@ XMLHttpRequestMainThread::EnsureXPCOMifi
 }
 
 bool
 XMLHttpRequestMainThread::ShouldBlockAuthPrompt()
 {
   // Verify that it's ok to prompt for credentials here, per spec
   // http://xhr.spec.whatwg.org/#the-send%28%29-method
 
-  for (RequestHeader& requestHeader : mAuthorRequestHeaders) {
-    if (requestHeader.name.EqualsLiteral("authorization")) {
+  for (uint32_t i = 0, len = mModifiedRequestHeaders.Length(); i < len; ++i) {
+    if (mModifiedRequestHeaders[i].header.
+          LowerCaseEqualsLiteral("authorization")) {
       return true;
     }
   }
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -194,33 +194,41 @@ public:
     SizeOfEventTargetIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(XMLHttpRequestEventTarget)
 
   // states
   virtual uint16_t ReadyState() const override;
 
   // request
-  nsresult InitChannel();
-
   virtual void
   Open(const nsACString& aMethod, const nsAString& aUrl,
-       ErrorResult& aRv) override;
+       ErrorResult& aRv) override
+  {
+    Open(aMethod, aUrl, true,
+         Optional<nsAString>(),
+         Optional<nsAString>(),
+         aRv);
+  }
 
   virtual void
   Open(const nsACString& aMethod, const nsAString& aUrl, bool aAsync,
        const Optional<nsAString>& aUser,
        const Optional<nsAString>& aPassword,
-       ErrorResult& aRv) override;
+       ErrorResult& aRv) override
+  {
+    aRv = Open(aMethod, NS_ConvertUTF16toUTF8(aUrl),
+               aAsync, aUser, aPassword);
+  }
 
   virtual void
-  SetRequestHeader(const nsACString& aName, const nsACString& aValue,
+  SetRequestHeader(const nsACString& aHeader, const nsACString& aValue,
                    ErrorResult& aRv) override
   {
-    aRv = SetRequestHeader(aName, aValue);
+    aRv = SetRequestHeader(aHeader, aValue);
   }
 
   virtual uint32_t
   Timeout() const override
   {
     return mTimeoutMilliseconds;
   }
 
@@ -233,113 +241,173 @@ public:
   SetWithCredentials(bool aWithCredentials, ErrorResult& aRv) override;
 
   virtual XMLHttpRequestUpload*
   GetUpload(ErrorResult& aRv) override;
 
 private:
   virtual ~XMLHttpRequestMainThread();
 
-  class RequestBodyBase
+  class RequestBody
   {
   public:
-    virtual nsresult GetAsStream(nsIInputStream** aResult,
-                                 uint64_t* aContentLength,
-                                 nsACString& aContentType,
-                                 nsACString& aCharset) const
+    RequestBody() : mType(eUninitialized)
+    {
+    }
+    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)
     {
-      NS_ASSERTION(false, "RequestBodyBase should not be used directly.");
-      return NS_ERROR_FAILURE;
+      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;
   };
 
-  template<typename Type>
-  class RequestBody final : public RequestBodyBase
+  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)
   {
-    Type* mBody;
-  public:
-    explicit RequestBody(Type* aBody) : mBody(aBody)
-    {
-    }
-    nsresult GetAsStream(nsIInputStream** aResult,
-                         uint64_t* aContentLength,
-                         nsACString& aContentType,
-                         nsACString& aCharset) const override;
-  };
-
-  nsresult SendInternal(const RequestBodyBase* aBody);
+    return Send(nullptr, aBody);
+  }
+  nsresult Send(const RequestBody& aBody)
+  {
+    return Send(Nullable<RequestBody>(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 = SendInternal(nullptr);
+    aRv = Send(Nullable<RequestBody>());
   }
 
   virtual void
   Send(JSContext* /*aCx*/, const ArrayBuffer& aArrayBuffer,
        ErrorResult& aRv) override
   {
-    RequestBody<const ArrayBuffer> body(&aArrayBuffer);
-    aRv = SendInternal(&body);
+    aRv = Send(RequestBody(&aArrayBuffer));
   }
 
   virtual void
   Send(JSContext* /*aCx*/, const ArrayBufferView& aArrayBufferView,
        ErrorResult& aRv) override
   {
-    RequestBody<const ArrayBufferView> body(&aArrayBufferView);
-    aRv = SendInternal(&body);
+    aRv = Send(RequestBody(&aArrayBufferView));
   }
 
   virtual void
   Send(JSContext* /*aCx*/, Blob& aBlob, ErrorResult& aRv) override
   {
-    RequestBody<Blob> body(&aBlob);
-    aRv = SendInternal(&body);
+    aRv = Send(RequestBody(aBlob));
   }
 
   virtual void Send(JSContext* /*aCx*/, URLSearchParams& aURLSearchParams,
                     ErrorResult& aRv) override
   {
-    RequestBody<URLSearchParams> body(&aURLSearchParams);
-    aRv = SendInternal(&body);
+    aRv = Send(RequestBody(aURLSearchParams));
   }
 
   virtual void
   Send(JSContext* /*aCx*/, nsIDocument& aDoc, ErrorResult& aRv) override
   {
-    RequestBody<nsIDocument> body(&aDoc);
-    aRv = SendInternal(&body);
+    aRv = Send(RequestBody(&aDoc));
   }
 
   virtual void
   Send(JSContext* aCx, const nsAString& aString, ErrorResult& aRv) override
   {
     if (DOMStringIsNull(aString)) {
       Send(aCx, aRv);
-    } else {
-      RequestBody<const nsAString> body(&aString);
-      aRv = SendInternal(&body);
+    }
+    else {
+      aRv = Send(RequestBody(aString));
     }
   }
 
   virtual void
   Send(JSContext* /*aCx*/, FormData& aFormData, ErrorResult& aRv) override
   {
-    RequestBody<FormData> body(&aFormData);
-    aRv = SendInternal(&body);
+    aRv = Send(RequestBody(aFormData));
   }
 
   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) {
@@ -353,18 +421,17 @@ public:
       if (ConvertJSValueToString(aCx, dataAsValue, eNull,
                                  eNull, dataAsString)) {
         Send(aCx, dataAsString, aRv);
       } else {
         aRv.Throw(NS_ERROR_FAILURE);
       }
       return;
     }
-    RequestBody<nsIInputStream> body(aStream);
-    aRv = SendInternal(&body);
+    aRv = Send(RequestBody(aStream));
   }
 
   void
   Abort() {
     ErrorResult rv;
     Abort(rv);
     MOZ_ASSERT(!rv.Failed());
   }
@@ -538,30 +605,27 @@ protected:
   bool IsSystemXHR() const;
 
   void ChangeStateToDone();
 
   void StartProgressEventTimer();
 
   nsresult OnRedirectVerifyCallback(nsresult result);
 
-  nsresult OpenInternal(const nsACString& aMethod,
-                        const nsACString& aUrl,
-                        const Optional<bool>& aAsync,
-                        const Optional<nsAString>& aUsername,
-                        const Optional<nsAString>& aPassword);
+  nsresult Open(const nsACString& method, const nsACString& url, bool async,
+                const Optional<nsAString>& user,
+                const Optional<nsAString>& password);
 
   already_AddRefed<nsXMLHttpRequestXPCOMifier> EnsureXPCOMifier();
 
   nsCOMPtr<nsISupports> mContext;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIChannel> mChannel;
-  nsCString mRequestMethod;
-  nsCOMPtr<nsIURI> mRequestURL;
   nsCOMPtr<nsIDocument> mResponseXML;
+  nsTArray<nsCString> mCORSUnsafeHeaders;
 
   nsCOMPtr<nsIStreamListener> mXMLParserStreamListener;
 
   // used to implement getAllResponseHeaders()
   class nsHeaderVisitor : public nsIHttpHeaderVisitor
   {
   public:
     NS_DECL_ISUPPORTS
@@ -722,23 +786,22 @@ protected:
   bool mIsMappedArrayBuffer;
 
   void ResetResponse();
 
   bool ShouldBlockAuthPrompt();
 
   struct RequestHeader
   {
-    nsCString name;
+    nsCString header;
     nsCString value;
   };
-  nsTArray<RequestHeader> mAuthorRequestHeaders;
+  nsTArray<RequestHeader> mModifiedRequestHeaders;
 
-  void GetAuthorRequestHeaderValue(const char* aName, nsACString& outValue);
-  void SetAuthorRequestHeadersOnChannel(nsCOMPtr<nsIHttpChannel> aChannel);
+  nsTHashtable<nsCStringHashKey> mAlreadySetHeaders;
 
   // Helper object to manage our XPCOM scriptability bits
   nsXMLHttpRequestXPCOMifier* mXPCOMifier;
 
   static bool sDontWarnAboutSyncXHR;
 };
 
 class MOZ_STACK_CLASS AutoDontWarnAboutSyncXHR
--- a/dom/xhr/XMLHttpRequestWorker.cpp
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -1493,38 +1493,34 @@ SendRunnable::RunOnMainThread(ErrorResul
   }
 
   mProxy->mWorkerPrivate = mWorkerPrivate;
 
   MOZ_ASSERT(!mProxy->mSyncLoopTarget);
   mProxy->mSyncLoopTarget.swap(mSyncLoopTarget);
 
   if (mHasUploadListeners) {
-    // Send() can be called more than once before failure,
-    // so don't attach the upload listeners more than once.
-    if (!mProxy->mUploadEventListenersAttached &&
-        !mProxy->AddRemoveEventListeners(true, true)) {
+    NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!");
+    if (!mProxy->AddRemoveEventListeners(true, true)) {
       MOZ_ASSERT(false, "This should never fail!");
     }
   }
 
   mProxy->mArrayBufferResponseWasTransferred = false;
 
   mProxy->mInnerChannelId++;
 
   aRv = mProxy->mXHR->Send(variant);
 
   if (!aRv.Failed()) {
     mProxy->mOutstandingSendCount++;
 
     if (!mHasUploadListeners) {
-      // Send() can be called more than once before failure,
-      // so don't attach the upload listeners more than once.
-      if (!mProxy->mUploadEventListenersAttached &&
-          !mProxy->AddRemoveEventListeners(true, true)) {
+      NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!");
+      if (!mProxy->AddRemoveEventListeners(true, true)) {
         MOZ_ASSERT(false, "This should never fail!");
       }
     }
   }
 }
 
 XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate)
 : mWorkerPrivate(aWorkerPrivate),
--- a/dom/xhr/tests/test_xhr_forbidden_headers.html
+++ b/dom/xhr/tests/test_xhr_forbidden_headers.html
@@ -23,16 +23,17 @@ var headers = [
   "aCCept-chaRset",
   "acCePt-eNcoDing",
   "aCcEsS-cOnTrOl-ReQuEsT-mEtHoD",
   "aCcEsS-cOnTrOl-ReQuEsT-hEaDeRs",
   "coNnEctIon",
   "coNtEnt-LEngth",
   "CoOKIe",
   "cOOkiE2",
+  "cOntEnt-tRAnsFer-enCoDiNg",
   "DATE",
   "dNT",
   "exPeCt",
   "hOSt",
   "keep-alive",
   "oRiGiN",
   "reFERer",
   "te",
@@ -48,17 +49,16 @@ var headers = [
 var i, request;
 
 function  startTest() {
   // Try setting headers in unprivileged context
   request = new XMLHttpRequest();
   request.open("GET", window.location.href);
   for (i = 0; i < headers.length; i++)
     request.setRequestHeader(headers[i], "test" + i);
-  request.send(); // headers aren't set on the channel until send()
 
   // Read out headers
   var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
   for (i = 0; i < headers.length; i++) {
     // Retrieving Content-Length will throw an exception
     var value = null;
     try {
       value = channel.getRequestHeader(headers[i]);
@@ -68,17 +68,16 @@ function  startTest() {
     isnot(value, "test" + i, "Setting " + headers[i] + " header in unprivileged context");
   }
 
   // Try setting headers in privileged context
   request = new XMLHttpRequest({mozAnon: true, mozSystem: true});
   request.open("GET", window.location.href);
   for (i = 0; i < headers.length; i++)
     request.setRequestHeader(headers[i], "test" + i);
-  request.send(); // headers aren't set on the channel until send()
 
   // Read out headers
   var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
   for (i = 0; i < headers.length; i++) {
     var value = channel.getRequestHeader(headers[i]);
     is(value, "test" + i, "Setting " + headers[i] + " header in privileged context");
   }
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/XMLHttpRequest/setrequestheader-case-insensitive.htm.ini
@@ -0,0 +1,5 @@
+[setrequestheader-case-insensitive.htm]
+  type: testharness
+  [XMLHttpRequest: setRequestHeader() - headers that differ in case]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/XMLHttpRequest/setrequestheader-header-allowed.htm.ini
@@ -0,0 +1,14 @@
+[setrequestheader-header-allowed.htm]
+  type: testharness
+  [XMLHttpRequest: setRequestHeader() - headers that are allowed (Authorization)]
+    expected: FAIL
+
+  [XMLHttpRequest: setRequestHeader() - headers that are allowed (Content-Transfer-Encoding)]
+    expected: FAIL
+
+  [XMLHttpRequest: setRequestHeader() - headers that are allowed (Content-Type)]
+    expected: FAIL
+
+  [XMLHttpRequest: setRequestHeader() - headers that are allowed (User-Agent)]
+    expected: FAIL
+