Bug 1039846 - Response implementation. r=baku
authorNikhil Marathe <nsm.nikhil@gmail.com>
Fri, 26 Sep 2014 16:41:15 -0700
changeset 210660 1aa52e01a6fbcd0de7fc690279d39b7079517cc0
parent 210659 1fb2e5db7f9861cf888f95da59cce660f5732cbf
child 210661 6e3f63326f130d68e4a66db3a26969bf566c81f4
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbaku
bugs1039846
milestone36.0a1
Bug 1039846 - Response implementation. r=baku
dom/base/URLSearchParams.h
dom/bindings/Errors.msg
dom/fetch/Fetch.cpp
dom/fetch/Fetch.h
dom/fetch/InternalResponse.cpp
dom/fetch/InternalResponse.h
dom/fetch/Request.cpp
dom/fetch/Request.h
dom/fetch/Response.cpp
dom/fetch/Response.h
dom/fetch/moz.build
dom/workers/test/fetch/mochitest.ini
dom/workers/test/fetch/test_response.html
dom/workers/test/fetch/worker_test_response.js
--- a/dom/base/URLSearchParams.h
+++ b/dom/base/URLSearchParams.h
@@ -70,17 +70,17 @@ public:
   void Set(const nsAString& aName, const nsAString& aValue);
 
   void Append(const nsAString& aName, const nsAString& aValue);
 
   bool Has(const nsAString& aName);
 
   void Delete(const nsAString& aName);
 
-  void Stringify(nsString& aRetval)
+  void Stringify(nsString& aRetval) const
   {
     Serialize(aRetval);
   }
 
 private:
   void AppendInternal(const nsAString& aName, const nsAString& aValue);
 
   void DeleteAll();
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -57,8 +57,9 @@ MSG_DEF(MSG_INVALID_READ_SIZE, 0, "0 (Ze
 MSG_DEF(MSG_HEADERS_IMMUTABLE, 0, "Headers are immutable and cannot be modified.")
 MSG_DEF(MSG_INVALID_HEADER_NAME, 1, "{0} is an invalid header name.")
 MSG_DEF(MSG_INVALID_HEADER_VALUE, 1, "{0} is an invalid header value.")
 MSG_DEF(MSG_INVALID_HEADER_SEQUENCE, 0, "Headers require name/value tuples when being initialized by a sequence.")
 MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 1, "Permission denied to pass cross-origin object as {0}.")
 MSG_DEF(MSG_MISSING_REQUIRED_DICTIONARY_MEMBER, 1, "Missing required {0}.")
 MSG_DEF(MSG_INVALID_REQUEST_METHOD, 1, "Invalid request method {0}.")
 MSG_DEF(MSG_REQUEST_BODY_CONSUMED_ERROR, 0, "Request body has already been consumed.")
+MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 0, "Response statusText may not contain newline or carriage return.")
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -1,100 +1,343 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Fetch.h"
 
 #include "nsIStringStream.h"
+#include "nsIUnicodeDecoder.h"
 #include "nsIUnicodeEncoder.h"
 
+#include "nsDOMString.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
 #include "nsStringStream.h"
 
+#include "mozilla/ErrorResult.h"
 #include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/Response.h"
 #include "mozilla/dom/URLSearchParams.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+nsresult
+ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
+                       nsIInputStream** aStream)
+{
+  aBuffer.ComputeLengthAndData();
+  //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
+  return NS_NewByteInputStream(aStream,
+                               reinterpret_cast<char*>(aBuffer.Data()),
+                               aBuffer.Length(), NS_ASSIGNMENT_COPY);
+}
+
+nsresult
+ExtractFromArrayBufferView(const ArrayBufferView& aBuffer,
+                           nsIInputStream** aStream)
+{
+  aBuffer.ComputeLengthAndData();
+  //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
+  return NS_NewByteInputStream(aStream,
+                               reinterpret_cast<char*>(aBuffer.Data()),
+                               aBuffer.Length(), NS_ASSIGNMENT_COPY);
+}
+
+nsresult
+ExtractFromScalarValueString(const nsString& aStr,
+                             nsIInputStream** aStream,
+                             nsCString& aContentType)
+{
+  nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8");
+  if (!encoder) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  int32_t destBufferLen;
+  nsresult rv = encoder->GetMaxLength(aStr.get(), aStr.Length(), &destBufferLen);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCString encoded;
+  if (!encoded.SetCapacity(destBufferLen, fallible_t())) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  char* destBuffer = encoded.BeginWriting();
+  int32_t srcLen = (int32_t) aStr.Length();
+  int32_t outLen = destBufferLen;
+  rv = encoder->Convert(aStr.get(), &srcLen, destBuffer, &outLen);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(outLen <= destBufferLen);
+  encoded.SetLength(outLen);
+
+  aContentType = NS_LITERAL_CSTRING("text/plain;charset=UTF-8");
+
+  return NS_NewCStringInputStream(aStream, encoded);
+}
+
+nsresult
+ExtractFromURLSearchParams(const URLSearchParams& aParams,
+                           nsIInputStream** aStream,
+                           nsCString& aContentType)
+{
+  nsAutoString serialized;
+  aParams.Stringify(serialized);
+  aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
+  return NS_NewStringInputStream(aStream, serialized);
+}
+}
+
 nsresult
 ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams& aBodyInit,
                           nsIInputStream** aStream,
                           nsCString& aContentType)
 {
   MOZ_ASSERT(aStream);
 
-  nsresult rv;
-  nsCOMPtr<nsIInputStream> byteStream;
   if (aBodyInit.IsArrayBuffer()) {
     const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer();
-    buf.ComputeLengthAndData();
-    //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
-    rv = NS_NewByteInputStream(getter_AddRefs(byteStream),
-                               reinterpret_cast<char*>(buf.Data()),
-                               buf.Length(), NS_ASSIGNMENT_COPY);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    return ExtractFromArrayBuffer(buf, aStream);
+  } else if (aBodyInit.IsArrayBufferView()) {
+    const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView();
+    return ExtractFromArrayBufferView(buf, aStream);
+  } else if (aBodyInit.IsScalarValueString()) {
+    nsAutoString str;
+    str.Assign(aBodyInit.GetAsScalarValueString());
+    return ExtractFromScalarValueString(str, aStream, aContentType);
+  } else if (aBodyInit.IsURLSearchParams()) {
+    URLSearchParams& params = aBodyInit.GetAsURLSearchParams();
+    return ExtractFromURLSearchParams(params, aStream, aContentType);
+  }
+
+  NS_NOTREACHED("Should never reach here");
+  return NS_ERROR_FAILURE;
+}
+
+nsresult
+ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams& aBodyInit,
+                          nsIInputStream** aStream,
+                          nsCString& aContentType)
+{
+  MOZ_ASSERT(aStream);
+
+  if (aBodyInit.IsArrayBuffer()) {
+    const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer();
+    return ExtractFromArrayBuffer(buf, aStream);
   } else if (aBodyInit.IsArrayBufferView()) {
     const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView();
-    buf.ComputeLengthAndData();
-    //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
-    rv = NS_NewByteInputStream(getter_AddRefs(byteStream),
-                               reinterpret_cast<char*>(buf.Data()),
-                               buf.Length(), NS_ASSIGNMENT_COPY);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    return ExtractFromArrayBufferView(buf, aStream);
   } else if (aBodyInit.IsScalarValueString()) {
-    nsString str = aBodyInit.GetAsScalarValueString();
-
-    nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8");
-    if (!encoder) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    int32_t destBufferLen;
-    rv = encoder->GetMaxLength(str.get(), str.Length(), &destBufferLen);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    nsCString encoded;
-    if (!encoded.SetCapacity(destBufferLen, fallible_t())) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    char* destBuffer = encoded.BeginWriting();
-    int32_t srcLen = (int32_t) str.Length();
-    int32_t outLen = destBufferLen;
-    rv = encoder->Convert(str.get(), &srcLen, destBuffer, &outLen);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    MOZ_ASSERT(outLen <= destBufferLen);
-    encoded.SetLength(outLen);
-    rv = NS_NewCStringInputStream(getter_AddRefs(byteStream), encoded);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    aContentType = NS_LITERAL_CSTRING("text/plain;charset=UTF-8");
+    nsAutoString str;
+    str.Assign(aBodyInit.GetAsScalarValueString());
+    return ExtractFromScalarValueString(str, aStream, aContentType);
   } else if (aBodyInit.IsURLSearchParams()) {
     URLSearchParams& params = aBodyInit.GetAsURLSearchParams();
-    nsString serialized;
-    params.Stringify(serialized);
-    rv = NS_NewStringInputStream(getter_AddRefs(byteStream), serialized);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+    return ExtractFromURLSearchParams(params, aStream, aContentType);
+  }
+
+  NS_NOTREACHED("Should never reach here");
+  return NS_ERROR_FAILURE;
+}
+
+namespace {
+nsresult
+DecodeUTF8(const nsCString& aBuffer, nsString& aDecoded)
+{
+  nsCOMPtr<nsIUnicodeDecoder> decoder =
+    EncodingUtils::DecoderForEncoding("UTF-8");
+  if (!decoder) {
+    return NS_ERROR_FAILURE;
+  }
+
+  int32_t destBufferLen;
+  nsresult rv =
+    decoder->GetMaxLength(aBuffer.get(), aBuffer.Length(), &destBufferLen);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!aDecoded.SetCapacity(destBufferLen, fallible_t())) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  char16_t* destBuffer = aDecoded.BeginWriting();
+  int32_t srcLen = (int32_t) aBuffer.Length();
+  int32_t outLen = destBufferLen;
+  rv = decoder->Convert(aBuffer.get(), &srcLen, destBuffer, &outLen);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(outLen <= destBufferLen);
+  aDecoded.SetLength(outLen);
+  return NS_OK;
+}
+}
+
+template <class Derived>
+already_AddRefed<Promise>
+FetchBody<Derived>::ConsumeBody(ConsumeType aType, ErrorResult& aRv)
+{
+  nsRefPtr<Promise> promise = Promise::Create(DerivedClass()->GetParentObject(), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  if (BodyUsed()) {
+    aRv.ThrowTypeError(MSG_REQUEST_BODY_CONSUMED_ERROR);
+    return nullptr;
+  }
+
+  SetBodyUsed();
+
+  // While the spec says to do this asynchronously, all the body constructors
+  // right now only accept bodies whose streams are backed by an in-memory
+  // buffer that can be read without blocking. So I think this is fine.
+  nsCOMPtr<nsIInputStream> stream;
+  DerivedClass()->GetBody(getter_AddRefs(stream));
+
+  if (!stream) {
+    aRv = NS_NewByteInputStream(getter_AddRefs(stream), "", 0,
+                                NS_ASSIGNMENT_COPY);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
     }
-    aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
+  }
+
+  AutoJSAPI api;
+  api.Init(DerivedClass()->GetParentObject());
+  JSContext* cx = api.cx();
+
+  // We can make this assertion because for now we only support memory backed
+  // structures for the body argument for a Request.
+  MOZ_ASSERT(NS_InputStreamIsBuffered(stream));
+  nsCString buffer;
+  uint64_t len;
+  aRv = stream->Available(&len);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  aRv = NS_ReadInputStreamToString(stream, buffer, len);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
   }
 
