Bug 1039846 - Split Headers into InternalHeaders. r=baku
authorNikhil Marathe <nsm.nikhil@gmail.com>
Thu, 02 Oct 2014 10:59:20 -0700
changeset 210661 6e3f63326f130d68e4a66db3a26969bf566c81f4
parent 210660 1aa52e01a6fbcd0de7fc690279d39b7079517cc0
child 210662 caa56adec4f51798f54a339ce4ee86039467666f
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbaku
bugs1039846
milestone36.0a1
Bug 1039846 - Split Headers into InternalHeaders. r=baku
dom/fetch/Fetch.cpp
dom/fetch/Headers.cpp
dom/fetch/Headers.h
dom/fetch/InternalHeaders.cpp
dom/fetch/InternalHeaders.h
dom/fetch/InternalRequest.cpp
dom/fetch/InternalRequest.h
dom/fetch/InternalResponse.cpp
dom/fetch/InternalResponse.h
dom/fetch/Request.cpp
dom/fetch/Request.h
dom/fetch/Response.cpp
dom/fetch/Response.h
dom/fetch/moz.build
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -313,18 +313,18 @@ already_AddRefed<Promise>
 FetchBody<Response>::ConsumeBody(ConsumeType aType, ErrorResult& aRv);
 
 template <class Derived>
 void
 FetchBody<Derived>::SetMimeType(ErrorResult& aRv)
 {
   // Extract mime type.
   nsTArray<nsCString> contentTypeValues;
-  MOZ_ASSERT(DerivedClass()->Headers_());
-  DerivedClass()->Headers_()->GetAll(NS_LITERAL_CSTRING("Content-Type"), contentTypeValues, aRv);
+  MOZ_ASSERT(DerivedClass()->GetInternalHeaders());
+  DerivedClass()->GetInternalHeaders()->GetAll(NS_LITERAL_CSTRING("Content-Type"), contentTypeValues, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   // HTTP ABNF states Content-Type may have only one value.
   // This is from the "parse a header value" of the fetch spec.
   if (contentTypeValues.Length() == 1) {
     mMimeType = contentTypeValues[0];
--- a/dom/fetch/Headers.cpp
+++ b/dom/fetch/Headers.cpp
@@ -5,23 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Headers.h"
 
 #include "mozilla/ErrorResult.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"
-#include "nsReadableUtils.h"
-
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Headers)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Headers)
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Headers, mOwner)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Headers)
@@ -56,314 +49,65 @@ Headers::PrefEnabled(JSContext* aCx, JSO
 }
 
 // static
 already_AddRefed<Headers>
 Headers::Constructor(const GlobalObject& aGlobal,
                      const Optional<HeadersOrByteStringSequenceSequenceOrByteStringMozMap>& aInit,
                      ErrorResult& aRv)
 {
-  nsRefPtr<Headers> headers = new Headers(aGlobal.GetAsSupports());
+  nsRefPtr<InternalHeaders> ih = new InternalHeaders();
+  nsRefPtr<Headers> headers = new Headers(aGlobal.GetAsSupports(), ih);
 
   if (!aInit.WasPassed()) {
     return headers.forget();
   }
 
   if (aInit.Value().IsHeaders()) {
-    headers->Fill(aInit.Value().GetAsHeaders(), aRv);
+    ih->Fill(*aInit.Value().GetAsHeaders().mInternalHeaders, aRv);
   } else if (aInit.Value().IsByteStringSequenceSequence()) {
-    headers->Fill(aInit.Value().GetAsByteStringSequenceSequence(), aRv);
+    ih->Fill(aInit.Value().GetAsByteStringSequenceSequence(), aRv);
   } else if (aInit.Value().IsByteStringMozMap()) {
-    headers->Fill(aInit.Value().GetAsByteStringMozMap(), aRv);
+    ih->Fill(aInit.Value().GetAsByteStringMozMap(), aRv);
   }
 
   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());
