Backed out 5 changesets (bug 1305162) for permatimeout in e10s browser_action_keyword.js
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 21 Jan 2017 09:41:03 -0800
changeset 377828 81824fdf77fb731a127aee0b55fdcbd00e084c25
parent 377827 f4b8933f62eafb86402ad92bfe8361cf91b31ced
child 377829 e78066796efe05df7b2229e759fa9eb63dc3ec14
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1305162
milestone53.0a1
backs outc633650caea682de11142d5cb6d28f3effcadf55
1a23ce5a17c338a9ef902d4f30e586ad4444af36
d1095e03dfc600771f54c7ff2dc5fd294ca1ab28
1202edf6008d7c4da0976c78ed88cec7aa4f05d1
de131f7c1fc1f60db42509aea646ef4540e6c5fe
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 5 changesets (bug 1305162) for permatimeout in e10s browser_action_keyword.js Backed out changeset c633650caea6 (bug 1305162) Backed out changeset 1a23ce5a17c3 (bug 1305162) Backed out changeset d1095e03dfc6 (bug 1305162) Backed out changeset 1202edf6008d (bug 1305162) Backed out changeset de131f7c1fc1 (bug 1305162)
devtools/client/netmonitor/test/browser_net_curl-utils.js
devtools/client/webconsole/test/browser_webconsole_netlogging.js
ipc/glue/InputStreamParams.ipdlh
netwerk/base/nsIMIMEInputStream.idl
netwerk/base/nsMIMEInputStream.cpp
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html
toolkit/components/remotebrowserutils/RemoteWebNavigation.js
toolkit/content/browser-child.js
toolkit/modules/addons/WebRequest.jsm
toolkit/modules/addons/WebRequestUpload.jsm
--- a/devtools/client/netmonitor/test/browser_net_curl-utils.js
+++ b/devtools/client/netmonitor/test/browser_net_curl-utils.js
@@ -36,25 +36,20 @@ add_task(function* () {
 
   data = yield createCurlData(requests.post, gNetwork);
   testIsUrlEncodedRequest(data);
   testWritePostDataTextParams(data);
 
   data = yield createCurlData(requests.multipart, gNetwork);
   testIsMultipartRequest(data);
   testGetMultipartBoundary(data);
-  testMultiPartHeaders(data);
   testRemoveBinaryDataFromMultipartText(data);
 
   data = yield createCurlData(requests.multipartForm, gNetwork);
-  testMultiPartHeaders(data);
-
-  testGetHeadersFromMultipartText({
-    postDataText: "Content-Type: text/plain\r\n\r\n",
-  });
+  testGetHeadersFromMultipartText(data);
 
   if (Services.appinfo.OS != "WINNT") {
     testEscapeStringPosix();
   } else {
     testEscapeStringWin();
   }
 
   yield teardown(monitor);
@@ -79,24 +74,16 @@ function testFindHeader(data) {
   is(hostName, "example.com",
     "Header with name 'Host' should be found in the request array.");
   is(requestedWithLowerCased, "XMLHttpRequest",
     "The search should be case insensitive.");
   is(doesNotExist, null,
     "Should return null when a header is not found.");
 }
 
-function testMultiPartHeaders(data) {
-  let headers = data.headers;
-  let contentType = CurlUtils.findHeader(headers, "Content-Type");
-
-  ok(contentType.startsWith("multipart/form-data; boundary="),
-     "Multi-part content type header is present in headers array");
-}
-
 function testWritePostDataTextParams(data) {
   let params = CurlUtils.writePostDataTextParams(data.postDataText);
   is(params, "param1=value1&param2=value2&param3=value3",
     "Should return a serialized representation of the request parameters");
 }
 
 function testGetMultipartBoundary(data) {
   let boundary = CurlUtils.getMultipartBoundary(data);
--- a/devtools/client/webconsole/test/browser_webconsole_netlogging.js
+++ b/devtools/client/webconsole/test/browser_webconsole_netlogging.js
@@ -113,28 +113,23 @@ add_task(function* testFormSubmission() 
     form.submit();
   }`);
   let request = yield finishedRequest;
 
   ok(request, "testFormSubmission() was logged");
 
   let client = hud.ui.webConsoleClient;
   const postData = yield client.getRequestPostData(request.actor);
-  const requestHeaders = yield client.getRequestHeaders(request.actor);
   const responseContent = yield client.getResponseContent(request.actor);
 
-  let getHeader = name => {
-    let header = requestHeaders.headers.find(h => h.name == name);
-    return header && header.value;
-  };
-
   is(request.request.method, "POST", "Method is correct");
-  is(getHeader("Content-Type"), "application/x-www-form-urlencoded",
-     "Content-Type is correct");
-  is(getHeader("Content-Length"), "20",
-     "Content-length is correct");
+  isnot(postData.postData.text
+    .indexOf("Content-Type: application/x-www-form-urlencoded"), -1,
+    "Content-Type is correct");
+  isnot(postData.postData.text
+    .indexOf("Content-Length: 20"), -1, "Content-length is correct");
   isnot(postData.postData.text
     .indexOf("name=foo+bar&age=144"), -1, "Form data is correct");
   is(responseContent.content.text.indexOf("<!DOCTYPE HTML>"), 0,
     "Response body's beginning is okay");
 
   yield closeTabAndToolbox();
 });
--- a/ipc/glue/InputStreamParams.ipdlh
+++ b/ipc/glue/InputStreamParams.ipdlh
@@ -6,22 +6,16 @@ include protocol PBlob;
 include ProtocolTypes;
 
 using struct mozilla::void_t
   from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace ipc {
 
-struct HeaderEntry
-{
-  nsCString name;
-  nsCString value;
-};
-
 struct StringInputStreamParams
 {
   nsCString data;
 };
 
 struct FileInputStreamParams
 {
   uint32_t fileDescriptorIndex;
@@ -87,14 +81,16 @@ struct BufferedInputStreamParams
 {
   OptionalInputStreamParams optionalStream;
   uint32_t bufferSize;
 };
 
 struct MIMEInputStreamParams
 {
   OptionalInputStreamParams optionalStream;
-  HeaderEntry[] headers;
+  nsCString headers;
+  nsCString contentLength;
   bool startedReading;
+  bool addContentLength;
 };
 
 } // namespace ipc
 } // namespace mozilla
--- a/netwerk/base/nsIMIMEInputStream.idl
+++ b/netwerk/base/nsIMIMEInputStream.idl
@@ -1,55 +1,41 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "nsIHttpHeaderVisitor.idl"
 #include "nsIInputStream.idl"
 
 /**
  * The MIME stream separates headers and a datastream. It also allows
  * automatic creation of the content-length header.
  */
 
 [scriptable, uuid(dcbce63c-1dd1-11b2-b94d-91f6d49a3161)]
 interface nsIMIMEInputStream : nsIInputStream
 {
     /**
      * When true a "Content-Length" header is automatically added to the
      * stream. The value of the content-length is automatically calculated
      * using the available() method on the data stream. The value is
      * recalculated every time the stream is rewinded to the start.
      * Not allowed to be changed once the stream has been started to be read.
-     *
-     * @deprecated A Content-Length header is automatically added when
-     * attaching the stream to a channel, so this setting no longer has any
-     * effect, and may not be set to false.
      */
     attribute boolean addContentLength;
 
     /**
      * Adds an additional header to the stream on the form "name: value". May
      * not be called once the stream has been started to be read.
      * @param name   name of the header
      * @param value  value of the header
      */
     void addHeader(in string name, in string value);
 
     /**
-     * Visits all headers which have been added via addHeader.  Calling
-     * addHeader while visiting request headers has undefined behavior.
-     *
-     * @param aVisitor
-     *        The header visitor instance.
-     */
-    void visitHeaders(in nsIHttpHeaderVisitor visitor);
-
-    /**
      * Sets data-stream. May not be called once the stream has been started
      * to be read.
      * The cursor of the new stream should be located at the beginning of the
      * stream if the implementation of the nsIMIMEInputStream also is used as
      * an nsISeekableStream.
      * @param stream  stream containing the data for the stream
      */
     void setData(in nsIInputStream stream);
--- a/netwerk/base/nsMIMEInputStream.cpp
+++ b/netwerk/base/nsMIMEInputStream.cpp
@@ -7,61 +7,68 @@
  * The MIME stream separates headers and a datastream. It also allows
  * automatic creation of the content-length header.
  */
 
 #include "ipc/IPCMessageUtils.h"
 
 #include "nsCOMPtr.h"
 #include "nsComponentManagerUtils.h"
-#include "nsIHttpHeaderVisitor.h"
+#include "nsIMultiplexInputStream.h"
 #include "nsIMIMEInputStream.h"
 #include "nsISeekableStream.h"
+#include "nsIStringStream.h"
 #include "nsString.h"
 #include "nsMIMEInputStream.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIIPCSerializableInputStream.h"
-#include "mozilla/Move.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 
 using namespace mozilla::ipc;
 using mozilla::Maybe;
-using mozilla::Move;
 
 class nsMIMEInputStream : public nsIMIMEInputStream,
                           public nsISeekableStream,
                           public nsIIPCSerializableInputStream
 {
     virtual ~nsMIMEInputStream();
 
 public:
     nsMIMEInputStream();
 
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIINPUTSTREAM
     NS_DECL_NSIMIMEINPUTSTREAM
     NS_DECL_NSISEEKABLESTREAM
     NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
 
+    nsresult Init();
+
 private:
 
     void InitStreams();
 
     struct MOZ_STACK_CLASS ReadSegmentsState {
         nsCOMPtr<nsIInputStream> mThisStream;
         nsWriteSegmentFun mWriter;
         void* mClosure;
     };
     static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
                               const char* aFromRawSegment, uint32_t aToOffset,
                               uint32_t aCount, uint32_t *aWriteCount);
 
-    nsTArray<HeaderEntry> mHeaders;
-
-    nsCOMPtr<nsIInputStream> mStream;
+    nsCString mHeaders;
+    nsCOMPtr<nsIStringInputStream> mHeaderStream;
+    
+    nsCString mContentLength;
+    nsCOMPtr<nsIStringInputStream> mCLStream;
+    
+    nsCOMPtr<nsIInputStream> mData;
+    nsCOMPtr<nsIMultiplexInputStream> mStream;
+    bool mAddContentLength;
     bool mStartedReading;
 };
 
 NS_IMPL_ADDREF(nsMIMEInputStream)
 NS_IMPL_RELEASE(nsMIMEInputStream)
 
 NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE,
                   NS_MIMEINPUTSTREAM_CID)
@@ -71,117 +78,140 @@ NS_IMPL_QUERY_INTERFACE_CI(nsMIMEInputSt
                            nsIInputStream,
                            nsISeekableStream,
                            nsIIPCSerializableInputStream)
 NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream,
                             nsIMIMEInputStream,
                             nsIInputStream,
                             nsISeekableStream)
 
-nsMIMEInputStream::nsMIMEInputStream() : mStartedReading(false)
+nsMIMEInputStream::nsMIMEInputStream() : mAddContentLength(false),
+                                         mStartedReading(false)
 {
 }
 
 nsMIMEInputStream::~nsMIMEInputStream()
 {
 }
 
+nsresult nsMIMEInputStream::Init()
+{
+    nsresult rv = NS_OK;
+    mStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1",
+                                &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mHeaderStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1",
+                                      &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    mCLStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mStream->AppendStream(mHeaderStream);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mStream->AppendStream(mCLStream);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+}
+
+
 NS_IMETHODIMP
 nsMIMEInputStream::GetAddContentLength(bool *aAddContentLength)
 {
-    *aAddContentLength = true;
+    *aAddContentLength = mAddContentLength;
     return NS_OK;
 }
 NS_IMETHODIMP
 nsMIMEInputStream::SetAddContentLength(bool aAddContentLength)
 {
     NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
-    if (!aAddContentLength) {
-      // Content-Length is automatically added by the channel when setting the
-      // upload stream, so setting this to false has no practical effect.
-      return NS_ERROR_FAILURE;
-    }
+    mAddContentLength = aAddContentLength;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMIMEInputStream::AddHeader(const char *aName, const char *aValue)
 {
     NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+    mHeaders.Append(aName);
+    mHeaders.AppendLiteral(": ");
+    mHeaders.Append(aValue);
+    mHeaders.AppendLiteral("\r\n");
 
-    HeaderEntry* entry = mHeaders.AppendElement();
-    entry->name().Append(aName);
-    entry->value().Append(aValue);
+    // Just in case someone somehow uses our stream, lets at least
+    // let the stream have a valid pointer. The stream will be properly
+    // initialized in nsMIMEInputStream::InitStreams
+    mHeaderStream->ShareData(mHeaders.get(), 0);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsMIMEInputStream::VisitHeaders(nsIHttpHeaderVisitor *visitor)
-{
-  nsresult rv;
-
-  for (auto& header : mHeaders) {
-    rv = visitor->VisitHeader(header.name(), header.value());
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsMIMEInputStream::SetData(nsIInputStream *aStream)
 {
     NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
+    // Remove the old stream if there is one
+    if (mData)
+        mStream->RemoveStream(2);
 
-    nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
-    if (!seekable) {
-      return NS_ERROR_INVALID_ARG;
-    }
-
-    mStream = aStream;
+    mData = aStream;
+    if (aStream)
+        mStream->AppendStream(mData);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMIMEInputStream::GetData(nsIInputStream **aStream)
 {
   NS_ENSURE_ARG_POINTER(aStream);
-  *aStream = mStream;
+  *aStream = mData;
   NS_IF_ADDREF(*aStream);
   return NS_OK;
 }
 
 // set up the internal streams
 void nsMIMEInputStream::InitStreams()
 {
     NS_ASSERTION(!mStartedReading,
                  "Don't call initStreams twice without rewinding");
 
     mStartedReading = true;
+
+    // We'll use the content-length stream to add the final \r\n
+    if (mAddContentLength) {
+        uint64_t cl = 0;
+        if (mData) {
+            mData->Available(&cl);
+        }
+        mContentLength.AssignLiteral("Content-Length: ");
+        mContentLength.AppendInt(cl);
+        mContentLength.AppendLiteral("\r\n\r\n");
+    }
+    else {
+        mContentLength.AssignLiteral("\r\n");
+    }
+    mCLStream->ShareData(mContentLength.get(), -1);
+    mHeaderStream->ShareData(mHeaders.get(), -1);
 }
 
 
 
 #define INITSTREAMS         \
 if (!mStartedReading) {     \
-    NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); \
     InitStreams();          \
 }
 
 // Reset mStartedReading when Seek-ing to start
 NS_IMETHODIMP
 nsMIMEInputStream::Seek(int32_t whence, int64_t offset)
 {
-    NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED);
-
     nsresult rv;
     nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
-
     if (whence == NS_SEEK_SET && offset == 0) {
         rv = stream->Seek(whence, offset);
         if (NS_SUCCEEDED(rv))
             mStartedReading = false;
     }
     else {
         INITSTREAMS;
         rv = stream->Seek(whence, offset);
@@ -249,44 +279,60 @@ NS_IMETHODIMP nsMIMEInputStream::SetEOF(
 nsresult
 nsMIMEInputStreamConstructor(nsISupports *outer, REFNSIID iid, void **result)
 {
     *result = nullptr;
 
     if (outer)
         return NS_ERROR_NO_AGGREGATION;
 
-    RefPtr<nsMIMEInputStream> inst = new nsMIMEInputStream();
+    nsMIMEInputStream *inst = new nsMIMEInputStream();
     if (!inst)
         return NS_ERROR_OUT_OF_MEMORY;
 
-    return inst->QueryInterface(iid, result);
+    NS_ADDREF(inst);
+
+    nsresult rv = inst->Init();
+    if (NS_FAILED(rv)) {
+        NS_RELEASE(inst);
+        return rv;
+    }
+
+    rv = inst->QueryInterface(iid, result);
+    NS_RELEASE(inst);
+
+    return rv;
 }
 
 void
 nsMIMEInputStream::Serialize(InputStreamParams& aParams,
                              FileDescriptorArray& aFileDescriptors)
 {
     MIMEInputStreamParams params;
 
-    if (mStream) {
+    if (mData) {
+        nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mData);
+        MOZ_ASSERT(stream);
+
         InputStreamParams wrappedParams;
-        SerializeInputStream(mStream, wrappedParams, aFileDescriptors);
+        SerializeInputStream(stream, wrappedParams, aFileDescriptors);
 
         NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None,
                      "Wrapped stream failed to serialize!");
 
         params.optionalStream() = wrappedParams;
     }
     else {
         params.optionalStream() = mozilla::void_t();
     }
 
     params.headers() = mHeaders;
+    params.contentLength() = mContentLength;
     params.startedReading() = mStartedReading;
+    params.addContentLength() = mAddContentLength;
 
     aParams = params;
 }
 
 bool
 nsMIMEInputStream::Deserialize(const InputStreamParams& aParams,
                                const FileDescriptorArray& aFileDescriptors)
 {
@@ -295,34 +341,48 @@ nsMIMEInputStream::Deserialize(const Inp
         return false;
     }
 
     const MIMEInputStreamParams& params =
         aParams.get_MIMEInputStreamParams();
     const OptionalInputStreamParams& wrappedParams = params.optionalStream();
 
     mHeaders = params.headers();
+    mContentLength = params.contentLength();
     mStartedReading = params.startedReading();
 
+    // nsMIMEInputStream::Init() already appended mHeaderStream & mCLStream
+    mHeaderStream->ShareData(mHeaders.get(),
+                             mStartedReading ? mHeaders.Length() : 0);
+    mCLStream->ShareData(mContentLength.get(),
+                         mStartedReading ? mContentLength.Length() : 0);
+
+    nsCOMPtr<nsIInputStream> stream;
     if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) {
-        nsCOMPtr<nsIInputStream> stream;
         stream = DeserializeInputStream(wrappedParams.get_InputStreamParams(),
                                         aFileDescriptors);
         if (!stream) {
             NS_WARNING("Failed to deserialize wrapped stream!");
             return false;
         }
 
-        mStream = stream;
+        mData = stream;
+
+        if (NS_FAILED(mStream->AppendStream(mData))) {
+            NS_WARNING("Failed to append stream!");
+            return false;
+        }
     }
     else {
         NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t,
                      "Unknown type for OptionalInputStreamParams!");
     }
 
+    mAddContentLength = params.addContentLength();
+
     return true;
 }
 
 Maybe<uint64_t>
 nsMIMEInputStream::ExpectedSerializedLength()
 {
     nsCOMPtr<nsIIPCSerializableInputStream> serializable = do_QueryInterface(mStream);
     return serializable ? serializable->ExpectedSerializedLength() : Nothing();
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -47,108 +47,27 @@
 #include "nsILoadGroupChild.h"
 #include "mozilla/ConsoleReportCollector.h"
 #include "LoadInfo.h"
 #include "nsISSLSocketControl.h"
 #include "mozilla/Telemetry.h"
 #include "nsIURL.h"
 #include "nsIConsoleService.h"
 #include "mozilla/BinarySearch.h"
-#include "mozilla/DebugOnly.h"
 #include "nsIHttpHeaderVisitor.h"
-#include "nsIMIMEInputStream.h"
 #include "nsIXULRuntime.h"
 #include "nsICacheInfoChannel.h"
 #include "nsIDOMWindowUtils.h"
 
 #include <algorithm>
 #include "HttpBaseChannel.h"
 
 namespace mozilla {
 namespace net {
 
-static
-bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader)
-{
-  // IMPORTANT: keep this list ASCII-code sorted
-  static nsHttpAtom const* blackList[] = {
-    &nsHttp::Accept,
-    &nsHttp::Accept_Encoding,
-    &nsHttp::Accept_Language,
-    &nsHttp::Authentication,
-    &nsHttp::Authorization,
-    &nsHttp::Connection,
-    &nsHttp::Content_Length,
-    &nsHttp::Cookie,
-    &nsHttp::Host,
-    &nsHttp::If,
-    &nsHttp::If_Match,
-    &nsHttp::If_Modified_Since,
-    &nsHttp::If_None_Match,
-    &nsHttp::If_None_Match_Any,
-    &nsHttp::If_Range,
-    &nsHttp::If_Unmodified_Since,
-    &nsHttp::Proxy_Authenticate,
-    &nsHttp::Proxy_Authorization,
-    &nsHttp::Range,
-    &nsHttp::TE,
-    &nsHttp::Transfer_Encoding,
-    &nsHttp::Upgrade,
-    &nsHttp::User_Agent,
-    &nsHttp::WWW_Authenticate
-  };
-
-  class HttpAtomComparator
-  {
-    nsHttpAtom const& mTarget;
-  public:
-    explicit HttpAtomComparator(nsHttpAtom const& aTarget)
-      : mTarget(aTarget) {}
-    int operator()(nsHttpAtom const* aVal) const {
-      if (mTarget == *aVal) {
-        return 0;
-      }
-      return strcmp(mTarget._val, aVal->_val);
-    }
-  };
-
-  size_t unused;
-  return BinarySearchIf(blackList, 0, ArrayLength(blackList),
-                        HttpAtomComparator(aHeader), &unused);
-}
-
-class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  explicit AddHeadersToChannelVisitor(nsIHttpChannel *aChannel)
-    : mChannel(aChannel)
-  {
-  }
-
-  NS_IMETHOD VisitHeader(const nsACString& aHeader,
-                         const nsACString& aValue) override
-  {
-    nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
-    if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
-      mChannel->SetRequestHeader(aHeader, aValue, false);
-    }
-    return NS_OK;
-  }
-private:
-  ~AddHeadersToChannelVisitor()
-  {
-  }
-
-  nsCOMPtr<nsIHttpChannel> mChannel;
-};
-
-NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
-
 HttpBaseChannel::HttpBaseChannel()
   : mStartPos(UINT64_MAX)
   , mStatus(NS_OK)
   , mLoadFlags(LOAD_NORMAL)
   , mCaps(0)
   , mClassOfService(0)
   , mPriority(PRIORITY_NORMAL)
   , mRedirectionLimit(gHttpHandler->RedirectionLimit())
@@ -729,48 +648,32 @@ HttpBaseChannel::SetUploadStream(nsIInpu
   // plugins, |stream| may include headers, specifically Content-Type and
   // Content-Length headers.  in this case, |contentType| and |contentLength|
   // would be unspecified.  this is traditionally the case of a POST request,
   // and so we select POST as the request method if contentType and
   // contentLength are unspecified.
 
   if (stream) {
     nsAutoCString method;
-    bool hasHeaders = false;
+    bool hasHeaders;
 
     // This method and ExplicitSetUploadStream mean different things by "empty
     // content type string".  This method means "no header", but
     // ExplicitSetUploadStream means "header with empty value".  So we have to
     // massage the contentType argument into the form ExplicitSetUploadStream
     // expects.
-    nsCOMPtr<nsIMIMEInputStream> mimeStream;
-    nsCString contentType(contentTypeArg);
-    if (contentType.IsEmpty()) {
-      contentType.SetIsVoid(true);
+    nsAutoCString contentType;
+    if (contentTypeArg.IsEmpty()) {
       method = NS_LITERAL_CSTRING("POST");
-
-      // MIME streams are a special case, and include headers which need to be
-      // copied to the channel.
-      mimeStream = do_QueryInterface(stream);
-      if (mimeStream) {
-        // Copy non-origin related headers to the channel.
-        nsCOMPtr<nsIHttpHeaderVisitor> visitor =
-          new AddHeadersToChannelVisitor(this);
-        mimeStream->VisitHeaders(visitor);
-
-        return ExplicitSetUploadStream(stream, contentType, contentLength,
-                                       method, hasHeaders);
-      }
-
       hasHeaders = true;
+      contentType.SetIsVoid(true);
     } else {
       method = NS_LITERAL_CSTRING("PUT");
-
-      MOZ_ASSERT(NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))),
-                 "nsIMIMEInputStream should not be set with an explicit content type");
+      hasHeaders = false;
+      contentType = contentTypeArg;
     }
     return ExplicitSetUploadStream(stream, contentType, contentLength,
                                    method, hasHeaders);
   }
 
   // if stream is null, ExplicitSetUploadStream returns error.
   // So we need special case for GET method.
   mUploadStreamHasHeaders = false;
@@ -902,23 +805,16 @@ HttpBaseChannel::ExplicitSetUploadStream
                                        const nsACString &aContentType,
                                        int64_t aContentLength,
                                        const nsACString &aMethod,
                                        bool aStreamHasHeaders)
 {
   // Ensure stream is set and method is valid
   NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
 
-  {
-    DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
-    MOZ_ASSERT(!aStreamHasHeaders ||
-               NS_FAILED(CallQueryInterface(aStream, getter_AddRefs(mimeStream.value))),
-               "nsIMIMEInputStream should not include headers");
-  }
-
   if (aContentLength < 0 && !aStreamHasHeaders) {
     nsresult rv = aStream->Available(reinterpret_cast<uint64_t*>(&aContentLength));
     if (NS_FAILED(rv) || aContentLength < 0) {
       NS_ERROR("unable to determine content length");
       return NS_ERROR_FAILURE;
     }
   }
 
@@ -2992,16 +2888,95 @@ HttpBaseChannel::ShouldRewriteRedirectTo
   // rewrite for 303 unless it was HEAD
   if (httpStatus == 303)
     return method != nsHttpRequestHead::kMethod_Head;
 
   // otherwise, such as for 307, do not rewrite
   return false;
 }
 
+static
+bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader)
+{
+  // IMPORTANT: keep this list ASCII-code sorted
+  static nsHttpAtom const* blackList[] = {
+    &nsHttp::Accept,
+    &nsHttp::Accept_Encoding,
+    &nsHttp::Accept_Language,
+    &nsHttp::Authentication,
+    &nsHttp::Authorization,
+    &nsHttp::Connection,
+    &nsHttp::Content_Length,
+    &nsHttp::Cookie,
+    &nsHttp::Host,
+    &nsHttp::If,
+    &nsHttp::If_Match,
+    &nsHttp::If_Modified_Since,
+    &nsHttp::If_None_Match,
+    &nsHttp::If_None_Match_Any,
+    &nsHttp::If_Range,
+    &nsHttp::If_Unmodified_Since,
+    &nsHttp::Proxy_Authenticate,
+    &nsHttp::Proxy_Authorization,
+    &nsHttp::Range,
+    &nsHttp::TE,
+    &nsHttp::Transfer_Encoding,
+    &nsHttp::Upgrade,
+    &nsHttp::User_Agent,
+    &nsHttp::WWW_Authenticate
+  };
+
+  class HttpAtomComparator
+  {
+    nsHttpAtom const& mTarget;
+  public:
+    explicit HttpAtomComparator(nsHttpAtom const& aTarget)
+      : mTarget(aTarget) {}
+    int operator()(nsHttpAtom const* aVal) const {
+      if (mTarget == *aVal) {
+        return 0;
+      }
+      return strcmp(mTarget._val, aVal->_val);
+    }
+  };
+
+  size_t unused;
+  return BinarySearchIf(blackList, 0, ArrayLength(blackList),
+                        HttpAtomComparator(aHeader), &unused);
+}
+
+class SetupReplacementChannelHeaderVisitor final : public nsIHttpHeaderVisitor
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit SetupReplacementChannelHeaderVisitor(nsIHttpChannel *aChannel)
+    : mChannel(aChannel)
+  {
+  }
+
+  NS_IMETHOD VisitHeader(const nsACString& aHeader,
+                         const nsACString& aValue) override
+  {
+    nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+    if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
+      mChannel->SetRequestHeader(aHeader, aValue, false);
+    }
+    return NS_OK;
+  }
+private:
+  ~SetupReplacementChannelHeaderVisitor()
+  {
+  }
+
+  nsCOMPtr<nsIHttpChannel> mChannel;
+};
+
+NS_IMPL_ISUPPORTS(SetupReplacementChannelHeaderVisitor, nsIHttpHeaderVisitor)
+
 nsresult
 HttpBaseChannel::SetupReplacementChannel(nsIURI       *newURI,
                                          nsIChannel   *newChannel,
                                          bool          preserveMethod,
                                          uint32_t      redirectFlags)
 {
   LOG(("HttpBaseChannel::SetupReplacementChannel "
      "[this=%p newChannel=%p preserveMethod=%d]",
@@ -3287,17 +3262,17 @@ HttpBaseChannel::SetupReplacementChannel
   if (cacheInfoChan) {
     cacheInfoChan->PreferAlternativeDataType(mPreferredCachedAltDataType);
   }
 
   if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
                        nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
     // Copy non-origin related headers to the new channel.
     nsCOMPtr<nsIHttpHeaderVisitor> visitor =
-      new AddHeadersToChannelVisitor(httpChannel);
+      new SetupReplacementChannelHeaderVisitor(httpChannel);
     mRequestHead.VisitHeaders(visitor);
   }
 
   // This channel has been redirected. Don't report timing info.
   mTimingEnabled = false;
   return NS_OK;
 }
 
--- a/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
+++ b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
@@ -144,33 +144,25 @@ CustomChannel.prototype = {
 <script type="text/javascript">
 <!--
 document.getElementById('form').submit();
 //-->
 </script>
 `;
     } else if (this.uri.spec.startsWith(ACTION_BASE)) {
       var postData = "";
-      var headers = {};
       if (this._uploadStream) {
         var bstream = Cc["@mozilla.org/binaryinputstream;1"]
             .createInstance(Ci.nsIBinaryInputStream);
         bstream.setInputStream(this._uploadStream);
         postData = bstream.readBytes(bstream.available());
-
-        if (this._uploadStream instanceof Ci.nsIMIMEInputStream) {
-          this._uploadStream.visitHeaders((name, value) => {
-            headers[name] = value;
-          });
-        }
       }
       data += `
 <input id="upload_stream" value="${this._uploadStream ? "yes" : "no"}">
 <input id="post_data" value="${btoa(postData)}">
-<input id="upload_headers" value='${JSON.stringify(headers)}'>
 `;
     }
 
     data += `
 </body>
 </html>
 `;
 
@@ -217,19 +209,18 @@ document.getElementById('form').submit()
 function frameScript() {
   addMessageListener("Test:WaitForIFrame", function() {
     var check = function() {
       if (content) {
         var frame = content.document.getElementById("frame");
         if (frame) {
           var upload_stream = frame.contentDocument.getElementById("upload_stream");
           var post_data = frame.contentDocument.getElementById("post_data");
-          var headers = frame.contentDocument.getElementById("upload_headers");
-          if (upload_stream && post_data && headers) {
-            sendAsyncMessage("Test:IFrameLoaded", [upload_stream.value, post_data.value, headers.value]);
+          if (upload_stream && post_data) {
+            sendAsyncMessage("Test:IFrameLoaded", [upload_stream.value, post_data.value]);
             return;
           }
         }
       }
 
       setTimeout(check, 100);
     };
 
@@ -240,19 +231,19 @@ function frameScript() {
 function loadTestTab(uri) {
   gBrowser.selectedTab = gBrowser.addTab(uri);
   var browser = gBrowser.selectedBrowser;
 
   let manager = browser.messageManager;
   browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
 
   return new Promise(resolve => {
-    function listener({ data: [hasUploadStream, postData, headers] }) {
+    function listener({ data: [hasUploadStream, postData] }) {
       manager.removeMessageListener("Test:IFrameLoaded", listener);
-      resolve([hasUploadStream, atob(postData), JSON.parse(headers)]);
+      resolve([hasUploadStream, atob(postData)]);
     }
 
     manager.addMessageListener("Test:IFrameLoaded", listener);
     manager.sendAsyncMessage("Test:WaitForIFrame");
   });
 }
 
 add_task(function*() {
@@ -276,19 +267,18 @@ add_task(function*() {
 add_task(function*() {
   var [hasUploadStream, postData] = yield loadTestTab(UPLOAD_FORM_URI);
   is(hasUploadStream, "no", "upload action should not have uploadStream");
 
   gBrowser.removeCurrentTab();
 });
 
 add_task(function*() {
-  var [hasUploadStream, postData, headers] = yield loadTestTab(POST_FORM_URI);
-
+  var [hasUploadStream, postData] = yield loadTestTab(POST_FORM_URI);
   is(hasUploadStream, "yes", "post action should have uploadStream");
-  is(postData, "foo=bar\r\n",
-     "POST data is received correctly");
-
-  is(headers["Content-Type"], "text/plain", "Content-Type header is correct");
-  is(headers["Content-Length"], undefined, "Content-Length header is correct");
+  is(postData,
+     "Content-Type: text/plain\r\n" +
+     "Content-Length: 9\r\n" +
+     "\r\n" +
+     "foo=bar\r\n", "POST data is received correctly");
 
   gBrowser.removeCurrentTab();
 });
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html
@@ -64,23 +64,24 @@ add_task(function* test_setup() {
     [blob.name]: blob,
     [file.name]: file,
   };
 });
 
 function background() {
   const FILTERS = {urls: ["<all_urls>"]};
 
+  let requestBodySupported = true;
+
   function onUpload(details) {
     let url = new URL(details.url);
     let upload = url.searchParams.get("upload");
-    if (!upload) {
+    if (!upload || !requestBodySupported) {
       return;
     }
-
     let requestBody = details.requestBody;
     browser.test.log(`onBeforeRequest upload: ${details.url} ${JSON.stringify(details.requestBody)}`);
     browser.test.assertTrue(!!requestBody, `Intercepted upload ${details.url} #${details.requestId} ${upload} have a requestBody`);
     if (!requestBody) {
       return;
     }
     let byteLength = parseInt(upload, 10);
     if (byteLength) {
@@ -104,18 +105,31 @@ function background() {
     FILTERS);
 
   let onBeforeRequest = details => {
     browser.test.log(`${name} ${details.requestId} ${details.url}`);
 
     onUpload(details);
   };
 
-  browser.webRequest.onBeforeRequest.addListener(
-    onBeforeRequest, FILTERS, ["requestBody"]);
+  try {
+    browser.webRequest.onBeforeRequest.addListener(
+      onBeforeRequest, FILTERS, ["requestBody"]);
+  } catch (e) {
+    browser.test.assertTrue(/\brequestBody\b/.test(e.message),
+                            "Request body is unsupported");
+
+    // requestBody is disabled in release builds
+    if (!/\brequestBody\b/.test(e.message)) {
+      throw e;
+    }
+
+    browser.webRequest.onBeforeRequest.addListener(
+      onBeforeRequest, FILTERS);
+  }
 }
 
 add_task(function* test_xhr_forms() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: [
         "webRequest",
         "webRequestBlocking",
--- a/toolkit/components/remotebrowserutils/RemoteWebNavigation.js
+++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.js
@@ -13,29 +13,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   "resource://gre/modules/sessionstore/Utils.jsm");
 
 function makeURI(url) {
   return Services.io.newURI(url);
 }
 
-function serializeInputStream(aStream) {
-  let data = {
-    content: NetUtil.readInputStreamToString(aStream, aStream.available()),
-  };
-
-  if (aStream instanceof Ci.nsIMIMEInputStream) {
-    data.headers = new Map();
-    aStream.visitHeaders((name, value) => {
-      data.headers.set(name, value);
-    });
-  }
-
-  return data;
+function readInputStreamToString(aStream) {
+  return NetUtil.readInputStreamToString(aStream, aStream.available());
 }
 
 function RemoteWebNavigation() {
   this.wrappedJSObject = this;
 }
 
 RemoteWebNavigation.prototype = {
   classDescription: "nsIWebNavigation for remote browsers",
@@ -87,18 +76,18 @@ RemoteWebNavigation.prototype = {
   },
   loadURIWithOptions(aURI, aLoadFlags, aReferrer, aReferrerPolicy,
                      aPostData, aHeaders, aBaseURI, aTriggeringPrincipal) {
     this._sendMessage("WebNavigation:LoadURI", {
       uri: aURI,
       flags: aLoadFlags,
       referrer: aReferrer ? aReferrer.spec : null,
       referrerPolicy: aReferrerPolicy,
-      postData: aPostData ? serializeInputStream(aPostData) : null,
-      headers: aHeaders ? serializeInputStream(aHeaders) : null,
+      postData: aPostData ? readInputStreamToString(aPostData) : null,
+      headers: aHeaders ? readInputStreamToString(aHeaders) : null,
       baseURI: aBaseURI ? aBaseURI.spec : null,
       triggeringPrincipal: aTriggeringPrincipal
                            ? Utils.serializePrincipal(aTriggeringPrincipal)
                            : null,
     });
   },
   setOriginAttributesBeforeLoading(aOriginAttributes) {
     this._sendMessage("WebNavigation:SetOriginAttributes", {
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -21,32 +21,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/sessionstore/Utils.jsm");
 
 if (AppConstants.MOZ_CRASHREPORTER) {
   XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
                                      "@mozilla.org/xre/app-info;1",
                                      "nsICrashReporter");
 }
 
-function makeInputStream(data) {
+function makeInputStream(aString) {
   let stream = Cc["@mozilla.org/io/string-input-stream;1"].
                createInstance(Ci.nsISupportsCString);
-  stream.data = data.content;
-
-  if (data.headers) {
-    let mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"]
-        .createInstance(Ci.nsIMIMEInputStream);
-
-    mimeStream.setData(stream);
-    for (let [name, value] of data.headers) {
-      mimeStream.addHeader(name, value);
-    }
-    return mimeStream;
-  }
-
+  stream.data = aString;
   return stream; // XPConnect will QI this to nsIInputStream for us.
 }
 
 var WebProgressListener = {
   init() {
     this._filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
                      .createInstance(Ci.nsIWebProgress);
     this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -14,16 +14,18 @@ const Cu = Components.utils;
 const Cr = Components.results;
 
 const {nsIHttpActivityObserver, nsISocketTransport} = Ci;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
                                   "resource://gre/modules/ExtensionUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebRequestCommon",
                                   "resource://gre/modules/WebRequestCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebRequestUpload",
                                   "resource://gre/modules/WebRequestUpload.jsm");
@@ -850,18 +852,24 @@ HttpObserverManager = {
   },
 
   onStopRequest(channel, loadContext) {
     this.runChannelListener(channel, loadContext, "onStop");
   },
 };
 
 var onBeforeRequest = {
-  allowedOptions: ["blocking", "requestBody"],
-
+  get allowedOptions() {
+    delete this.allowedOptions;
+    this.allowedOptions = ["blocking"];
+    if (!AppConstants.RELEASE_OR_BETA) {
+      this.allowedOptions.push("requestBody");
+    }
+    return this.allowedOptions;
+  },
   addListener(callback, filter = null, opt_extraInfoSpec = null) {
     let opts = parseExtra(opt_extraInfoSpec, this.allowedOptions);
     opts.filter = parseFilter(filter);
     ContentPolicyManager.addListener(callback, opts);
     HttpObserverManager.addListener("opening", callback, opts);
   },
 
   removeListener(callback) {
--- a/toolkit/modules/addons/WebRequestUpload.jsm
+++ b/toolkit/modules/addons/WebRequestUpload.jsm
@@ -363,21 +363,41 @@ function parseFormData(stream, channel, 
       let [name, value] = part.replace(/\+/g, " ").split("=").map(decodeURIComponent);
       formData.get(name).push(value);
     }
 
     return formData;
   }
 
   try {
+    let headers;
     if (stream instanceof Ci.nsIMIMEInputStream && stream.data) {
+      // MIME input streams encode additional headers as a block at the
+      // beginning of their stream. The actual request data comes from a
+      // sub-stream, which is accessible via their `data` member. The
+      // difference in available bytes between the outer stream and the
+      // inner data stream tells us the size of that header block.
+      //
+      // Since we need to know at least the value of the Content-Type
+      // header to properly parse the request body, we need to read and
+      // parse the header block in order to extract it.
+
+      headers = readString(createTextStream(stream),
+                           stream.available() - stream.data.available());
+
+      rewind(stream);
       stream = stream.data;
     }
 
-    let contentType = channel.getRequestHeader("Content-Type");
+    let contentType;
+    try {
+      contentType = channel.getRequestHeader("Content-Type");
+    } catch (e) {
+      contentType = new Headers(headers).get("content-type");
+    }
 
     switch (Headers.getParam(contentType, "")) {
       case "multipart/form-data":
         let boundary = Headers.getParam(contentType, "boundary");
         return parseMultiPart(stream, `\r\n--${boundary}`);
 
       case "application/x-www-form-urlencoded":
         return parseUrlEncoded(stream);
@@ -477,40 +497,36 @@ function* getRawDataChunked(outerStream,
     } finally {
       rewind(stream);
     }
   }
 }
 
 WebRequestUpload = {
   createRequestBody(channel) {
-    if (!(channel instanceof Ci.nsIUploadChannel) || !channel.uploadStream) {
-      return null;
-    }
+    if (channel instanceof Ci.nsIUploadChannel && channel.uploadStream) {
+      try {
+        let stream = channel.uploadStream;
+
+        let formData = createFormData(stream, channel);
+        if (formData) {
+          return {formData};
+        }
 
-    if (channel instanceof Ci.nsIUploadChannel2 && channel.uploadStreamHasHeaders) {
-      return {error: "Upload streams with headers are unsupported"};
+        // If we failed to parse the stream as form data, return it as a
+        // sequence of raw data chunks, along with a leniently-parsed form
+        // data object, which ignores encoding errors.
+        return {
+          raw: Array.from(getRawDataChunked(stream)),
+          lenientFormData: createFormData(stream, channel, true),
+        };
+      } catch (e) {
+        Cu.reportError(e);
+        return {error: e.message || String(e)};
+      }
     }
 
-    try {
-      let stream = channel.uploadStream;
-
-      let formData = createFormData(stream, channel);
-      if (formData) {
-        return {formData};
-      }
-
-      // If we failed to parse the stream as form data, return it as a
-      // sequence of raw data chunks, along with a leniently-parsed form
-      // data object, which ignores encoding errors.
-      return {
-        raw: Array.from(getRawDataChunked(stream)),
-        lenientFormData: createFormData(stream, channel, true),
-      };
-    } catch (e) {
-      Cu.reportError(e);
-      return {error: e.message || String(e)};
-    }
+    return null;
   },
 };
 
 XPCOMUtils.defineLazyPreferenceGetter(WebRequestUpload, "MAX_RAW_BYTES",
                                       "webextensions.webRequest.requestBodyMaxRawBytes");