-  MOZ_ASSERT(byteStream);
-  byteStream.forget(aStream);
-  return NS_OK;
+  buffer.SetLength(len);
+
+  switch (aType) {
+    case CONSUME_ARRAYBUFFER: {
+      JS::Rooted<JSObject*> arrayBuffer(cx);
+      arrayBuffer =
+        ArrayBuffer::Create(cx, buffer.Length(),
+                            reinterpret_cast<const uint8_t*>(buffer.get()));
+      JS::Rooted<JS::Value> val(cx);
+      val.setObjectOrNull(arrayBuffer);
+      promise->MaybeResolve(cx, val);
+      return promise.forget();
+    }
+    case CONSUME_BLOB: {
+      // XXXnsm it is actually possible to avoid these duplicate allocations
+      // for the Blob case by having the Blob adopt the stream's memory
+      // directly, but I've not added a special case for now.
+      //
+      // FIXME(nsm): Use nsContentUtils::CreateBlobBuffer once blobs have been fixed on
+      // workers.
+      uint32_t blobLen = buffer.Length();
+      void* blobData = moz_malloc(blobLen);
+      nsRefPtr<File> blob;
+      if (blobData) {
+        memcpy(blobData, buffer.BeginReading(), blobLen);
+        blob = File::CreateMemoryFile(DerivedClass()->GetParentObject(), blobData, blobLen,
+                                      NS_ConvertUTF8toUTF16(mMimeType));
+      } else {
+        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+        return nullptr;
+      }
+
+      promise->MaybeResolve(blob);
+      return promise.forget();
+    }
+    case CONSUME_JSON: {
+      nsAutoString decoded;
+      aRv = DecodeUTF8(buffer, decoded);
+      if (NS_WARN_IF(aRv.Failed())) {
+        return nullptr;
+      }
+
+      JS::Rooted<JS::Value> json(cx);
+      if (!JS_ParseJSON(cx, decoded.get(), decoded.Length(), &json)) {
+        JS::Rooted<JS::Value> exn(cx);
+        if (JS_GetPendingException(cx, &exn)) {
+          JS_ClearPendingException(cx);
+          promise->MaybeReject(cx, exn);
+        }
+      }
+      promise->MaybeResolve(cx, json);
+      return promise.forget();
+    }
+    case CONSUME_TEXT: {
+      nsAutoString decoded;
+      aRv = DecodeUTF8(buffer, decoded);
+      if (NS_WARN_IF(aRv.Failed())) {
+        return nullptr;
+      }
+
+      promise->MaybeResolve(decoded);
+      return promise.forget();
+    }
+  }
+
+  NS_NOTREACHED("Unexpected consume body type");
+  // Silence warnings.
+  return nullptr;
 }
 