+  nsRefPtr<InternalHeaders> ih = new InternalHeaders();
+  nsRefPtr<Headers> headers = new Headers(aGlobal.GetAsSupports(), ih);
 
   if (aInit.IsHeaders()) {
-    headers->Fill(aInit.GetAsHeaders(), aRv);
+    ih->Fill(*(aInit.GetAsHeaders().get()->mInternalHeaders), aRv);
   } else if (aInit.IsByteStringSequenceSequence()) {
-    headers->Fill(aInit.GetAsByteStringSequenceSequence(), aRv);
+    ih->Fill(aInit.GetAsByteStringSequenceSequence(), aRv);
   } else if (aInit.IsByteStringMozMap()) {
-    headers->Fill(aInit.GetAsByteStringMozMap(), aRv);
+    ih->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)
-{
-  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)) {
-    return;
-  }
-
-  mList.AppendElement(Entry(lowerName, aValue));
-}
-
-void
-Headers::Delete(const nsACString& aName, ErrorResult& aRv)
-{
-  nsAutoCString lowerName;
-  ToLowerCase(aName, lowerName);
-
-  if (IsInvalidMutableHeader(lowerName, nullptr, aRv)) {
-    return;
-  }
-
-  // remove in reverse order to minimize copying
-  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
-    if (lowerName == mList[i].mName) {
-      mList.RemoveElementAt(i);
-    }
-  }
-}
-
-void
-Headers::Get(const nsACString& aName, nsCString& aValue, ErrorResult& aRv) const
-{
-  nsAutoCString lowerName;
-  ToLowerCase(aName, lowerName);
-
-  if (IsInvalidName(lowerName, aRv)) {
-    return;
-  }
-
-  for (uint32_t i = 0; i < mList.Length(); ++i) {
-    if (lowerName == mList[i].mName) {
-      aValue = mList[i].mValue;
-      return;
-    }
-  }
-
-  // No value found, so return null to content
-  aValue.SetIsVoid(true);
-}
-
-void
-Headers::GetAll(const nsACString& aName, nsTArray<nsCString>& aResults,
-                ErrorResult& aRv) const
-{
-  nsAutoCString lowerName;
-  ToLowerCase(aName, lowerName);
-
-  if (IsInvalidName(lowerName, aRv)) {
-    return;
-  }
-
-  aResults.SetLength(0);
-  for (uint32_t i = 0; i < mList.Length(); ++i) {
-    if (lowerName == mList[i].mName) {
-      aResults.AppendElement(mList[i].mValue);
-    }
-  }
-}
-
-bool
-Headers::Has(const nsACString& aName, ErrorResult& aRv) const
-{
-  nsAutoCString lowerName;
-  ToLowerCase(aName, lowerName);
-
-  if (IsInvalidName(lowerName, aRv)) {
-    return false;
-  }
-
-  for (uint32_t i = 0; i < mList.Length(); ++i) {
-    if (lowerName == mList[i].mName) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void
-Headers::Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv)
-{
-  nsAutoCString lowerName;
-  ToLowerCase(aName, lowerName);
-
-  if (IsInvalidMutableHeader(lowerName, &aValue, aRv)) {
-    return;
-  }
-
-  int32_t firstIndex = INT32_MAX;
-
-  // remove in reverse order to minimize copying
-  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
-    if (lowerName == mList[i].mName) {
-      firstIndex = std::min(firstIndex, i);
-      mList.RemoveElementAt(i);
-    }
-  }
-
-  if (firstIndex < INT32_MAX) {
-    Entry* entry = mList.InsertElementAt(firstIndex);
-    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);
-  }
-  mGuard = aGuard;
-}
-
 JSObject*
 Headers::WrapObject(JSContext* aCx)
 {
   return mozilla::dom::HeadersBinding::Wrap(aCx, this);
 }
 
 Headers::~Headers()
 {
 }
