Bug 1039846 - Request implementation. r=baku
authorNikhil Marathe <nsm.nikhil@gmail.com>
Tue, 23 Sep 2014 22:03:20 -0700
changeset 209083 85663bd538c30f0456f3fd041ab9cfefb6f8154f
parent 209082 009ae35b0c6752a7926ada4490fa38826cc65485
child 209084 4e8f29e386bda1cb17ffb0e02e62cf6ece3cc000
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbaku
bugs1039846
milestone35.0a1
Bug 1039846 - Request implementation. r=baku
dom/bindings/Errors.msg
dom/fetch/Fetch.cpp
dom/fetch/Fetch.h
dom/fetch/Headers.cpp
dom/fetch/Headers.h
dom/fetch/InternalRequest.cpp
dom/fetch/InternalRequest.h
dom/fetch/Request.cpp
dom/fetch/Request.h
dom/fetch/moz.build
dom/workers/WorkerPrivate.h
dom/workers/test/fetch/mochitest.ini
dom/workers/test/fetch/test_request.html
dom/workers/test/fetch/worker_test_request.js
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -55,8 +55,10 @@ MSG_DEF(MSG_INVALID_URL, 1, "{0} is not 
 MSG_DEF(MSG_METADATA_NOT_CONFIGURED, 0, "Either size or lastModified should be true.")
 MSG_DEF(MSG_INVALID_READ_SIZE, 0, "0 (Zero) is not a valid read size.")
 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.")
new file mode 100644
--- /dev/null
+++ b/dom/fetch/Fetch.cpp
@@ -0,0 +1,100 @@
+/* -*- 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 "nsIUnicodeEncoder.h"
+
+#include "nsStringStream.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/URLSearchParams.h"
+
+namespace mozilla {
+namespace dom {
+
+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;
+    }
+  } 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;
+    }
+  } 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");
+  } 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;
+    }
+    aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
+  }
+
+  MOZ_ASSERT(byteStream);
+  byteStream.forget(aStream);
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/fetch/Fetch.h
@@ -0,0 +1,29 @@
+/* -*- 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_Fetch_h
+#define mozilla_dom_Fetch_h
+
+#include "mozilla/dom/UnionTypes.h"
+
+class nsIInputStream;
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * 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);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Fetch_h
--- a/dom/fetch/Headers.cpp
+++ b/dom/fetch/Headers.cpp
@@ -2,17 +2,16 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "mozilla/dom/Headers.h"
 
 #include "mozilla/ErrorResult.h"
-#include "mozilla/dom/UnionTypes.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/Preferences.h"
 
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsDOMString.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
@@ -78,16 +77,49 @@ Headers::Constructor(const GlobalObject&
 
   if (aRv.Failed()) {
     return nullptr;
   }
 
   return headers.forget();
 }
 
+// static
+already_AddRefed<Headers>
+Headers::Constructor(const GlobalObject& aGlobal,
+                     const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit,
+                     ErrorResult& aRv)
+{
+  nsRefPtr<Headers> headers = new Headers(aGlobal.GetAsSupports());
+
+  if (aInit.IsHeaders()) {
+    headers->Fill(aInit.GetAsHeaders(), aRv);
+  } else if (aInit.IsByteStringSequenceSequence()) {
+    headers->Fill(aInit.GetAsByteStringSequenceSequence(), aRv);
+  } else if (aInit.IsByteStringMozMap()) {
+    headers->Fill(aInit.GetAsByteStringMozMap(), aRv);
+  }
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return headers.forget();
+}
+
+Headers::Headers(const Headers& aOther)
+  : mOwner(aOther.mOwner)
+  , mGuard(aOther.mGuard)
+{
+  SetIsDOMBinding();
+  ErrorResult result;
+  Fill(aOther, result);
+  MOZ_ASSERT(!result.Failed());
+}
+
 void
 Headers::Append(const nsACString& aName, const nsACString& aValue,
                 ErrorResult& aRv)
 {
   nsAutoCString lowerName;
   ToLowerCase(aName, lowerName);
 
   if (IsInvalidMutableHeader(lowerName, &aValue, aRv)) {
@@ -198,16 +230,22 @@ Headers::Set(const nsACString& aName, co
     entry->mName = lowerName;
     entry->mValue = aValue;
   } else {
     mList.AppendElement(Entry(lowerName, aValue));
   }
 }
 
 void
+Headers::Clear()
+{
+  mList.Clear();
+}
+
+void
 Headers::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv)
 {
   // Rather than re-validate all current headers, just require code to set
   // this prior to populating the Headers object.  Allow setting immutable
   // late, though, as that is pretty much required to have a  useful, immutable
   // headers object.
   if (aGuard != HeadersGuardEnum::Immutable && mList.Length() > 0) {
     aRv.Throw(NS_ERROR_FAILURE);
@@ -323,11 +361,10 @@ void
 Headers::Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv)
 {
   nsTArray<nsString> keys;
   aInit.GetKeys(keys);
   for (uint32_t i = 0; i < keys.Length() && !aRv.Failed(); ++i) {
     Append(NS_ConvertUTF16toUTF8(keys[i]), aInit.Get(keys[i]), aRv);
   }
 }
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/Headers.h
+++ b/dom/fetch/Headers.h
@@ -3,16 +3,18 @@
 /* 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_Headers_h
 #define mozilla_dom_Headers_h
 
 #include "mozilla/dom/HeadersBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+
 #include "nsClassHashtable.h"
 #include "nsWrapperCache.h"
 
 class nsPIDOMWindow;
 
 namespace mozilla {
 
 class ErrorResult;
@@ -37,53 +39,67 @@ private:
     { }
 
     Entry() { }
 
     nsCString mName;
     nsCString mValue;
   };
 
-  nsRefPtr<nsISupports> mOwner;
+  nsCOMPtr<nsISupports> mOwner;
   HeadersGuardEnum mGuard;
   nsTArray<Entry> mList;
 
 public:
   explicit Headers(nsISupports* aOwner, HeadersGuardEnum aGuard = HeadersGuardEnum::None)
     : mOwner(aOwner)
     , mGuard(aGuard)
   {
     SetIsDOMBinding();
   }
 
+  explicit Headers(const Headers& aOther);
+
   static bool PrefEnabled(JSContext* cx, JSObject* obj);
 
   static already_AddRefed<Headers>
   Constructor(const GlobalObject& aGlobal,
               const Optional<HeadersOrByteStringSequenceSequenceOrByteStringMozMap>& aInit,
               ErrorResult& aRv);
 
+  static already_AddRefed<Headers>
+  Constructor(const GlobalObject& aGlobal,
+              const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit,
+              ErrorResult& aRv);
+
   void Append(const nsACString& aName, const nsACString& aValue,
               ErrorResult& aRv);
   void Delete(const nsACString& aName, ErrorResult& aRv);
   void Get(const nsACString& aName, nsCString& aValue, ErrorResult& aRv) const;
   void GetAll(const nsACString& aName, nsTArray<nsCString>& aResults,
               ErrorResult& aRv) const;
   bool Has(const nsACString& aName, ErrorResult& aRv) const;
   void Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv);
 
+  void Clear();
+
   // ChromeOnly
   HeadersGuardEnum Guard() const { return mGuard; }
   void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv);
 
   virtual JSObject* WrapObject(JSContext* aCx);
   nsISupports* GetParentObject() const { return mOwner; }
 
+  void Fill(const Headers& aInit, ErrorResult& aRv);
 private:
-  Headers(const Headers& aOther) MOZ_DELETE;
+  // Since Headers is also an nsISupports, the above constructor can
+  // accidentally be invoked as new Headers(Headers*[, implied None guard]) when
+  // the intention is to use the copy constructor. Explicitly disallow it.
+  Headers(Headers* aOther) MOZ_DELETE;
+
   virtual ~Headers();
 
   static bool IsSimpleHeader(const nsACString& aName,
                              const nsACString* aValue = nullptr);
   static bool IsInvalidName(const nsACString& aName, ErrorResult& aRv);
   static bool IsInvalidValue(const nsACString& aValue, ErrorResult& aRv);
   bool IsImmutable(ErrorResult& aRv) const;
   bool IsForbiddenRequestHeader(const nsACString& aName) const;
@@ -98,17 +114,16 @@ private:
     return IsInvalidName(aName, aRv) ||
            (aValue && IsInvalidValue(*aValue, aRv)) ||
            IsImmutable(aRv) ||
            IsForbiddenRequestHeader(aName) ||
            IsForbiddenRequestNoCorsHeader(aName, aValue) ||
            IsForbiddenResponseHeader(aName);
   }
 
-  void Fill(const Headers& aInit, ErrorResult& aRv);
   void Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv);
   void Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Headers_h
new file mode 100644
--- /dev/null
+++ b/dom/fetch/InternalRequest.cpp
@@ -0,0 +1,60 @@
+/* -*- 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 "InternalRequest.h"
+
+#include "nsIContentPolicy.h"
+#include "nsIDocument.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/workers/Workers.h"
+
+#include "WorkerPrivate.h"
+
+namespace mozilla {
+namespace dom {
+
+// The global is used to extract the principal.
+already_AddRefed<InternalRequest>
+InternalRequest::GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const
+{
+  nsRefPtr<InternalRequest> copy = new InternalRequest();
+  copy->mURL.Assign(mURL);
+  copy->SetMethod(mMethod);
+  copy->mHeaders = new Headers(*mHeaders);
+
+  copy->mBodyStream = mBodyStream;
+  copy->mPreserveContentCodings = true;
+
+  if (NS_IsMainThread()) {
+    nsIPrincipal* principal = aGlobal->PrincipalOrNull();
+    MOZ_ASSERT(principal);
+    aRv = nsContentUtils::GetASCIIOrigin(principal, copy->mOrigin);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+  } else {
+    workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(worker);
+    worker->AssertIsOnWorkerThread();
+
+    workers::WorkerPrivate::LocationInfo& location = worker->GetLocationInfo();
+    copy->mOrigin = NS_ConvertUTF16toUTF8(location.mOrigin);
+  }
+
+  copy->mMode = mMode;
+  copy->mCredentialsMode = mCredentialsMode;
+  // FIXME(nsm): Add ContentType fetch to nsIContentPolicy and friends.
+  // Then set copy's mContext to that.
+  return copy.forget();
+}
+
+InternalRequest::~InternalRequest()
+{
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/fetch/InternalRequest.h
@@ -0,0 +1,264 @@
+/* -*- 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_InternalRequest_h
+#define mozilla_dom_InternalRequest_h
+
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+
+#include "nsIContentPolicy.h"
+#include "nsIInputStream.h"
+#include "nsISupportsImpl.h"
+
+class nsIDocument;
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+
+class FetchBodyStream;
+class Request;
+
+class InternalRequest MOZ_FINAL
+{
+  friend class Request;
+
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest)
+
+  enum ContextFrameType
+  {
+    FRAMETYPE_AUXILIARY = 0,
+    FRAMETYPE_TOP_LEVEL,
+    FRAMETYPE_NESTED,
+    FRAMETYPE_NONE,
+  };
+
+  // Since referrer type can be none, client or a URL.
+  enum ReferrerType
+  {
+    REFERRER_NONE = 0,
+    REFERRER_CLIENT,
+    REFERRER_URL,
+  };
+
+  enum ResponseTainting
+  {
+    RESPONSETAINT_BASIC,
+    RESPONSETAINT_CORS,
+    RESPONSETAINT_OPAQUE,
+  };
+
+  explicit InternalRequest()
+    : mMethod("GET")
+    , mHeaders(new Headers(nullptr, HeadersGuardEnum::None))
+    , mContextFrameType(FRAMETYPE_NONE)
+    , mReferrerType(REFERRER_CLIENT)
+    , mMode(RequestMode::No_cors)
+    , mCredentialsMode(RequestCredentials::Omit)
+    , mResponseTainting(RESPONSETAINT_BASIC)
+    , mRedirectCount(0)
+    , mAuthenticationFlag(false)
+    , mForceOriginHeader(false)
+    , mManualRedirect(false)
+    , mPreserveContentCodings(false)
+    , mSameOriginDataURL(false)
+    , mSkipServiceWorker(false)
+    , mSynchronous(false)
+    , mUnsafeRequest(false)
+    , mUseURLCredentials(false)
+  {
+  }
+
+  explicit InternalRequest(const InternalRequest& aOther)
+    : mMethod(aOther.mMethod)
+    , mURL(aOther.mURL)
+    , mHeaders(aOther.mHeaders)
+    , mBodyStream(aOther.mBodyStream)
+    , mContext(aOther.mContext)
+    , mOrigin(aOther.mOrigin)
+    , mContextFrameType(aOther.mContextFrameType)
+    , mReferrerType(aOther.mReferrerType)
+    , mReferrerURL(aOther.mReferrerURL)
+    , mMode(aOther.mMode)
+    , mCredentialsMode(aOther.mCredentialsMode)
+    , mResponseTainting(aOther.mResponseTainting)
+    , mRedirectCount(aOther.mRedirectCount)
+    , mAuthenticationFlag(aOther.mAuthenticationFlag)
+    , mForceOriginHeader(aOther.mForceOriginHeader)
+    , mManualRedirect(aOther.mManualRedirect)
+    , mPreserveContentCodings(aOther.mPreserveContentCodings)
+    , mSameOriginDataURL(aOther.mSameOriginDataURL)
+    , mSandboxedStorageAreaURLs(aOther.mSandboxedStorageAreaURLs)
+    , mSkipServiceWorker(aOther.mSkipServiceWorker)
+    , mSynchronous(aOther.mSynchronous)
+    , mUnsafeRequest(aOther.mUnsafeRequest)
+    , mUseURLCredentials(aOther.mUseURLCredentials)
+  {
+  }
+
+  void
+  GetMethod(nsCString& aMethod) const
+  {
+    aMethod.Assign(mMethod);
+  }
+
+  void
+  SetMethod(const nsACString& aMethod)
+  {
+    mMethod.Assign(aMethod);
+  }
+
+  void
+  GetURL(nsCString& aURL) const
+  {
+    aURL.Assign(mURL);
+  }
+
+  bool
+  ReferrerIsNone() const
+  {
+    return mReferrerType == REFERRER_NONE;
+  }
+
+  bool
+  ReferrerIsURL() const
+  {
+    return mReferrerType == REFERRER_URL;
+  }
+
+  bool
+  ReferrerIsClient() const
+  {
+    return mReferrerType == REFERRER_CLIENT;
+  }
+
+  nsCString
+  ReferrerAsURL() const
+  {
+    MOZ_ASSERT(ReferrerIsURL());
+    return mReferrerURL;
+  }
+
+  void
+  SetReferrer(const nsACString& aReferrer)
+  {
+    // May be removed later.
+    MOZ_ASSERT(!ReferrerIsNone());
+    mReferrerType = REFERRER_URL;
+    mReferrerURL.Assign(aReferrer);
+  }
+
+  bool
+  IsSynchronous() const
+  {
+    return mSynchronous;
+  }
+
+  void
+  SetMode(RequestMode aMode)
+  {
+    mMode = aMode;
+  }
+
+  void
+  SetCredentialsMode(RequestCredentials aCredentialsMode)
+  {
+    mCredentialsMode = aCredentialsMode;
+  }
+
+  nsContentPolicyType
+  GetContext() const
+  {
+    return mContext;
+  }
+
+  Headers*
+  Headers_()
+  {
+    return mHeaders;
+  }
+
+  bool
+  ForceOriginHeader()
+  {
+    return mForceOriginHeader;
+  }
+
+  void
+  GetOrigin(nsCString& aOrigin) const
+  {
+    aOrigin.Assign(mOrigin);
+  }
+
+  void
+  SetBody(nsIInputStream* aStream)
+  {
+    mBodyStream = aStream;
+  }
+
+  // Will return the original stream!
+  // Use a tee or copy if you don't want to erase the original.
+  void
+  GetBody(nsIInputStream** aStream)
+  {
+    nsCOMPtr<nsIInputStream> s = mBodyStream;
+    s.forget(aStream);
+  }
+
+  // The global is used as the client for the new object.
+  already_AddRefed<InternalRequest>
+  GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const;
+
+private:
+  ~InternalRequest();
+
+  void
+  SetURL(const nsACString& aURL)
+  {
+    mURL.Assign(aURL);
+  }
+
+  nsCString mMethod;
+  nsCString mURL;
+  nsRefPtr<Headers> mHeaders;
+  nsCOMPtr<nsIInputStream> mBodyStream;
+
+  // nsContentPolicyType does not cover the complete set defined in the spec,
+  // but it is a good start.
+  nsContentPolicyType mContext;
+
+  nsCString mOrigin;
+
+  ContextFrameType mContextFrameType;
+  ReferrerType mReferrerType;
+
+  // When mReferrerType is REFERRER_URL.
+  nsCString mReferrerURL;
+
+  RequestMode mMode;
+  RequestCredentials mCredentialsMode;
+  ResponseTainting mResponseTainting;
+
+  uint32_t mRedirectCount;
+
+  bool mAuthenticationFlag;
+  bool mForceOriginHeader;
+  bool mManualRedirect;
+  bool mPreserveContentCodings;
+  bool mSameOriginDataURL;
+  bool mSandboxedStorageAreaURLs;
+  bool mSkipServiceWorker;
+  bool mSynchronous;
+  bool mUnsafeRequest;
+  bool mUseURLCredentials;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_InternalRequest_h
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -1,118 +1,443 @@
 /* -*- 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 "nsDOMFile.h"
 #include "nsDOMString.h"
-#include "nsISupportsImpl.h"
-#include "nsIURI.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/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 "File.h"
+#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, mHeaders)
+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(nsISupports* aOwner)
+Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest)
   : mOwner(aOwner)
-  , mHeaders(new Headers(aOwner))
+  , mRequest(aRequest)
+  , mBodyUsed(false)
 {
   SetIsDOMBinding();
 }
 
 Request::~Request()
 {
 }
 
+already_AddRefed<InternalRequest>
+Request::GetInternalRequest()
+{
+  nsRefPtr<InternalRequest> r = mRequest;
+  return r.forget();
+}
+
 /*static*/ already_AddRefed<Request>
-Request::Constructor(const GlobalObject& global,
+Request::Constructor(const GlobalObject& aGlobal,
                      const RequestOrScalarValueString& aInput,
-                     const RequestInit& aInit, ErrorResult& rv)
+                     const RequestInit& aInit, ErrorResult& aRv)
 {
-  nsRefPtr<Request> request = new Request(global.GetAsSupports());
-  return request.forget();
+  nsRefPtr<InternalRequest> request;
+
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
+  if (aInput.IsRequest()) {
+    nsRefPtr<Request> inputReq = &aInput.GetAsRequest();
+    if (inputReq->BodyUsed()) {
+      aRv.ThrowTypeError(MSG_REQUEST_BODY_CONSUMED_ERROR);
+      return nullptr;
+    }
+
+    inputReq->SetBodyUsed();
+    request = inputReq->GetInternalRequest();
+  } else {
+    request = new InternalRequest();
+  }
+
+  request = request->GetRequestConstructorCopy(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  RequestMode fallbackMode = RequestMode::EndGuard_;
+  RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
+  if (aInput.IsScalarValueString()) {
+    nsString input;
+    input.Assign(aInput.GetAsScalarValueString());
+
+    nsString requestURL;
+    if (NS_IsMainThread()) {
+      nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
+      MOZ_ASSERT(window);
+      nsCOMPtr<nsIURI> docURI = window->GetDocumentURI();
+      nsCString spec;
+      aRv = docURI->GetSpec(spec);
+      if (NS_WARN_IF(aRv.Failed())) {
+        return nullptr;
+      }
+
+      nsRefPtr<mozilla::dom::URL> url =
+        dom::URL::Constructor(aGlobal, input, NS_ConvertUTF8toUTF16(spec), aRv);
+      if (aRv.Failed()) {
+        return nullptr;
+      }
+
+      url->Stringify(requestURL, aRv);
+      if (aRv.Failed()) {
+        return nullptr;
+      }
+    } else {
+      workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
+      MOZ_ASSERT(worker);
+      worker->AssertIsOnWorkerThread();
+
+      nsString baseURL = NS_ConvertUTF8toUTF16(worker->GetLocationInfo().mHref);
+      nsRefPtr<workers::URL> url =
+        workers::URL::Constructor(aGlobal, input, baseURL, aRv);
+      if (aRv.Failed()) {
+        return nullptr;
+      }
+
+      url->Stringify(requestURL, aRv);
+      if (aRv.Failed()) {
+        return nullptr;
+      }
+    }
+    request->SetURL(NS_ConvertUTF16toUTF8(requestURL));
+    fallbackMode = RequestMode::Cors;
+    fallbackCredentials = RequestCredentials::Omit;
+  }
+
+  RequestMode mode = aInit.mMode.WasPassed() ? aInit.mMode.Value() : fallbackMode;
+  RequestCredentials credentials =
+    aInit.mCredentials.WasPassed() ? aInit.mCredentials.Value()
+                                   : fallbackCredentials;
+
+  if (mode != RequestMode::EndGuard_) {
+    request->SetMode(mode);
+  }
+
+  if (credentials != RequestCredentials::EndGuard_) {
+    request->SetCredentialsMode(credentials);
+  }
+
+  if (aInit.mMethod.WasPassed()) {
+    nsCString method = aInit.mMethod.Value();
+    ToLowerCase(method);
+
+    if (!method.EqualsASCII("options") &&
+        !method.EqualsASCII("get") &&
+        !method.EqualsASCII("head") &&
+        !method.EqualsASCII("post") &&
+        !method.EqualsASCII("put") &&
+        !method.EqualsASCII("delete")) {
+      NS_ConvertUTF8toUTF16 label(method);
+      aRv.ThrowTypeError(MSG_INVALID_REQUEST_METHOD, &label);
+      return nullptr;
+    }
+
+    ToUpperCase(method);
+    request->SetMethod(method);
+  }
+
+  nsRefPtr<Request> domRequest = new Request(global, request);
+  nsRefPtr<Headers> domRequestHeaders = domRequest->Headers_();
+
+  nsRefPtr<Headers> headers;
+  if (aInit.mHeaders.WasPassed()) {
+    headers = Headers::Constructor(aGlobal, aInit.mHeaders.Value(), aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+  } else {
+    headers = new Headers(*domRequestHeaders);
+  }
+
+  domRequestHeaders->Clear();
+
+  if (domRequest->Mode() == RequestMode::No_cors) {
+    nsCString method;
+    domRequest->GetMethod(method);
+    ToLowerCase(method);
+    if (!method.EqualsASCII("get") &&
+        !method.EqualsASCII("head") &&
+        !method.EqualsASCII("post")) {
+      NS_ConvertUTF8toUTF16 label(method);
+      aRv.ThrowTypeError(MSG_INVALID_REQUEST_METHOD, &label);
+      return nullptr;
+    }
+
+    domRequestHeaders->SetGuard(HeadersGuardEnum::Request_no_cors, aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+  }
+
+  domRequestHeaders->Fill(*headers, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  if (aInit.mBody.WasPassed()) {
+    const OwningArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams& bodyInit = aInit.mBody.Value();
+    nsCOMPtr<nsIInputStream> stream;
+    nsCString contentType;
+    aRv = ExtractByteStreamFromBody(bodyInit,
+                                    getter_AddRefs(stream), contentType);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+    request->SetBody(stream);
+
+    if (!contentType.IsVoid() &&
+        !domRequestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
+      domRequestHeaders->Append(NS_LITERAL_CSTRING("Content-Type"),
+                                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);
+  }
+
+  return domRequest.forget();
 }
 
 already_AddRefed<Request>
 Request::Clone() const
 {
-  nsRefPtr<Request> request = new Request(mOwner);
+  // 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);
+      nsCOMPtr<nsIDOMBlob> blob;
+      if (blobData) {
+        memcpy(blobData, buffer.BeginReading(), blobLen);
+        blob = DOMFile::CreateMemoryFile(blobData, blobLen,
+                                         NS_ConvertUTF8toUTF16(mMimeType));
+      } else {
+        aRv = NS_ERROR_OUT_OF_MEMORY;
+        return nullptr;
+      }
+
+      JS::Rooted<JS::Value> jsBlob(cx);
+      if (NS_IsMainThread()) {
+        aRv = nsContentUtils::WrapNative(cx, blob, &jsBlob);
+        if (aRv.Failed()) {
+          return nullptr;
+        }
+      } else {
+        jsBlob.setObject(*workers::file::CreateBlob(cx, blob));
+      }
+      promise->MaybeResolve(cx, jsBlob);
+      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)
 {
-  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();
+  return ConsumeBody(CONSUME_ARRAYBUFFER, aRv);
 }
 
 already_AddRefed<Promise>
 Request::Blob(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();
+  return ConsumeBody(CONSUME_BLOB, aRv);
 }
 
 already_AddRefed<Promise>
 Request::Json(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();
+  return ConsumeBody(CONSUME_JSON, aRv);
 }
 
 already_AddRefed<Promise>
 Request::Text(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();
-}
-
-bool
-Request::BodyUsed()
-{
-  return false;
+  return ConsumeBody(CONSUME_TEXT, aRv);
 }
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -4,74 +4,82 @@
  * 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/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
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Request)
 
 public:
-  Request(nsISupports* aOwner);
+  Request(nsIGlobalObject* aOwner, InternalRequest* aRequest);
 
   JSObject*
   WrapObject(JSContext* aCx)
   {
     return RequestBinding::Wrap(aCx, this);
   }
 
   void
   GetUrl(DOMString& aUrl) const
   {
-    aUrl.AsAString() = EmptyString();
+    aUrl.AsAString() = NS_ConvertUTF8toUTF16(mRequest->mURL);
   }
 
   void
   GetMethod(nsCString& aMethod) const
   {
-    aMethod = EmptyCString();
+    aMethod = mRequest->mMethod;
   }
 
   RequestMode
   Mode() const
   {
-    return RequestMode::Same_origin;
+    return mRequest->mMode;
   }
 
   RequestCredentials
   Credentials() const
   {
-    return RequestCredentials::Omit;
+    return mRequest->mCredentialsMode;
   }
 
   void
   GetReferrer(DOMString& aReferrer) const
   {
-    aReferrer.AsAString() = EmptyString();
+    if (mRequest->ReferrerIsNone()) {
+      aReferrer.AsAString() = EmptyString();
+      return;
+    }
+
+    // FIXME(nsm): Spec doesn't say what to do if referrer is client.
+    aReferrer.AsAString() = NS_ConvertUTF8toUTF16(mRequest->mReferrerURL);
   }
 
-  Headers* Headers_() const { return mHeaders; }
+  Headers* Headers_() const { return mRequest->Headers_(); }
 
   static already_AddRefed<Request>
   Constructor(const GlobalObject& aGlobal, const RequestOrScalarValueString& aInput,
               const RequestInit& aInit, ErrorResult& rv);
 
   nsISupports* GetParentObject() const
   {
     return mOwner;
@@ -88,20 +96,46 @@ public:
 
   already_AddRefed<Promise>
   Json(ErrorResult& aRv);
 
   already_AddRefed<Promise>
   Text(ErrorResult& aRv);
 
   bool
-  BodyUsed();
+  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();
 
-  nsCOMPtr<nsISupports> mOwner;
-  nsRefPtr<Headers> mHeaders;
+  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/moz.build
+++ b/dom/fetch/moz.build
@@ -1,22 +1,26 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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',
     'Request.h',
     'Response.h',
 ]
 
 UNIFIED_SOURCES += [
+    'Fetch.cpp',
     'Headers.cpp',
+    'InternalRequest.cpp',
     'Request.cpp',
     'Response.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../workers',
 ]
 
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -17,17 +17,17 @@
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsRefPtrHashtable.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
-#include "StructuredCloneTags.h"
+#include "mozilla/dom/StructuredCloneTags.h"
 
 #include "Queue.h"
 #include "WorkerFeature.h"
 
 class JSAutoStructuredCloneBuffer;
 class nsIChannel;
 class nsIDocument;
 class nsIEventTarget;
--- a/dom/workers/test/fetch/mochitest.ini
+++ b/dom/workers/test/fetch/mochitest.ini
@@ -1,5 +1,7 @@
 [DEFAULT]
 support-files =
   worker_interfaces.js
+  worker_test_request.js
 
 [test_interfaces.html]
+[test_request.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/fetch/test_request.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 XXXXXX - Test Request 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 checkEnabled() {
+    var worker = new Worker("worker_test_request.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() {
+    checkEnabled();
+  });
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/fetch/worker_test_request.js
@@ -0,0 +1,215 @@
+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 req = new Request("");
+  is(req.method, "GET", "Default Request method is GET");
+  ok(req.headers instanceof Headers, "Request should have non-null Headers object");
+  is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL");
+  is(req.referrer, "", "Default referrer is `client` which serializes to empty string.");
+  is(req.mode, "cors", "Request mode for string input is cors");
+  is(req.credentials, "omit", "Default Request credentials is omit");
+
+  var req = new Request(req);
+  is(req.method, "GET", "Default Request method is GET");
+  ok(req.headers instanceof Headers, "Request should have non-null Headers object");
+  is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL");
+  is(req.referrer, "", "Default referrer is `client` which serializes to empty string.");
+  is(req.mode, "cors", "Request mode string input is cors");
+  is(req.credentials, "omit", "Default Request credentials is omit");
+}
+
+function testClone() {
+  var req = (new Request("./cloned_request.txt", {
+              method: 'POST',
+              headers: { "Content-Length": 5 },
+              body: "Sample body",
+              mode: "same-origin",
+              credentials: "same-origin",
+            })).clone();
+  ok(req.method === "POST", "Request method is POST");
+  ok(req.headers instanceof Headers, "Request should have non-null Headers object");
+  is(req.headers.get('content-length'), "5", "Request content-length should be 5.");
+  ok(req.url === (new URL("./cloned_request.txt", self.location.href)).href,
+       "URL should be resolved with entry settings object's API base URL");
+  ok(req.referrer === "", "Default referrer is `client` which serializes to empty string.");
+  ok(req.mode === "same-origin", "Request mode is same-origin");
+  ok(req.credentials === "same-origin", "Default credentials is same-origin");
+}
+
+function testUsedRequest() {
+  // Passing a used request should fail.
+  var req = new Request("", { body: "This is foo" });
+  var p1 = req.text().then(function(v) {
+    try {
+      var req2 = new Request(req);
+      ok(false, "Used Request cannot be passed to new Request");
+    } catch(e) {
+      ok(true, "Used Request cannot be passed to new Request");
+    }
+  });
+
+  // Passing a request should set the request as used.
+  var reqA = new Request("", { body: "This is foo" });
+  var reqB = new Request(reqA);
+  is(reqA.bodyUsed, true, "Passing a Request to another Request should set the former as used");
+  return p1;
+}
+
+function testSimpleUrlParse() {
+  // Just checks that the URL parser is actually being used.
+  var req = new Request("/file.html");
+  is(req.url, (new URL("/file.html", self.location.href)).href, "URL parser should be used to resolve Request URL");
+}
+
+function testMethod() {
+  var allowed = ["delete", "get", "head", "options", "post", "put"];
+  for (var i = 0; i < allowed.length; ++i) {
+    try {
+      var r = new Request("", { method: allowed[i] });
+      ok(true, "Method " + allowed[i] + " should be allowed");
+    } catch(e) {
+      ok(false, "Method " + allowed[i] + " should be allowed");
+    }
+  }
+
+  var forbidden = ["aardvark", "connect", "trace", "track"];
+  for (var i = 0; i < forbidden.length; ++i) {
+    try {
+      var r = new Request("", { method: forbidden[i] });
+      ok(false, "Method " + forbidden[i] + " should be forbidden");
+    } catch(e) {
+      ok(true, "Method " + forbidden[i] + " should be forbidden");
+    }
+  }
+
+  var allowedNoCors = ["get", "head", "post"];
+  for (var i = 0; i < allowedNoCors.length; ++i) {
+    try {
+      var r = new Request("", { method: allowedNoCors[i], mode: "no-cors" });
+      ok(true, "Method " + allowedNoCors[i] + " should be allowed in no-cors mode");
+    } catch(e) {
+      ok(false, "Method " + allowedNoCors[i] + " should be allowed in no-cors mode");
+    }
+  }
+
+  var forbiddenNoCors = ["aardvark", "delete", "options", "put"];
+  for (var i = 0; i < forbiddenNoCors.length; ++i) {
+    try {
+      var r = new Request("", { method: forbiddenNoCors[i], mode: "no-cors" });
+      ok(false, "Method " + forbiddenNoCors[i] + " should be forbidden in no-cors mode");
+    } catch(e) {
+      ok(true, "Method " + forbiddenNoCors[i] + " should be forbidden in no-cors mode");
+    }
+  }
+}
+
+function testUrlFragment() {
+  var req = new Request("./request#withfragment");
+  ok(req.url, (new URL("./request", self.location.href)).href, "request.url should be serialized with exclude fragment flag set");
+}
+
+function testBodyUsed() {
+  var req = new Request("./bodyused", { body: "Sample body" });
+  is(req.bodyUsed, false, "bodyUsed is initially false.");
+  return req.text().then((v) => {
+    is(v, "Sample body", "Body should match");
+    is(req.bodyUsed, true, "After reading body, bodyUsed should be true.");
+  }).then((v) => {
+    return req.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 req1 = new Request("", { body: text });
+  var p1 = req1.text().then(function(v) {
+    ok(typeof v === "string", "Should resolve to string");
+    is(text, v, "Extracted string should match");
+  });
+
+  var req2 = new Request("", { body: new Uint8Array([72, 101, 108, 108, 111]) });
+  var p2 = req2.text().then(function(v) {
+    is("Hello", v, "Extracted string should match");
+  });
+
+  var req2b = new Request("", { body: (new Uint8Array([72, 101, 108, 108, 111])).buffer });
+  var p2b = req2b.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 req3 = new Request("", { body: params });
+  var p3 = req3.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 newReq = function() { return new Request("", { body: text }); }
+  return newReq().text().then(function(v) {
+    ok(typeof v === "string", "Should resolve to string");
+    is(text, v, "Extracted string should match");
+  }).then(function() {
+    return newReq().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 newReq().json().then(function(v) {
+      ok(false, "Invalid json should reject");
+    }, function(e) {
+      ok(true, "Invalid json should reject");
+    })
+  }).then(function() {
+    return newReq().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();
+  testSimpleUrlParse();
+  testUrlFragment();
+  testMethod();
+
+  Promise.resolve()
+    .then(testBodyCreation)
+    .then(testBodyUsed)
+    .then(testBodyExtraction)
+    .then(testUsedRequest)
+    // Put more promise based tests here.
+    .then(done)
+    .catch(function(e) {
+      ok(false, "Some Request tests failed " + e);
+      done();
+    })
+}