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