-
-// static
-bool
-Headers::IsSimpleHeader(const nsACString& aName, const nsACString* aValue)
-{
-  // Note, we must allow a null content-type value here to support
-  // get("content-type"), but the IsInvalidValue() check will prevent null
-  // from being set or appended.
-  return aName.EqualsLiteral("accept") ||
-         aName.EqualsLiteral("accept-language") ||
-         aName.EqualsLiteral("content-language") ||
-         (aName.EqualsLiteral("content-type") &&
-          (!aValue || nsContentUtils::IsAllowedNonCorsContentType(*aValue)));
-}
-
-//static
-bool
-Headers::IsInvalidName(const nsACString& aName, ErrorResult& aRv)
-{
-  if (!NS_IsValidHTTPToken(aName)) {
-    NS_ConvertUTF8toUTF16 label(aName);
-    aRv.ThrowTypeError(MSG_INVALID_HEADER_NAME, &label);
-    return true;
-  }
-
-  return false;
-}
-
-// static
-bool
-Headers::IsInvalidValue(const nsACString& aValue, ErrorResult& aRv)
-{
-  if (!NS_IsReasonableHTTPHeaderValue(aValue)) {
-    NS_ConvertUTF8toUTF16 label(aValue);
-    aRv.ThrowTypeError(MSG_INVALID_HEADER_VALUE, &label);
-    return true;
-  }
-  return false;
-}
-
-bool
-Headers::IsImmutable(ErrorResult& aRv) const
-{
-  if (mGuard == HeadersGuardEnum::Immutable) {
-    aRv.ThrowTypeError(MSG_HEADERS_IMMUTABLE);
-    return true;
-  }
-  return false;
-}
-
-bool
-Headers::IsForbiddenRequestHeader(const nsACString& aName) const
-{
-  return mGuard == HeadersGuardEnum::Request &&
-         nsContentUtils::IsForbiddenRequestHeader(aName);
-}
-
-bool
-Headers::IsForbiddenRequestNoCorsHeader(const nsACString& aName,
-                                        const nsACString* aValue) const
-{
-  return mGuard == HeadersGuardEnum::Request_no_cors &&
-         !IsSimpleHeader(aName, aValue);
-}
-
-bool
-Headers::IsForbiddenResponseHeader(const nsACString& aName) const
-{
-  return mGuard == HeadersGuardEnum::Response &&
-         nsContentUtils::IsForbiddenResponseHeader(aName);
-}
-
-void
-Headers::Fill(const Headers& aInit, ErrorResult& aRv)
-{
-  const nsTArray<Entry>& list = aInit.mList;
-  for (uint32_t i = 0; i < list.Length() && !aRv.Failed(); ++i) {
-    const Entry& entry = list[i];
-    Append(entry.mName, entry.mValue, aRv);
-  }
-}
-
-void
-Headers::Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv)
-{
-  for (uint32_t i = 0; i < aInit.Length() && !aRv.Failed(); ++i) {
-    const Sequence<nsCString>& tuple = aInit[i];
-    if (tuple.Length() != 2) {
-      aRv.ThrowTypeError(MSG_INVALID_HEADER_SEQUENCE);
-      return;
-    }
-    Append(tuple[0], tuple[1], aRv);
-  }
-}
-
-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
@@ -8,121 +8,122 @@
 #define mozilla_dom_Headers_h
 
 #include "mozilla/dom/HeadersBinding.h"
 #include "mozilla/dom/UnionTypes.h"
 
 #include "nsClassHashtable.h"
 #include "nsWrapperCache.h"
 
+#include "InternalHeaders.h"
+
 class nsPIDOMWindow;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 template<typename T> class MozMap;
 class HeadersOrByteStringSequenceSequenceOrByteStringMozMap;
 
+/**
+ * This Headers class is only used to represent the content facing Headers
+ * object. It is actually backed by an InternalHeaders implementation. Gecko
+ * code should NEVER use this, except in the Request and Response
+ * implementations, where they must always be created from the backing
+ * InternalHeaders object.
+ */
 class Headers MOZ_FINAL : public nsISupports
                         , public nsWrapperCache
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Headers)
 
-private:
-  struct Entry
-  {
-    Entry(const nsACString& aName, const nsACString& aValue)
-      : mName(aName)
-      , mValue(aValue)
-    { }
+  friend class Request;
+  friend class Response;
 
-    Entry() { }
-
-    nsCString mName;
-    nsCString mValue;
-  };
-
+private:
   nsCOMPtr<nsISupports> mOwner;
-  HeadersGuardEnum mGuard;
-  nsTArray<Entry> mList;
+  nsRefPtr<InternalHeaders> mInternalHeaders;
 
 public:
-  explicit Headers(nsISupports* aOwner, HeadersGuardEnum aGuard = HeadersGuardEnum::None)
+  explicit Headers(nsISupports* aOwner, InternalHeaders* aInternalHeaders)
     : mOwner(aOwner)
-    , mGuard(aGuard)
+    , mInternalHeaders(aInternalHeaders)
   {
   }
 
-  explicit Headers(const Headers& aOther);
+  explicit Headers(const Headers& aOther) MOZ_DELETE;
 
   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;
+              ErrorResult& aRv)
+  {
+    mInternalHeaders->Append(aName, aValue, aRv);
+  }
+
+  void Delete(const nsACString& aName, ErrorResult& aRv)
+  {
+    mInternalHeaders->Delete(aName, aRv);
+  }
+
+  void Get(const nsACString& aName, nsCString& aValue, ErrorResult& aRv) const
+  {
+    mInternalHeaders->Get(aName, aValue, aRv);
+  }
+
   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);
+              ErrorResult& aRv) const
+  {
+    mInternalHeaders->GetAll(aName, aResults, aRv);
+  }
 
-  void Clear();
+  bool Has(const nsACString& aName, ErrorResult& aRv) const
+  {
+    return mInternalHeaders->Has(aName, aRv);
+  }
+
+  void Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv)
+  {
+    mInternalHeaders->Set(aName, aValue, aRv);
+  }
 
   // ChromeOnly
