mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
Bug 1039846 - Response implementation. r=baku
--HG-- extra : rebase_source : 0da394758a5ccf6c1fe87d1a51ed0c4c27d9350e
This commit is contained in:
parent
beef012a79
commit
7b2c55eff6
@ -75,7 +75,7 @@ public:
|
||||
|
||||
void Delete(const nsAString& aName);
|
||||
|
||||
void Stringify(nsString& aRetval)
|
||||
void Stringify(nsString& aRetval) const
|
||||
{
|
||||
Serialize(aRetval);
|
||||
}
|
||||
|
@ -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.")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
24
dom/fetch/InternalResponse.cpp
Normal file
24
dom/fetch/InternalResponse.cpp
Normal 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
|
102
dom/fetch/InternalResponse.h
Normal file
102
dom/fetch/InternalResponse.h
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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]
|
||||
|
48
dom/workers/test/fetch/test_response.html
Normal file
48
dom/workers/test/fetch/test_response.html
Normal 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>
|
||||
|
124
dom/workers/test/fetch/worker_test_response.js
Normal file
124
dom/workers/test/fetch/worker_test_response.js
Normal 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();
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user