+template
+already_AddRefed<Promise>
+FetchBody<Request>::ConsumeBody(ConsumeType aType, ErrorResult& aRv);
+
+template
+already_AddRefed<Promise>
+FetchBody<Response>::ConsumeBody(ConsumeType aType, ErrorResult& aRv);
+
+template <class Derived>
+void
+FetchBody<Derived>::SetMimeType(ErrorResult& aRv)
+{
+  // Extract mime type.
+  nsTArray<nsCString> contentTypeValues;
+  MOZ_ASSERT(DerivedClass()->Headers_());
+  DerivedClass()->Headers_()->GetAll(NS_LITERAL_CSTRING("Content-Type"), contentTypeValues, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  // HTTP ABNF states Content-Type may have only one value.
+  // This is from the "parse a header value" of the fetch spec.
+  if (contentTypeValues.Length() == 1) {
+    mMimeType = contentTypeValues[0];
+    ToLowerCase(mMimeType);
+  }
+}
+
+template
+void
+FetchBody<Request>::SetMimeType(ErrorResult& aRv);
+
+template
+void
+FetchBody<Response>::SetMimeType(ErrorResult& aRv);
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/Fetch.h
+++ b/dom/fetch/Fetch.h
@@ -8,22 +8,99 @@
 
 #include "mozilla/dom/UnionTypes.h"
 
 class nsIInputStream;
 
 namespace mozilla {
 namespace dom {
 
+class Promise;
+
 /*
  * Creates an nsIInputStream based on the fetch specifications 'extract a byte
  * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract.
  * Stores content type in out param aContentType.
  */
 nsresult
 ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams& aBodyInit,
                           nsIInputStream** aStream,
                           nsCString& aContentType);
 
+/*
+ * Non-owning version.
+ */
+nsresult
+ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams& aBodyInit,
+                          nsIInputStream** aStream,
+                          nsCString& aContentType);
+
+template <class Derived>
+class FetchBody {
+public:
+  bool
+  BodyUsed() { return mBodyUsed; }
+
+  already_AddRefed<Promise>
+  ArrayBuffer(ErrorResult& aRv)
+  {
+    return ConsumeBody(CONSUME_ARRAYBUFFER, aRv);
+  }
+
+  already_AddRefed<Promise>
+  Blob(ErrorResult& aRv)
+  {
+    return ConsumeBody(CONSUME_BLOB, aRv);
+  }
+
+  already_AddRefed<Promise>
+  Json(ErrorResult& aRv)
+  {
+    return ConsumeBody(CONSUME_JSON, aRv);
+  }
+
+  already_AddRefed<Promise>
+  Text(ErrorResult& aRv)
+  {
+    return ConsumeBody(CONSUME_TEXT, aRv);
+  }
+
+protected:
+  FetchBody()
+    : mBodyUsed(false)
+  {
+  }
+
+  void
+  SetBodyUsed()
+  {
+    mBodyUsed = true;
+  }
+
+  void
+  SetMimeType(ErrorResult& aRv);
+
+private:
+  enum ConsumeType
+  {
+    CONSUME_ARRAYBUFFER,
+    CONSUME_BLOB,
+    // FormData not supported right now,
+    CONSUME_JSON,
+    CONSUME_TEXT,
+  };
+
+  Derived*
+  DerivedClass() const
+  {
+    return static_cast<Derived*>(const_cast<FetchBody*>(this));
+  }
+
+  already_AddRefed<Promise>
+  ConsumeBody(ConsumeType aType, ErrorResult& aRv);
+
+  bool mBodyUsed;
+  nsCString mMimeType;
+};
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Fetch_h
new file mode 100644
--- /dev/null
+++ b/dom/fetch/InternalResponse.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "InternalResponse.h"
+
+#include "nsIDOMFile.h"
+
+#include "mozilla/dom/Headers.h"
+
+namespace mozilla {
+namespace dom {
+
+InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusText)
+  : mType(ResponseType::Default)
+  , mStatus(aStatus)
+  , mStatusText(aStatusText)
+  , mHeaders(new Headers(nullptr, HeadersGuardEnum::Response))
+{
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/fetch/InternalResponse.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_InternalResponse_h
+#define mozilla_dom_InternalResponse_h
+
+#include "nsISupportsImpl.h"
+
+#include "mozilla/dom/ResponseBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class InternalResponse MOZ_FINAL
+{
+  friend class FetchDriver;
+
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalResponse)
+
+  InternalResponse(uint16_t aStatus, const nsACString& aStatusText);
+
+  explicit InternalResponse(const InternalResponse& aOther) MOZ_DELETE;
+
+  static already_AddRefed<InternalResponse>
+  NetworkError()
+  {
+    nsRefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString());
+    response->mType = ResponseType::Error;
+    return response.forget();
+  }
+
+  ResponseType
+  Type() const
+  {
+    return mType;
+  }
+
+  bool
+  IsError() const
+  {
+    return Type() == ResponseType::Error;
+  }
+
+  // FIXME(nsm): Return with exclude fragment.
+  nsCString&
+  GetUrl()
+  {
+    return mURL;
+  }
+
+  uint16_t
+  GetStatus() const
+  {
+    return mStatus;
+  }
+
+  const nsCString&
+  GetStatusText() const
+  {
+    return mStatusText;
+  }
+
+  Headers*
+  Headers_()
+  {
+    return mHeaders;
+  }
+
+  void
+  GetBody(nsIInputStream** aStream)
+  {
+    nsCOMPtr<nsIInputStream> stream = mBody;
+    stream.forget(aStream);
+  }
+
+  void
+  SetBody(nsIInputStream* aBody)
+  {
+    mBody = aBody;
+  }
+
+private:
+  ~InternalResponse()
+  { }
+
+  ResponseType mType;
+  nsCString mTerminationReason;
+  nsCString mURL;
+  const uint16_t mStatus;
+  const nsCString mStatusText;
+  nsRefPtr<Headers> mHeaders;
+  nsCOMPtr<nsIInputStream> mBody;
+  nsCString mContentType;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_InternalResponse_h
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -1,52 +1,43 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "Request.h"
 
-#include "nsIUnicodeDecoder.h"
 #include "nsIURI.h"
-
-#include "nsDOMString.h"
-#include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
-#include "nsStreamUtils.h"
-#include "nsStringStream.h"
 
 #include "mozilla/ErrorResult.h"
-#include "mozilla/dom/EncodingUtils.h"
-#include "mozilla/dom/File.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/URL.h"
 #include "mozilla/dom/workers/bindings/URL.h"
 
-// dom/workers
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Request)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Request)
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest)
-  : mOwner(aOwner)
+  : FetchBody<Request>()
+  , mOwner(aOwner)
   , mRequest(aRequest)
-  , mBodyUsed(false)
 {
 }
 
 Request::~Request()
 {
 }
 
 already_AddRefed<InternalRequest>
@@ -219,214 +210,23 @@ Request::Constructor(const GlobalObject&
                                 contentType, aRv);
     }
 
     if (aRv.Failed()) {
       return nullptr;
     }
   }
 
-  // Extract mime type.
-  nsTArray<nsCString> contentTypeValues;
-  domRequestHeaders->GetAll(NS_LITERAL_CSTRING("Content-Type"),
-                            contentTypeValues, aRv);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  // HTTP ABNF states Content-Type may have only one value.
-  // This is from the "parse a header value" of the fetch spec.
-  if (contentTypeValues.Length() == 1) {
-    domRequest->mMimeType = contentTypeValues[0];
-    ToLowerCase(domRequest->mMimeType);
-  }
-
+  domRequest->SetMimeType(aRv);
   return domRequest.forget();
 }
 
 already_AddRefed<Request>
 Request::Clone() const
 {
   // FIXME(nsm): Bug 1073231. This is incorrect, but the clone method isn't
   // well defined yet.
   nsRefPtr<Request> request = new Request(mOwner,
                                           new InternalRequest(*mRequest));
   return request.forget();
 }