-  HeadersGuardEnum Guard() const { return mGuard; }
-  void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv);
+  HeadersGuardEnum Guard() const
+  {
+    return mInternalHeaders->Guard();
+  }
+
+  void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv)
+  {
+    mInternalHeaders->SetGuard(aGuard, aRv);
+  }
 
   virtual JSObject* WrapObject(JSContext* aCx);
   nsISupports* GetParentObject() const { return mOwner; }
 
-  void Fill(const Headers& aInit, ErrorResult& aRv);
 private:
-  // 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;
-  bool IsForbiddenRequestNoCorsHeader(const nsACString& aName,
-                                      const nsACString* aValue = nullptr) const;
-  bool IsForbiddenResponseHeader(const nsACString& aName) const;
-
-  bool IsInvalidMutableHeader(const nsACString& aName,
-                              const nsACString* aValue,
-                              ErrorResult& aRv) const
+  InternalHeaders*
+  GetInternalHeaders() const
   {
-    return IsInvalidName(aName, aRv) ||
-           (aValue && IsInvalidValue(*aValue, aRv)) ||
-           IsImmutable(aRv) ||
-           IsForbiddenRequestHeader(aName) ||
-           IsForbiddenRequestNoCorsHeader(aName, aValue) ||
-           IsForbiddenResponseHeader(aName);
+    return mInternalHeaders;
   }
