Bug 1039846 - Response implementation. r=baku

--HG--
extra : rebase_source : 0da394758a5ccf6c1fe87d1a51ed0c4c27d9350e
This commit is contained in:
Nikhil Marathe 2014-09-26 16:41:15 -07:00
parent beef012a79
commit 7b2c55eff6
14 changed files with 805 additions and 403 deletions

View File

@ -75,7 +75,7 @@ public:
void Delete(const nsAString& aName);
void Stringify(nsString& aRetval)
void Stringify(nsString& aRetval) const
{
Serialize(aRetval);
}

View File

@ -62,3 +62,4 @@ MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 1, "Permission denied to pass cross-o
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.")

View File

@ -6,16 +6,98 @@
#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,
@ -23,78 +105,239 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrScalarValueS
{
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();
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;
}
aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
return ExtractFromURLSearchParams(params, aStream, aContentType);
}
MOZ_ASSERT(byteStream);
byteStream.forget(aStream);
return NS_OK;
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();
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;
}
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;
}
}
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;
}
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

View File

@ -13,6 +13,8 @@ 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.
@ -23,6 +25,81 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrScalarValueS
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

View File

@ -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

View File

@ -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

View File

@ -5,25 +5,16 @@
#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 {
@ -39,9 +30,9 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
NS_INTERFACE_MAP_END
Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest)
: mOwner(aOwner)
: FetchBody<Request>()
, mOwner(aOwner)
, mRequest(aRequest)
, mBodyUsed(false)
{
}
@ -224,21 +215,7 @@ Request::Constructor(const GlobalObject& aGlobal,
}
}
// 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();
}
@ -251,182 +228,5 @@ Request::Clone() const
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

View File

@ -9,6 +9,7 @@
#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.
@ -25,6 +26,7 @@ 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)
@ -76,11 +78,14 @@ public:
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;
}
@ -88,51 +93,13 @@ public:
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

View File

@ -4,15 +4,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Response.h"
#include "nsDOMString.h"
#include "nsPIDOMWindow.h"
#include "nsIURI.h"
#include "nsISupportsImpl.h"
#include "nsIURI.h"
#include "nsPIDOMWindow.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 {
@ -25,9 +29,10 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
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)
{
}
@ -38,11 +43,9 @@ 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();
}
@ -58,81 +61,93 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
}
/*static*/ already_AddRefed<Response>
Response::Constructor(const GlobalObject& global,
Response::Constructor(const GlobalObject& aGlobal,
const Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams>& aBody,
const ResponseInit& aInit, ErrorResult& rv)
const ResponseInit& aInit, ErrorResult& aRv)
{
nsRefPtr<Response> response = new Response(global.GetAsSupports());
return response.forget();
if (aInit.mStatus < 200 || aInit.mStatus > 599) {
aRv.Throw(NS_ERROR_RANGE_ERR);
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");
}
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();
}
// FIXME(nsm): Bug 1073231: This is currently unspecced!
already_AddRefed<Response>
Response::Clone()
{
nsRefPtr<Response> response = new Response(mOwner);
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mOwner);
nsRefPtr<Response> response = new Response(global, mInternalResponse);
return response.forget();
}
already_AddRefed<Promise>
Response::ArrayBuffer(ErrorResult& aRv)
void
Response::SetBody(nsIInputStream* aBody)
{
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()) {
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;
}
promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
return promise.forget();
}
already_AddRefed<Promise>
Response::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
Response::BodyUsed()
{
return false;
// FIXME(nsm): Do we flip bodyUsed here?
mInternalResponse->SetBody(aBody);
}
} // namespace dom
} // namespace mozilla

View File

@ -9,9 +9,12 @@
#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 {
@ -22,12 +25,15 @@ 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)
@ -38,29 +44,32 @@ public:
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);
@ -73,7 +82,7 @@ public:
const Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams>& aBody,
const ResponseInit& aInit, ErrorResult& rv);
nsISupports* GetParentObject() const
nsIGlobalObject* GetParentObject() const
{
return mOwner;
}
@ -81,25 +90,13 @@ public:
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

View File

@ -8,6 +8,7 @@ EXPORTS.mozilla.dom += [
'Fetch.h',
'Headers.h',
'InternalRequest.h',
'InternalResponse.h',
'Request.h',
'Response.h',
]
@ -16,6 +17,7 @@ UNIFIED_SOURCES += [
'Fetch.cpp',
'Headers.cpp',
'InternalRequest.cpp',
'InternalResponse.cpp',
'Request.cpp',
'Response.cpp',
]

View File

@ -2,6 +2,8 @@
support-files =
worker_interfaces.js
worker_test_request.js
worker_test_response.js
[test_interfaces.html]
[test_request.html]
[test_response.html]

View File

@ -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>

View File

@ -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();
})
}