-
-namespace {
-nsresult
-DecodeUTF8(const nsCString& aBuffer, nsString& aDecoded)
-{
-  nsCOMPtr<nsIUnicodeDecoder> decoder =
-    EncodingUtils::DecoderForEncoding("UTF-8");
-  if (!decoder) {
-    return NS_ERROR_FAILURE;
-  }
-
-  int32_t destBufferLen;
-  nsresult rv =
-    decoder->GetMaxLength(aBuffer.get(), aBuffer.Length(), &destBufferLen);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (!aDecoded.SetCapacity(destBufferLen, fallible_t())) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  char16_t* destBuffer = aDecoded.BeginWriting();
-  int32_t srcLen = (int32_t) aBuffer.Length();
-  int32_t outLen = destBufferLen;
-  rv = decoder->Convert(aBuffer.get(), &srcLen, destBuffer, &outLen);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  MOZ_ASSERT(outLen <= destBufferLen);
-  aDecoded.SetLength(outLen);
-  return NS_OK;
-}
-}
-
-already_AddRefed<Promise>
-Request::ConsumeBody(ConsumeType aType, ErrorResult& aRv)
-{
-  nsRefPtr<Promise> promise = Promise::Create(mOwner, aRv);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  if (BodyUsed()) {
-    aRv.ThrowTypeError(MSG_REQUEST_BODY_CONSUMED_ERROR);
-    return nullptr;
-  }
-
-  SetBodyUsed();
-
-  // While the spec says to do this asynchronously, all the body constructors
-  // right now only accept bodies whose streams are backed by an in-memory
-  // buffer that can be read without blocking. So I think this is fine.
-  nsCOMPtr<nsIInputStream> stream;
-  mRequest->GetBody(getter_AddRefs(stream));
-
-  if (!stream) {
-    aRv = NS_NewByteInputStream(getter_AddRefs(stream), "", 0,
-                                NS_ASSIGNMENT_COPY);
-    if (aRv.Failed()) {
-      return nullptr;
-    }
-  }
-
-  AutoJSAPI api;
-  api.Init(mOwner);
-  JSContext* cx = api.cx();
-
-  // We can make this assertion because for now we only support memory backed
-  // structures for the body argument for a Request.
-  MOZ_ASSERT(NS_InputStreamIsBuffered(stream));
-  nsCString buffer;
-  uint64_t len;
-  aRv = stream->Available(&len);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  aRv = NS_ReadInputStreamToString(stream, buffer, len);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  buffer.SetLength(len);
-
-  switch (aType) {
-    case CONSUME_ARRAYBUFFER: {
-      JS::Rooted<JSObject*> arrayBuffer(cx);
-      arrayBuffer =
-        ArrayBuffer::Create(cx, buffer.Length(),
-                            reinterpret_cast<const uint8_t*>(buffer.get()));
-      JS::Rooted<JS::Value> val(cx);
-      val.setObjectOrNull(arrayBuffer);
-      promise->MaybeResolve(cx, val);
-      return promise.forget();
-    }
-    case CONSUME_BLOB: {
-      // XXXnsm it is actually possible to avoid these duplicate allocations
-      // for the Blob case by having the Blob adopt the stream's memory
-      // directly, but I've not added a special case for now.
-      //
-      // This is similar to nsContentUtils::CreateBlobBuffer, but also deals
-      // with worker wrapping.
-      uint32_t blobLen = buffer.Length();
-      void* blobData = moz_malloc(blobLen);
-      nsRefPtr<File> blob;
-      if (blobData) {
-        memcpy(blobData, buffer.BeginReading(), blobLen);
-        blob = File::CreateMemoryFile(GetParentObject(), blobData, blobLen,
-                                      NS_ConvertUTF8toUTF16(mMimeType));
-      } else {
-        aRv = NS_ERROR_OUT_OF_MEMORY;
-        return nullptr;
-      }
-
-      promise->MaybeResolve(blob);
-      return promise.forget();
-    }
-    case CONSUME_JSON: {
-      nsString decoded;
-      aRv = DecodeUTF8(buffer, decoded);
-      if (aRv.Failed()) {
-        return nullptr;
-      }
-
-      JS::Rooted<JS::Value> json(cx);
-      if (!JS_ParseJSON(cx, decoded.get(), decoded.Length(), &json)) {
-        JS::Rooted<JS::Value> exn(cx);
-        if (JS_GetPendingException(cx, &exn)) {
-          JS_ClearPendingException(cx);
-          promise->MaybeReject(cx, exn);
-        }
-      }
-      promise->MaybeResolve(cx, json);
-      return promise.forget();
-    }
-    case CONSUME_TEXT: {
-      nsString decoded;
-      aRv = DecodeUTF8(buffer, decoded);
-      if (aRv.Failed()) {
-        return nullptr;
-      }
-
-      promise->MaybeResolve(decoded);
-      return promise.forget();
-    }
-  }
-
-  NS_NOTREACHED("Unexpected consume body type");
-  // Silence warnings.
-  return nullptr;
-}
-
-already_AddRefed<Promise>
-Request::ArrayBuffer(ErrorResult& aRv)
-{
-  return ConsumeBody(CONSUME_ARRAYBUFFER, aRv);
-}
-
-already_AddRefed<Promise>
-Request::Blob(ErrorResult& aRv)
-{
-  return ConsumeBody(CONSUME_BLOB, aRv);
-}
-
-already_AddRefed<Promise>
-Request::Json(ErrorResult& aRv)
-{
-  return ConsumeBody(CONSUME_JSON, aRv);
-}
-
-already_AddRefed<Promise>
-Request::Text(ErrorResult& aRv)
-{
-  return ConsumeBody(CONSUME_TEXT, aRv);
-}
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -4,32 +4,34 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_Request_h
 #define mozilla_dom_Request_h
 
 #include "nsISupportsImpl.h"
 #include "nsWrapperCache.h"
 
+#include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/InternalRequest.h"
 // Required here due to certain WebIDL enums/classes being declared in both
 // files.
 #include "mozilla/dom/RequestBinding.h"
 #include "mozilla/dom/UnionTypes.h"
 
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class Headers;
 class Promise;
 
 class Request MOZ_FINAL : public nsISupports
                         , public nsWrapperCache