-
-  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/InternalHeaders.cpp
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/InternalHeaders.h"
+
+#include "mozilla/ErrorResult.h"
+
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+void
+InternalHeaders::Append(const nsACString& aName, const nsACString& aValue,
+                        ErrorResult& aRv)
+{
+  nsAutoCString lowerName;
+  ToLowerCase(aName, lowerName);
+
+  if (IsInvalidMutableHeader(lowerName, aValue, aRv)) {
+    return;
+  }
+
+  mList.AppendElement(Entry(lowerName, aValue));
+}
+
+void
+InternalHeaders::Delete(const nsACString& aName, ErrorResult& aRv)
+{
+  nsAutoCString lowerName;
+  ToLowerCase(aName, lowerName);
+
+  if (IsInvalidMutableHeader(lowerName, aRv)) {
+    return;
+  }
+
+  // remove in reverse order to minimize copying
+  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
+    if (lowerName == mList[i].mName) {
+      mList.RemoveElementAt(i);
+    }
+  }
+}
+
+void
+InternalHeaders::Get(const nsACString& aName, nsCString& aValue, ErrorResult& aRv) const
+{
+  nsAutoCString lowerName;
+  ToLowerCase(aName, lowerName);
+
+  if (IsInvalidName(lowerName, aRv)) {
+    return;
+  }
+
+  for (uint32_t i = 0; i < mList.Length(); ++i) {
+    if (lowerName == mList[i].mName) {
+      aValue = mList[i].mValue;
+      return;
+    }
+  }
+
+  // No value found, so return null to content
+  aValue.SetIsVoid(true);
+}
+
+void
+InternalHeaders::GetAll(const nsACString& aName, nsTArray<nsCString>& aResults,
+                        ErrorResult& aRv) const
+{
+  nsAutoCString lowerName;
+  ToLowerCase(aName, lowerName);
+
+  if (IsInvalidName(lowerName, aRv)) {
+    return;
+  }
+
+  aResults.SetLength(0);
+  for (uint32_t i = 0; i < mList.Length(); ++i) {
+    if (lowerName == mList[i].mName) {
+      aResults.AppendElement(mList[i].mValue);
+    }
+  }
+}
+
+bool
+InternalHeaders::Has(const nsACString& aName, ErrorResult& aRv) const
+{
+  nsAutoCString lowerName;
+  ToLowerCase(aName, lowerName);
+
+  if (IsInvalidName(lowerName, aRv)) {
+    return false;
+  }
+
+  for (uint32_t i = 0; i < mList.Length(); ++i) {
+    if (lowerName == mList[i].mName) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void
+InternalHeaders::Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv)
+{
+  nsAutoCString lowerName;
+  ToLowerCase(aName, lowerName);
+
+  if (IsInvalidMutableHeader(lowerName, aValue, aRv)) {
+    return;
+  }
+
+  int32_t firstIndex = INT32_MAX;
+
+  // remove in reverse order to minimize copying
+  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
+    if (lowerName == mList[i].mName) {
+      firstIndex = std::min(firstIndex, i);
+      mList.RemoveElementAt(i);
+    }
+  }
+
+  if (firstIndex < INT32_MAX) {
+    Entry* entry = mList.InsertElementAt(firstIndex);
+    entry->mName = lowerName;
+    entry->mValue = aValue;
+  } else {
+    mList.AppendElement(Entry(lowerName, aValue));
+  }
+}
+
+void
+InternalHeaders::Clear()
+{
+  mList.Clear();
+}
+
+void
+InternalHeaders::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv)
+{
+  // Rather than re-validate all current headers, just require code to set
+  // this prior to populating the InternalHeaders 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);
+  }
+  mGuard = aGuard;
+}
+
+InternalHeaders::~InternalHeaders()
+{
+}
+
+// static
+bool
+InternalHeaders::IsSimpleHeader(const nsACString& aName, const nsACString& aValue)
+{
+  // Note, we must allow a null content-type value here to support
+  // get("content-type"), but the IsInvalidValue() check will prevent null
+  // from being set or appended.
+  return aName.EqualsLiteral("accept") ||
+         aName.EqualsLiteral("accept-language") ||
+         aName.EqualsLiteral("content-language") ||
+         (aName.EqualsLiteral("content-type") &&
+          nsContentUtils::IsAllowedNonCorsContentType(aValue));
+}
+
+//static
+bool
+InternalHeaders::IsInvalidName(const nsACString& aName, ErrorResult& aRv)
+{
+  if (!NS_IsValidHTTPToken(aName)) {
+    NS_ConvertUTF8toUTF16 label(aName);
+    aRv.ThrowTypeError(MSG_INVALID_HEADER_NAME, &label);
+    return true;
+  }
+
+  return false;
+}
+
+// static
+bool
+InternalHeaders::IsInvalidValue(const nsACString& aValue, ErrorResult& aRv)
+{
+  if (!NS_IsReasonableHTTPHeaderValue(aValue)) {
+    NS_ConvertUTF8toUTF16 label(aValue);
+    aRv.ThrowTypeError(MSG_INVALID_HEADER_VALUE, &label);
+    return true;
+  }
+  return false;
+}
+
+bool
+InternalHeaders::IsImmutable(ErrorResult& aRv) const
+{
+  if (mGuard == HeadersGuardEnum::Immutable) {
+    aRv.ThrowTypeError(MSG_HEADERS_IMMUTABLE);
+    return true;
+  }
+  return false;
+}
+
+bool
+InternalHeaders::IsForbiddenRequestHeader(const nsACString& aName) const
+{
+  return mGuard == HeadersGuardEnum::Request &&
+         nsContentUtils::IsForbiddenRequestHeader(aName);
+}
+
+bool
+InternalHeaders::IsForbiddenRequestNoCorsHeader(const nsACString& aName) const
+{
+  return mGuard == HeadersGuardEnum::Request_no_cors &&
+         !IsSimpleHeader(aName, EmptyCString());
+}
+
+bool
+InternalHeaders::IsForbiddenRequestNoCorsHeader(const nsACString& aName,
+                                                const nsACString& aValue) const
+{
+  return mGuard == HeadersGuardEnum::Request_no_cors &&
+         !IsSimpleHeader(aName, aValue);
+}
+
+bool
+InternalHeaders::IsForbiddenResponseHeader(const nsACString& aName) const
+{
+  return mGuard == HeadersGuardEnum::Response &&
+         nsContentUtils::IsForbiddenResponseHeader(aName);
+}
+
+void
+InternalHeaders::Fill(const InternalHeaders& aInit, ErrorResult& aRv)
+{
+  const nsTArray<Entry>& list = aInit.mList;
+  for (uint32_t i = 0; i < list.Length() && !aRv.Failed(); ++i) {
+    const Entry& entry = list[i];
+    Append(entry.mName, entry.mValue, aRv);
+  }
+}
+
+void
+InternalHeaders::Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv)
+{
+  for (uint32_t i = 0; i < aInit.Length() && !aRv.Failed(); ++i) {
+    const Sequence<nsCString>& tuple = aInit[i];
+    if (tuple.Length() != 2) {
+      aRv.ThrowTypeError(MSG_INVALID_HEADER_SEQUENCE);
+      return;
+    }
+    Append(tuple[0], tuple[1], aRv);
+  }
+}
+
+void
+InternalHeaders::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
new file mode 100644
--- /dev/null
+++ b/dom/fetch/InternalHeaders.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_InternalHeaders_h
+#define mozilla_dom_InternalHeaders_h
+
+// needed for HeadersGuardEnum.
+#include "mozilla/dom/HeadersBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+
+#include "nsClassHashtable.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+template<typename T> class MozMap;
+class HeadersOrByteStringSequenceSequenceOrByteStringMozMap;
+
+class InternalHeaders MOZ_FINAL
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalHeaders)
+
+private:
+  struct Entry
+  {
+    Entry(const nsACString& aName, const nsACString& aValue)
+      : mName(aName)
+      , mValue(aValue)
+    { }
+
+    Entry() { }
+
+    nsCString mName;
+    nsCString mValue;
+  };
+
+  HeadersGuardEnum mGuard;
+  nsTArray<Entry> mList;
+
+public:
+  explicit InternalHeaders(HeadersGuardEnum aGuard = HeadersGuardEnum::None)
+    : mGuard(aGuard)
+  {
+  }
+
+  explicit InternalHeaders(const InternalHeaders& aOther)
+    : mGuard(aOther.mGuard)
+  {
+    ErrorResult result;
+    Fill(aOther, result);
+    MOZ_ASSERT(!result.Failed());
+  }
+
+  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();
+
+  HeadersGuardEnum Guard() const { return mGuard; }
+  void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv);
+
+  void Fill(const InternalHeaders& aInit, ErrorResult& aRv);
+  void Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv);
+  void Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv);
+private:
+  virtual ~InternalHeaders();
+
+  static bool IsSimpleHeader(const nsACString& aName,
+                             const nsACString& aValue);
+  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;
+  bool IsForbiddenRequestNoCorsHeader(const nsACString& aName) const;
+  bool IsForbiddenRequestNoCorsHeader(const nsACString& aName,
+                                      const nsACString& aValue) const;
+  bool IsForbiddenResponseHeader(const nsACString& aName) const;
+
+  bool IsInvalidMutableHeader(const nsACString& aName,
+                              ErrorResult& aRv) const
+  {
+    return IsInvalidMutableHeader(aName, EmptyCString(), aRv);
+  }
+
+  bool IsInvalidMutableHeader(const nsACString& aName,
+                              const nsACString& aValue,
+                              ErrorResult& aRv) const
+  {
+    return IsInvalidName(aName, aRv) ||
+           IsInvalidValue(aValue, aRv) ||
+           IsImmutable(aRv) ||
+           IsForbiddenRequestHeader(aName) ||
+           IsForbiddenRequestNoCorsHeader(aName, aValue) ||
+           IsForbiddenResponseHeader(aName);
+  }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_InternalHeaders_h
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -19,17 +19,17 @@ 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->mHeaders = new InternalHeaders(*mHeaders);
 
   copy->mBodyStream = mBodyStream;
   copy->mPreserveContentCodings = true;
 
   if (NS_IsMainThread()) {
     nsIPrincipal* principal = aGlobal->PrincipalOrNull();
     MOZ_ASSERT(principal);
     aRv = nsContentUtils::GetASCIIOrigin(principal, copy->mOrigin);
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -1,17 +1,18 @@
 /* -*- 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/HeadersBinding.h"
+#include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/RequestBinding.h"
 #include "mozilla/dom/UnionTypes.h"
 
 #include "nsIContentPolicy.h"
 #include "nsIInputStream.h"
 #include "nsISupportsImpl.h"
 
 class nsIDocument;
@@ -50,17 +51,17 @@ public:
   {
     RESPONSETAINT_BASIC,
     RESPONSETAINT_CORS,
     RESPONSETAINT_OPAQUE,
   };
 
   explicit InternalRequest()
     : mMethod("GET")
-    , mHeaders(new Headers(nullptr, HeadersGuardEnum::None))
+    , mHeaders(new InternalHeaders(HeadersGuardEnum::None))
     , mContextFrameType(FRAMETYPE_NONE)
     , mReferrerType(REFERRER_CLIENT)
     , mMode(RequestMode::No_cors)
     , mCredentialsMode(RequestCredentials::Omit)
     , mResponseTainting(RESPONSETAINT_BASIC)
     , mRedirectCount(0)
     , mAuthenticationFlag(false)
     , mForceOriginHeader(false)
@@ -154,16 +155,22 @@ public:
   }
 
   bool
   IsSynchronous() const
   {
     return mSynchronous;
   }
 
+  RequestMode
+  Mode() const
+  {
+    return mMode;
+  }
+
   void
   SetMode(RequestMode aMode)
   {
     mMode = aMode;
   }
 
   void
   SetCredentialsMode(RequestCredentials aCredentialsMode)
@@ -172,18 +179,18 @@ public:
   }
 
   nsContentPolicyType
   GetContext() const
   {
     return mContext;
   }
 
-  Headers*
-  Headers_()
+  InternalHeaders*
+  Headers()
   {
     return mHeaders;
   }
 
   bool
   ForceOriginHeader()
   {
     return mForceOriginHeader;
@@ -220,17 +227,17 @@ private:
   void
   SetURL(const nsACString& aURL)
   {
     mURL.Assign(aURL);
   }
 
   nsCString mMethod;
   nsCString mURL;
-  nsRefPtr<Headers> mHeaders;
+  nsRefPtr<InternalHeaders> 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;
 
--- a/dom/fetch/InternalResponse.cpp
+++ b/dom/fetch/InternalResponse.cpp
@@ -2,23 +2,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "InternalResponse.h"
 
 #include "nsIDOMFile.h"
 
-#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/InternalHeaders.h"
 
 namespace mozilla {
 namespace dom {
 
 InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusText)
   : mType(ResponseType::Default)
   , mStatus(aStatus)
   , mStatusText(aStatusText)
-  , mHeaders(new Headers(nullptr, HeadersGuardEnum::Response))
+  , mHeaders(new InternalHeaders(HeadersGuardEnum::Response))
 {
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -1,23 +1,26 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_InternalResponse_h
 #define mozilla_dom_InternalResponse_h
 
+#include "nsIInputStream.h"
 #include "nsISupportsImpl.h"
 
 #include "mozilla/dom/ResponseBinding.h"
 
 namespace mozilla {
 namespace dom {
 
+class InternalHeaders;
+
 class InternalResponse MOZ_FINAL
 {
   friend class FetchDriver;
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalResponse)
 
   InternalResponse(uint16_t aStatus, const nsACString& aStatusText);
@@ -58,18 +61,18 @@ public:
   }
 
   const nsCString&
   GetStatusText() const
   {
     return mStatusText;
   }
 
-  Headers*
-  Headers_()
+  InternalHeaders*
+  Headers()
   {
     return mHeaders;
   }
 
   void
   GetBody(nsIInputStream** aStream)
   {
     nsCOMPtr<nsIInputStream> stream = mBody;
@@ -86,17 +89,17 @@ private:
   ~InternalResponse()
   { }
 
   ResponseType mType;
   nsCString mTerminationReason;
   nsCString mURL;
   const uint16_t mStatus;
   const nsCString mStatusText;
-  nsRefPtr<Headers> mHeaders;
+  nsRefPtr<InternalHeaders> mHeaders;
   nsCOMPtr<nsIInputStream> mBody;
   nsCString mContentType;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_InternalResponse_h
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -17,17 +17,17 @@
 
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Request)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Request)
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner, mHeaders)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest)
   : FetchBody<Request>()
@@ -150,83 +150,95 @@ Request::Constructor(const GlobalObject&
       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<InternalHeaders> requestHeaders = request->Headers();
 
-  nsRefPtr<Headers> headers;
+  nsRefPtr<InternalHeaders> headers;
   if (aInit.mHeaders.WasPassed()) {
-    headers = Headers::Constructor(aGlobal, aInit.mHeaders.Value(), aRv);
+    nsRefPtr<Headers> h = Headers::Constructor(aGlobal, aInit.mHeaders.Value(), aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
+    headers = h->GetInternalHeaders();
   } else {
-    headers = new Headers(*domRequestHeaders);
+    headers = new InternalHeaders(*requestHeaders);
   }
 
-  domRequestHeaders->Clear();
+  requestHeaders->Clear();
 
-  if (domRequest->Mode() == RequestMode::No_cors) {
+  if (request->Mode() == RequestMode::No_cors) {
     nsCString method;
-    domRequest->GetMethod(method);
+    request->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);
+    requestHeaders->SetGuard(HeadersGuardEnum::Request_no_cors, aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
   }
 
-  domRequestHeaders->Fill(*headers, aRv);
+  requestHeaders->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);
+        !requestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
+      requestHeaders->Append(NS_LITERAL_CSTRING("Content-Type"),
+                             contentType, aRv);
     }
 
     if (aRv.Failed()) {
       return nullptr;
     }
   }
 
+  nsRefPtr<Request> domRequest = new Request(global, request);
   domRequest->SetMimeType(aRv);
   return domRequest.forget();
 }
 
 already_AddRefed<Request>
 Request::Clone() const
 {
   // FIXME(nsm): Bug 1073231. This is incorrect, but the clone method isn't
   // well defined yet.
   nsRefPtr<Request> request = new Request(mOwner,
                                           new InternalRequest(*mRequest));
   return request.forget();
 }
+
+Headers*
+Request::Headers_()
+{
+  if (!mHeaders) {
+    mHeaders = new Headers(mOwner, mRequest->Headers());
+  }
+
+  return mHeaders;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -17,16 +17,17 @@
 #include "mozilla/dom/UnionTypes.h"
 
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class Headers;
+class InternalHeaders;
 class Promise;
 
 class Request MOZ_FINAL : public nsISupports
                         , public nsWrapperCache
                         , public FetchBody<Request>
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Request)
@@ -71,17 +72,23 @@ public:
       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 mRequest->Headers_(); }
+  InternalHeaders*
+  GetInternalHeaders() const
+  {
+    return mRequest->Headers();
+  }
+
+  Headers* Headers_();
 
   void
   GetBody(nsIInputStream** aStream) { return mRequest->GetBody(aStream); }
 
   static already_AddRefed<Request>
   Constructor(const GlobalObject& aGlobal, const RequestOrScalarValueString& aInput,
               const RequestInit& aInit, ErrorResult& rv);
 
@@ -95,14 +102,16 @@ public:
 
   already_AddRefed<InternalRequest>
   GetInternalRequest();
 private:
   ~Request();
 
   nsCOMPtr<nsIGlobalObject> mOwner;
   nsRefPtr<InternalRequest> mRequest;
+  // Lazily created.
+  nsRefPtr<Headers> mHeaders;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Request_h
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -17,17 +17,17 @@
 
 #include "InternalResponse.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Response)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Response)
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Response, mOwner)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Response, mOwner, mHeaders)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 Response::Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse)
   : FetchBody<Response>()
@@ -93,41 +93,41 @@ Response::Constructor(const GlobalObject
 
   nsRefPtr<InternalResponse> internalResponse =
     new InternalResponse(aInit.mStatus, statusText);
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   nsRefPtr<Response> r = new Response(global, internalResponse);
 
   if (aInit.mHeaders.WasPassed()) {
-    internalResponse->Headers_()->Clear();
+    internalResponse->Headers()->Clear();
 
     // Instead of using Fill, create an object to allow the constructor to
     // unwrap the HeadersInit.
     nsRefPtr<Headers> headers =
       Headers::Constructor(aGlobal, aInit.mHeaders.Value(), aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
 
-    internalResponse->Headers_()->Fill(*headers, aRv);
+    internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
   }
 
   if (aBody.WasPassed()) {
     nsCOMPtr<nsIInputStream> bodyStream;
     nsCString contentType;
     aRv = ExtractByteStreamFromBody(aBody.Value(), getter_AddRefs(bodyStream), contentType);
     internalResponse->SetBody(bodyStream);
 
     if (!contentType.IsVoid() &&
-        !internalResponse->Headers_()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
-      internalResponse->Headers_()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, aRv);
+        !internalResponse->Headers()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
+      internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, aRv);
     }
 
     if (aRv.Failed()) {
       return nullptr;
     }
   }
 
   r->SetMimeType(aRv);
@@ -144,10 +144,20 @@ Response::Clone()
 }
 
 void
 Response::SetBody(nsIInputStream* aBody)
 {
   // FIXME(nsm): Do we flip bodyUsed here?
   mInternalResponse->SetBody(aBody);
 }
+
+Headers*
+Response::Headers_()
+{
+  if (!mHeaders) {
+    mHeaders = new Headers(mOwner, mInternalResponse->Headers());
+  }
+
+  return mHeaders;
+}
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -16,16 +16,17 @@
 #include "InternalResponse.h"
 
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class Headers;
+class InternalHeaders;
 class Promise;
 
 class Response MOZ_FINAL : public nsISupports
                          , public nsWrapperCache
                          , public FetchBody<Response>
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Response)
@@ -60,18 +61,23 @@ public:
   }
 
   void
   GetStatusText(nsCString& aStatusText) const
   {
     aStatusText = mInternalResponse->GetStatusText();
   }
 
-  Headers*
-  Headers_() const { return mInternalResponse->Headers_(); }
+  InternalHeaders*
+  GetInternalHeaders() const
+  {
+    return mInternalResponse->Headers();
+  }
+
+  Headers* Headers_();
 
   void
   GetBody(nsIInputStream** aStream) { return mInternalResponse->GetBody(aStream); }
 
   static already_AddRefed<Response>
   Error(const GlobalObject& aGlobal);
 
   static already_AddRefed<Response>
@@ -92,14 +98,16 @@ public:
 
   void
   SetBody(nsIInputStream* aBody);
 private:
   ~Response();
 
   nsCOMPtr<nsIGlobalObject> mOwner;
   nsRefPtr<InternalResponse> mInternalResponse;
+  // Lazily created
+  nsRefPtr<Headers> mHeaders;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Response_h
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -2,25 +2,27 @@
 # 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',
+    'InternalHeaders.h',
     'InternalRequest.h',
     'InternalResponse.h',
     'Request.h',
     'Response.h',
 ]
 
 UNIFIED_SOURCES += [
     'Fetch.cpp',
     'Headers.cpp',
+    'InternalHeaders.cpp',
     'InternalRequest.cpp',
     'InternalResponse.cpp',
     'Request.cpp',
     'Response.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../workers',