Bug 1039846 - Request implementation. r=baku
authorNikhil Marathe <nsm.nikhil@gmail.com>
Tue, 23 Sep 2014 22:03:20 -0700
changeset 233618 85663bd538c30f0456f3fd041ab9cfefb6f8154f
parent 233617 009ae35b0c6752a7926ada4490fa38826cc65485
child 233619 4e8f29e386bda1cb17ffb0e02e62cf6ece3cc000
push id611
push userraliiev@mozilla.com
push dateMon, 05 Jan 2015 23:23:16 +0000
treeherdermozilla-release@345cd3b9c445 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1039846
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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();
+    })
+}