+                        , public FetchBody<Request>
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Request)
 
 public:
   Request(nsIGlobalObject* aOwner, InternalRequest* aRequest);
 
   JSObject*
@@ -71,71 +73,36 @@ public:
     }
 
     // FIXME(nsm): Spec doesn't say what to do if referrer is client.
     aReferrer.AsAString() = NS_ConvertUTF8toUTF16(mRequest->mReferrerURL);
   }
 
   Headers* Headers_() const { return mRequest->Headers_(); }
 
+  void
+  GetBody(nsIInputStream** aStream) { return mRequest->GetBody(aStream); }
+
   static already_AddRefed<Request>
   Constructor(const GlobalObject& aGlobal, const RequestOrScalarValueString& aInput,
               const RequestInit& aInit, ErrorResult& rv);
 
-  nsISupports* GetParentObject() const
+  nsIGlobalObject* GetParentObject() const
   {
     return mOwner;
   }
 
   already_AddRefed<Request>
   Clone() const;
 
-  already_AddRefed<Promise>
-  ArrayBuffer(ErrorResult& aRv);
-
-  already_AddRefed<Promise>
-  Blob(ErrorResult& aRv);
-
-  already_AddRefed<Promise>
-  Json(ErrorResult& aRv);
-
-  already_AddRefed<Promise>
-  Text(ErrorResult& aRv);
-
-  bool
-  BodyUsed() const
-  {
-    return mBodyUsed;
-  }
-
   already_AddRefed<InternalRequest>
   GetInternalRequest();
 private:
-  enum ConsumeType
-  {
-    CONSUME_ARRAYBUFFER,
-    CONSUME_BLOB,
-    // FormData not supported right now,
-    CONSUME_JSON,
-    CONSUME_TEXT,
-  };
-
   ~Request();
 
-  already_AddRefed<Promise>
-  ConsumeBody(ConsumeType aType, ErrorResult& aRv);
-
-  void
-  SetBodyUsed()
-  {
-    mBodyUsed = true;
-  }
-
   nsCOMPtr<nsIGlobalObject> mOwner;
   nsRefPtr<InternalRequest> mRequest;
-  bool mBodyUsed;
-  nsCString mMimeType;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Request_h
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -1,138 +1,153 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "Response.h"
-#include "nsDOMString.h"
+
+#include "nsISupportsImpl.h"
+#include "nsIURI.h"
 #include "nsPIDOMWindow.h"
-#include "nsIURI.h"
-#include "nsISupportsImpl.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/Promise.h"
 
+#include "nsDOMString.h"
+
+#include "InternalResponse.h"
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Response)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Response)
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Response, mOwner)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-Response::Response(nsISupports* aOwner)
-  : mOwner(aOwner)
-  , mHeaders(new Headers(aOwner))
+Response::Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse)
+  : FetchBody<Response>()
+  , mOwner(aGlobal)
+  , mInternalResponse(aInternalResponse)
 {
 }
 
 Response::~Response()
 {
 }
 
 /* static */ already_AddRefed<Response>
 Response::Error(const GlobalObject& aGlobal)
 {
-  ErrorResult result;
-  ResponseInit init;
-  init.mStatus = 0;
-  Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams> body;
-  nsRefPtr<Response> r = Response::Constructor(aGlobal, body, init, result);
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  nsRefPtr<InternalResponse> error = InternalResponse::NetworkError();
+  nsRefPtr<Response> r = new Response(global, error);
   return r.forget();
 }
 
 /* static */ already_AddRefed<Response>
 Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
                    uint16_t aStatus)
 {
   ErrorResult result;
   ResponseInit init;
   Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams> body;
   nsRefPtr<Response> r = Response::Constructor(aGlobal, body, init, result);
   return r.forget();
 }
 
 /*static*/ already_AddRefed<Response>
-Response::Constructor(const GlobalObject& global,
+Response::Constructor(const GlobalObject& aGlobal,
                       const Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams>& aBody,
-                      const ResponseInit& aInit, ErrorResult& rv)
-{
-  nsRefPtr<Response> response = new Response(global.GetAsSupports());
-  return response.forget();
-}
-
-already_AddRefed<Response>
-Response::Clone()
-{
-  nsRefPtr<Response> response = new Response(mOwner);
-  return response.forget();
-}
-
-already_AddRefed<Promise>
-Response::ArrayBuffer(ErrorResult& aRv)
+                      const ResponseInit& aInit, ErrorResult& aRv)
 {
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
-  MOZ_ASSERT(global);
-  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
-  return promise.forget();
-}
-
-already_AddRefed<Promise>
-Response::Blob(ErrorResult& aRv)
-{
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
-  MOZ_ASSERT(global);
-  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
-  if (aRv.Failed()) {
+  if (aInit.mStatus < 200 || aInit.mStatus > 599) {
+    aRv.Throw(NS_ERROR_RANGE_ERR);
     return nullptr;
   }
 
-  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
-  return promise.forget();
-}
-
-already_AddRefed<Promise>
-Response::Json(ErrorResult& aRv)
-{
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
-  MOZ_ASSERT(global);
-  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
-  if (aRv.Failed()) {
-    return nullptr;
+  nsCString statusText;
+  if (aInit.mStatusText.WasPassed()) {
+    statusText = aInit.mStatusText.Value();
+    nsACString::const_iterator start, end;
+    statusText.BeginReading(start);
+    statusText.EndReading(end);
+    if (FindCharInReadable('\r', start, end)) {
+      aRv.ThrowTypeError(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR);
+      return nullptr;
+    }
+    // Reset iterator since FindCharInReadable advances it.
+    statusText.BeginReading(start);
+    if (FindCharInReadable('\n', start, end)) {
+      aRv.ThrowTypeError(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR);
+      return nullptr;
+    }
+  } else {
+    // Since we don't support default values for ByteString.
+    statusText = NS_LITERAL_CSTRING("OK");
   }
 
-  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
-  return promise.forget();
+  nsRefPtr<InternalResponse> internalResponse =
+    new InternalResponse(aInit.mStatus, statusText);
+
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  nsRefPtr<Response> r = new Response(global, internalResponse);
+
+  if (aInit.mHeaders.WasPassed()) {
+    internalResponse->Headers_()->Clear();
+
+    // Instead of using Fill, create an object to allow the constructor to
+    // unwrap the HeadersInit.
+    nsRefPtr<Headers> headers =
+      Headers::Constructor(aGlobal, aInit.mHeaders.Value(), aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+
+    internalResponse->Headers_()->Fill(*headers, aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+  }
+
+  if (aBody.WasPassed()) {
+    nsCOMPtr<nsIInputStream> bodyStream;
+    nsCString contentType;
+    aRv = ExtractByteStreamFromBody(aBody.Value(), getter_AddRefs(bodyStream), contentType);
+    internalResponse->SetBody(bodyStream);
+
+    if (!contentType.IsVoid() &&
+        !internalResponse->Headers_()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
+      internalResponse->Headers_()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, aRv);
+    }
+
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+  }
+
+  r->SetMimeType(aRv);
+  return r.forget();
 }
 
-already_AddRefed<Promise>
-Response::Text(ErrorResult& aRv)
+// FIXME(nsm): Bug 1073231: This is currently unspecced!
+already_AddRefed<Response>
+Response::Clone()
 {
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
-  MOZ_ASSERT(global);
-  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
-  return promise.forget();
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mOwner);
+  nsRefPtr<Response> response = new Response(global, mInternalResponse);
+  return response.forget();
 }
 
-bool
-Response::BodyUsed()
+void
+Response::SetBody(nsIInputStream* aBody)
 {
-  return false;
+  // FIXME(nsm): Do we flip bodyUsed here?
+  mInternalResponse->SetBody(aBody);
 }
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -4,105 +4,102 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_Response_h
 #define mozilla_dom_Response_h
 
 #include "nsWrapperCache.h"
 #include "nsISupportsImpl.h"
 
+#include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/ResponseBinding.h"
 #include "mozilla/dom/UnionTypes.h"
 
+#include "InternalResponse.h"
+
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class Headers;
 class Promise;
 
 class Response MOZ_FINAL : public nsISupports
                          , public nsWrapperCache
+                         , public FetchBody<Response>
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Response)
 
 public:
-  explicit Response(nsISupports* aOwner);
+  Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse);
+
+  Response(const Response& aOther) MOZ_DELETE;
 
   JSObject*
   WrapObject(JSContext* aCx)
   {
     return ResponseBinding::Wrap(aCx, this);
   }
 
   ResponseType
   Type() const
   {
-    return ResponseType::Error;
+    return mInternalResponse->Type();
   }
 
   void
   GetUrl(DOMString& aUrl) const
   {
-    aUrl.AsAString() = EmptyString();
+    aUrl.AsAString() = NS_ConvertUTF8toUTF16(mInternalResponse->GetUrl());
   }
 
   uint16_t
   Status() const
   {
-    return 400;
+    return mInternalResponse->GetStatus();
   }
 
   void
   GetStatusText(nsCString& aStatusText) const
   {
-    aStatusText = EmptyCString();
+    aStatusText = mInternalResponse->GetStatusText();
   }
 
   Headers*
-  Headers_() const { return mHeaders; }
+  Headers_() const { return mInternalResponse->Headers_(); }
+
+  void
+  GetBody(nsIInputStream** aStream) { return mInternalResponse->GetBody(aStream); }
 
   static already_AddRefed<Response>
   Error(const GlobalObject& aGlobal);
 
   static already_AddRefed<Response>
   Redirect(const GlobalObject& aGlobal, const nsAString& aUrl, uint16_t aStatus);
 
   static already_AddRefed<Response>
   Constructor(const GlobalObject& aGlobal,
               const Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams>& aBody,
               const ResponseInit& aInit, ErrorResult& rv);
 
-  nsISupports* GetParentObject() const
+  nsIGlobalObject* GetParentObject() const
   {
     return mOwner;
   }
 
   already_AddRefed<Response>
   Clone();
 
-  already_AddRefed<Promise>
-  ArrayBuffer(ErrorResult& aRv);
-
-  already_AddRefed<Promise>
-  Blob(ErrorResult& aRv);
-
-  already_AddRefed<Promise>
-  Json(ErrorResult& aRv);
-
-  already_AddRefed<Promise>
-  Text(ErrorResult& aRv);
-
-  bool
-  BodyUsed();
+  void
+  SetBody(nsIInputStream* aBody);
 private:
   ~Response();
 
-  nsCOMPtr<nsISupports> mOwner;
-  nsRefPtr<Headers> mHeaders;
+  nsCOMPtr<nsIGlobalObject> mOwner;
+  nsRefPtr<InternalResponse> mInternalResponse;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Response_h
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -3,24 +3,26 @@
 # 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/.
 
 EXPORTS.mozilla.dom += [
     'Fetch.h',
     'Headers.h',
     'InternalRequest.h',
+    'InternalResponse.h',
     'Request.h',
     'Response.h',
 ]
 
 UNIFIED_SOURCES += [
     'Fetch.cpp',
     'Headers.cpp',
     'InternalRequest.cpp',
+    'InternalResponse.cpp',
     'Request.cpp',
     'Response.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../workers',
 ]
 
--- a/dom/workers/test/fetch/mochitest.ini
+++ b/dom/workers/test/fetch/mochitest.ini
@@ -1,7 +1,9 @@
 [DEFAULT]
 support-files =
   worker_interfaces.js
   worker_test_request.js
+  worker_test_response.js
 
 [test_interfaces.html]
 [test_request.html]
+[test_response.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/fetch/test_response.html
@@ -0,0 +1,48 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1039846 - Test Response object in worker</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  function runTest() {
+    var worker = new Worker("worker_test_response.js");
+    worker.onmessage = function(event) {
+
+      if (event.data.type == 'finish') {
+        SimpleTest.finish();
+      } else if (event.data.type == 'status') {
+        ok(event.data.status, event.data.msg);
+      }
+    }
+
+    worker.onerror = function(event) {
+      ok(false, "Worker had an error: " + event.message + " at " + event.lineno);
+      SimpleTest.finish();
+    };
+
+    worker.postMessage(true);
+  }
+
+  SimpleTest.waitForExplicitFinish();
+
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.fetch.enabled", true]
+  ]}, function() {
+    runTest();
+  });
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/fetch/worker_test_response.js
@@ -0,0 +1,124 @@
+function ok(a, msg) {
+  dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
+  postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+  dump("IS: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
+  postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+}
+
+function testDefaultCtor() {
+  var res = new Response();
+  is(res.type, "default", "Default Response type is default");
+  ok(res.headers instanceof Headers, "Response should have non-null Headers object");
+  is(res.url, "", "URL should be empty string");
+  is(res.status, 200, "Default status is 200");
+  is(res.statusText, "OK", "Default statusText is OK");
+}
+
+function testClone() {
+  var res = (new Response("This is a body", {
+              status: 404,
+              statusText: "Not Found",
+              headers: { "Content-Length": 5 },
+            })).clone();
+  is(res.status, 404, "Response status is 404");
+  is(res.statusText, "Not Found", "Response statusText is POST");
+  ok(res.headers instanceof Headers, "Response should have non-null Headers object");
+  is(res.headers.get('content-length'), "5", "Response content-length should be 5.");
+}
+
+function testBodyUsed() {
+  var res = new Response("Sample body");
+  ok(!res.bodyUsed, "bodyUsed is initially false.");
+  return res.text().then((v) => {
+    is(v, "Sample body", "Body should match");
+    ok(res.bodyUsed, "After reading body, bodyUsed should be true.");
+  }).then(() => {
+    return res.blob().then((v) => {
+      ok(false, "Attempting to read body again should fail.");
+    }, (e) => {
+      ok(true, "Attempting to read body again should fail.");
+    })
+  });
+}
+
+// FIXME(nsm): Bug 1071290: We can't use Blobs as the body yet.
+function testBodyCreation() {
+  var text = "κόσμε";
+  var res1 = new Response(text);
+  var p1 = res1.text().then(function(v) {
+    ok(typeof v === "string", "Should resolve to string");
+    is(text, v, "Extracted string should match");
+  });
+
+  var res2 = new Response(new Uint8Array([72, 101, 108, 108, 111]));
+  var p2 = res2.text().then(function(v) {
+    is("Hello", v, "Extracted string should match");
+  });
+
+  var res2b = new Response((new Uint8Array([72, 101, 108, 108, 111])).buffer);
+  var p2b = res2b.text().then(function(v) {
+    is("Hello", v, "Extracted string should match");
+  });
+
+  var params = new URLSearchParams();
+  params.append("item", "Geckos");
+  params.append("feature", "stickyfeet");
+  params.append("quantity", "700");
+  var res3 = new Response(params);
+  var p3 = res3.text().then(function(v) {
+    var extracted = new URLSearchParams(v);
+    is(extracted.get("item"), "Geckos", "Param should match");
+    is(extracted.get("feature"), "stickyfeet", "Param should match");
+    is(extracted.get("quantity"), "700", "Param should match");
+  });
+
+  return Promise.all([p1, p2, p2b, p3]);
+}
+
+function testBodyExtraction() {
+  var text = "κόσμε";
+  var newRes = function() { return new Response(text); }
+  return newRes().text().then(function(v) {
+    ok(typeof v === "string", "Should resolve to string");
+    is(text, v, "Extracted string should match");
+  }).then(function() {
+    return newRes().blob().then(function(v) {
+      ok(v instanceof Blob, "Should resolve to Blob");
+      var fs = new FileReaderSync();
+      is(fs.readAsText(v), text, "Decoded Blob should match original");
+    });
+  }).then(function() {
+    return newRes().json().then(function(v) {
+      ok(false, "Invalid json should reject");
+    }, function(e) {
+      ok(true, "Invalid json should reject");
+    })
+  }).then(function() {
+    return newRes().arrayBuffer().then(function(v) {
+      ok(v instanceof ArrayBuffer, "Should resolve to ArrayBuffer");
+      var dec = new TextDecoder();
+      is(dec.decode(new Uint8Array(v)), text, "UTF-8 decoded ArrayBuffer should match original");
+    });
+  })
+}
+
+onmessage = function() {
+  var done = function() { postMessage({ type: 'finish' }) }
+
+  testDefaultCtor();
+  testClone();
+
+  Promise.resolve()
+    .then(testBodyCreation)
+    .then(testBodyUsed)
+    .then(testBodyExtraction)
+    // Put more promise based tests here.
+    .then(done)
+    .catch(function(e) {
+      ok(false, "Some Response tests failed " + e);
+      done();
+    })
+}