mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-30 12:27:29 +09:00
Merge remote-tracking branch 'origin/tracking' into custom
This commit is contained in:
commit
a55674f653
89 changed files with 10466 additions and 916 deletions
207
dom/fetch/BodyExtractor.cpp
Normal file
207
dom/fetch/BodyExtractor.cpp
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "BodyExtractor.h"
|
||||
#include "mozilla/dom/EncodingUtils.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
#include "mozilla/dom/FormData.h"
|
||||
#include "mozilla/dom/TypedArray.h"
|
||||
#include "mozilla/dom/URLSearchParams.h"
|
||||
#include "mozilla/dom/XMLHttpRequest.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIDOMDocument.h"
|
||||
#include "nsIDOMSerializer.h"
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIOutputStream.h"
|
||||
#include "nsIStorageStream.h"
|
||||
#include "nsStringStream.h"
|
||||
#include "nsIUnicodeEncoder.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
static nsresult
|
||||
GetBufferDataAsStream(const uint8_t* aData, uint32_t aDataLength,
|
||||
nsIInputStream** aResult, uint64_t* aContentLength,
|
||||
nsACString& aContentType, nsACString& aCharset)
|
||||
{
|
||||
aContentType.SetIsVoid(true);
|
||||
aCharset.Truncate();
|
||||
|
||||
*aContentLength = aDataLength;
|
||||
const char* data = reinterpret_cast<const char*>(aData);
|
||||
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength,
|
||||
NS_ASSIGNMENT_COPY);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
stream.forget(aResult);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
template<> nsresult
|
||||
BodyExtractor<const ArrayBuffer>::GetAsStream(nsIInputStream** aResult,
|
||||
uint64_t* aContentLength,
|
||||
nsACString& aContentTypeWithCharset,
|
||||
nsACString& aCharset) const
|
||||
{
|
||||
mBody->ComputeLengthAndData();
|
||||
return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
|
||||
aResult, aContentLength, aContentTypeWithCharset,
|
||||
aCharset);
|
||||
}
|
||||
|
||||
template<> nsresult
|
||||
BodyExtractor<const ArrayBufferView>::GetAsStream(nsIInputStream** aResult,
|
||||
uint64_t* aContentLength,
|
||||
nsACString& aContentTypeWithCharset,
|
||||
nsACString& aCharset) const
|
||||
{
|
||||
mBody->ComputeLengthAndData();
|
||||
return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
|
||||
aResult, aContentLength, aContentTypeWithCharset,
|
||||
aCharset);
|
||||
}
|
||||
|
||||
template<> nsresult
|
||||
BodyExtractor<nsIDocument>::GetAsStream(nsIInputStream** aResult,
|
||||
uint64_t* aContentLength,
|
||||
nsACString& aContentTypeWithCharset,
|
||||
nsACString& aCharset) const
|
||||
{
|
||||
nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(mBody));
|
||||
NS_ENSURE_STATE(domdoc);
|
||||
aCharset.AssignLiteral("UTF-8");
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIStorageStream> storStream;
|
||||
rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIOutputStream> output;
|
||||
rv = storStream->GetOutputStream(0, getter_AddRefs(output));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (mBody->IsHTMLDocument()) {
|
||||
aContentTypeWithCharset.AssignLiteral("text/html;charset=UTF-8");
|
||||
|
||||
nsString serialized;
|
||||
if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nsAutoCString utf8Serialized;
|
||||
if (!AppendUTF16toUTF8(serialized, utf8Serialized, fallible)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
uint32_t written;
|
||||
rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
MOZ_ASSERT(written == utf8Serialized.Length());
|
||||
} else {
|
||||
aContentTypeWithCharset.AssignLiteral("application/xml;charset=UTF-8");
|
||||
|
||||
nsCOMPtr<nsIDOMSerializer> serializer =
|
||||
do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Make sure to use the encoding we'll send
|
||||
rv = serializer->SerializeToStream(domdoc, output, aCharset);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
output->Close();
|
||||
|
||||
uint32_t length;
|
||||
rv = storStream->GetLength(&length);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
*aContentLength = length;
|
||||
|
||||
rv = storStream->NewInputStream(0, aResult);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
template<> nsresult
|
||||
BodyExtractor<const nsAString>::GetAsStream(nsIInputStream** aResult,
|
||||
uint64_t* aContentLength,
|
||||
nsACString& aContentTypeWithCharset,
|
||||
nsACString& aCharset) const
|
||||
{
|
||||
nsCOMPtr<nsIUnicodeEncoder> encoder =
|
||||
EncodingUtils::EncoderForEncoding("UTF-8");
|
||||
if (!encoder) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
int32_t destBufferLen;
|
||||
nsresult rv = encoder->GetMaxLength(mBody->BeginReading(), mBody->Length(),
|
||||
&destBufferLen);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCString encoded;
|
||||
if (!encoded.SetCapacity(destBufferLen, fallible)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
char* destBuffer = encoded.BeginWriting();
|
||||
int32_t srcLen = (int32_t) mBody->Length();
|
||||
int32_t outLen = destBufferLen;
|
||||
rv = encoder->Convert(mBody->BeginReading(), &srcLen, destBuffer, &outLen);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(outLen <= destBufferLen);
|
||||
encoded.SetLength(outLen);
|
||||
|
||||
rv = NS_NewCStringInputStream(aResult, encoded);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
*aContentLength = outLen;
|
||||
aContentTypeWithCharset.AssignLiteral("text/plain;charset=UTF-8");
|
||||
aCharset.AssignLiteral("UTF-8");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
template<> nsresult
|
||||
BodyExtractor<nsIInputStream>::GetAsStream(nsIInputStream** aResult,
|
||||
uint64_t* aContentLength,
|
||||
nsACString& aContentTypeWithCharset,
|
||||
nsACString& aCharset) const
|
||||
{
|
||||
aContentTypeWithCharset.AssignLiteral("text/plain");
|
||||
aCharset.Truncate();
|
||||
|
||||
nsresult rv = mBody->Available(aContentLength);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIInputStream> stream(mBody);
|
||||
stream.forget(aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
template<> nsresult
|
||||
BodyExtractor<nsIXHRSendable>::GetAsStream(nsIInputStream** aResult,
|
||||
uint64_t* aContentLength,
|
||||
nsACString& aContentTypeWithCharset,
|
||||
nsACString& aCharset) const
|
||||
{
|
||||
return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset,
|
||||
aCharset);
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
48
dom/fetch/BodyExtractor.h
Normal file
48
dom/fetch/BodyExtractor.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_BodyExtractor_h
|
||||
#define mozilla_dom_BodyExtractor_h
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "nsString.h"
|
||||
|
||||
class nsIInputStream;
|
||||
class nsIGlobalObject;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class BodyExtractorBase
|
||||
{
|
||||
public:
|
||||
virtual nsresult GetAsStream(nsIInputStream** aResult,
|
||||
uint64_t* aContentLength,
|
||||
nsACString& aContentTypeWithCharset,
|
||||
nsACString& aCharset) const = 0;
|
||||
};
|
||||
|
||||
// The implementation versions of this template are:
|
||||
// ArrayBuffer, ArrayBufferView, nsIXHRSendable (Blob, FormData,
|
||||
// URLSearchParams), nsAString, nsIDocument, nsIInputStream.
|
||||
template<typename Type>
|
||||
class BodyExtractor final : public BodyExtractorBase
|
||||
{
|
||||
Type* mBody;
|
||||
public:
|
||||
explicit BodyExtractor(Type* aBody) : mBody(aBody)
|
||||
{}
|
||||
|
||||
nsresult GetAsStream(nsIInputStream** aResult,
|
||||
uint64_t* aContentLength,
|
||||
nsACString& aContentTypeWithCharset,
|
||||
nsACString& aCharset) const override;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_dom_BodyExtractor_h
|
||||
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
#include "Fetch.h"
|
||||
#include "FetchConsumer.h"
|
||||
#include "FetchStream.h"
|
||||
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "nsIStreamLoader.h"
|
||||
#include "nsIThreadRetargetableRequest.h"
|
||||
#include "nsIUnicodeDecoder.h"
|
||||
#include "nsIUnicodeEncoder.h"
|
||||
|
||||
#include "nsDOMString.h"
|
||||
#include "nsNetUtil.h"
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/BodyUtil.h"
|
||||
#include "mozilla/dom/EncodingUtils.h"
|
||||
#include "mozilla/dom/DOMError.h"
|
||||
#include "mozilla/dom/Exceptions.h"
|
||||
#include "mozilla/dom/FetchDriver.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
|
|
@ -35,8 +35,10 @@
|
|||
#include "mozilla/dom/Response.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/URLSearchParams.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "mozilla/dom/workers/ServiceWorkerManager.h"
|
||||
|
||||
#include "BodyExtractor.h"
|
||||
#include "FetchObserver.h"
|
||||
#include "InternalRequest.h"
|
||||
#include "InternalResponse.h"
|
||||
|
|
@ -722,154 +724,48 @@ WorkerFetchResolver::FlushConsoleReport()
|
|||
mReporter->FlushConsoleReports(worker->GetDocument());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
nsresult
|
||||
ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
|
||||
nsIInputStream** aStream,
|
||||
uint64_t& aContentLength)
|
||||
{
|
||||
aBuffer.ComputeLengthAndData();
|
||||
aContentLength = aBuffer.Length();
|
||||
//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,
|
||||
uint64_t& aContentLength)
|
||||
{
|
||||
aBuffer.ComputeLengthAndData();
|
||||
aContentLength = aBuffer.Length();
|
||||
//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
|
||||
ExtractFromBlob(const Blob& aBlob,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentType,
|
||||
uint64_t& aContentLength)
|
||||
{
|
||||
RefPtr<BlobImpl> impl = aBlob.Impl();
|
||||
ErrorResult rv;
|
||||
aContentLength = impl->GetSize(rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
impl->GetInternalStream(aStream, rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
nsAutoString type;
|
||||
impl->GetType(type);
|
||||
aContentType = NS_ConvertUTF16toUTF8(type);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ExtractFromFormData(FormData& aFormData,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentType,
|
||||
uint64_t& aContentLength)
|
||||
{
|
||||
nsAutoCString unusedCharset;
|
||||
return aFormData.GetSendInfo(aStream, &aContentLength,
|
||||
aContentType, unusedCharset);
|
||||
}
|
||||
|
||||
nsresult
|
||||
ExtractFromUSVString(const nsString& aStr,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentType,
|
||||
uint64_t& aContentLength)
|
||||
{
|
||||
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)) {
|
||||
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");
|
||||
aContentLength = outLen;
|
||||
|
||||
return NS_NewCStringInputStream(aStream, encoded);
|
||||
}
|
||||
|
||||
nsresult
|
||||
ExtractFromURLSearchParams(const URLSearchParams& aParams,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentType,
|
||||
uint64_t& aContentLength)
|
||||
{
|
||||
nsAutoString serialized;
|
||||
aParams.Stringify(serialized);
|
||||
aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
|
||||
aContentLength = serialized.Length();
|
||||
return NS_NewCStringInputStream(aStream, NS_ConvertUTF16toUTF8(serialized));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
nsresult
|
||||
ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
|
||||
ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentType,
|
||||
nsCString& aContentTypeWithCharset,
|
||||
uint64_t& aContentLength)
|
||||
{
|
||||
MOZ_ASSERT(aStream);
|
||||
nsAutoCString charset;
|
||||
aContentTypeWithCharset.SetIsVoid(true);
|
||||
|
||||
if (aBodyInit.IsArrayBuffer()) {
|
||||
const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer();
|
||||
return ExtractFromArrayBuffer(buf, aStream, aContentLength);
|
||||
BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsArrayBufferView()) {
|
||||
const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView();
|
||||
return ExtractFromArrayBufferView(buf, aStream, aContentLength);
|
||||
BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsBlob()) {
|
||||
const Blob& blob = aBodyInit.GetAsBlob();
|
||||
return ExtractFromBlob(blob, aStream, aContentType, aContentLength);
|
||||
Blob& blob = aBodyInit.GetAsBlob();
|
||||
BodyExtractor<nsIXHRSendable> body(&blob);
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsFormData()) {
|
||||
FormData& form = aBodyInit.GetAsFormData();
|
||||
return ExtractFromFormData(form, aStream, aContentType, aContentLength);
|
||||
FormData& formData = aBodyInit.GetAsFormData();
|
||||
BodyExtractor<nsIXHRSendable> body(&formData);
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsUSVString()) {
|
||||
nsAutoString str;
|
||||
str.Assign(aBodyInit.GetAsUSVString());
|
||||
return ExtractFromUSVString(str, aStream, aContentType, aContentLength);
|
||||
BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsURLSearchParams()) {
|
||||
URLSearchParams& params = aBodyInit.GetAsURLSearchParams();
|
||||
return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength);
|
||||
URLSearchParams& usp = aBodyInit.GetAsURLSearchParams();
|
||||
BodyExtractor<nsIXHRSendable> body(&usp);
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
|
||||
NS_NOTREACHED("Should never reach here");
|
||||
|
|
@ -877,69 +773,228 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDa
|
|||
}
|
||||
|
||||
nsresult
|
||||
ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
|
||||
ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentType,
|
||||
nsCString& aContentTypeWithCharset,
|
||||
uint64_t& aContentLength)
|
||||
{
|
||||
MOZ_ASSERT(aStream);
|
||||
MOZ_ASSERT(!*aStream);
|
||||
|
||||
nsAutoCString charset;
|
||||
aContentTypeWithCharset.SetIsVoid(true);
|
||||
|
||||
if (aBodyInit.IsArrayBuffer()) {
|
||||
const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer();
|
||||
return ExtractFromArrayBuffer(buf, aStream, aContentLength);
|
||||
BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsArrayBufferView()) {
|
||||
const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView();
|
||||
return ExtractFromArrayBufferView(buf, aStream, aContentLength);
|
||||
BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsBlob()) {
|
||||
const Blob& blob = aBodyInit.GetAsBlob();
|
||||
return ExtractFromBlob(blob, aStream, aContentType, aContentLength);
|
||||
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsBlob());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsFormData()) {
|
||||
FormData& form = aBodyInit.GetAsFormData();
|
||||
return ExtractFromFormData(form, aStream, aContentType, aContentLength);
|
||||
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsFormData());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsUSVString()) {
|
||||
nsAutoString str;
|
||||
str.Assign(aBodyInit.GetAsUSVString());
|
||||
return ExtractFromUSVString(str, aStream, aContentType, aContentLength);
|
||||
BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
if (aBodyInit.IsURLSearchParams()) {
|
||||
URLSearchParams& params = aBodyInit.GetAsURLSearchParams();
|
||||
return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength);
|
||||
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsURLSearchParams());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
|
||||
NS_NOTREACHED("Should never reach here");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentTypeWithCharset,
|
||||
uint64_t& aContentLength)
|
||||
{
|
||||
MOZ_ASSERT(aStream);
|
||||
MOZ_ASSERT(!*aStream);
|
||||
|
||||
// ReadableStreams should be handled by
|
||||
// BodyExtractorReadableStream::GetAsStream.
|
||||
MOZ_ASSERT(!aBodyInit.IsReadableStream());
|
||||
|
||||
nsAutoCString charset;
|
||||
aContentTypeWithCharset.SetIsVoid(true);
|
||||
|
||||
if (aBodyInit.IsArrayBuffer()) {
|
||||
BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
|
||||
if (aBodyInit.IsArrayBufferView()) {
|
||||
BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
|
||||
if (aBodyInit.IsBlob()) {
|
||||
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsBlob());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
|
||||
if (aBodyInit.IsFormData()) {
|
||||
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsFormData());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
|
||||
if (aBodyInit.IsUSVString()) {
|
||||
BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
|
||||
if (aBodyInit.IsURLSearchParams()) {
|
||||
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsURLSearchParams());
|
||||
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
|
||||
charset);
|
||||
}
|
||||
|
||||
NS_NOTREACHED("Should never reach here");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
template <class Derived>
|
||||
FetchBody<Derived>::FetchBody()
|
||||
FetchBody<Derived>::FetchBody(nsIGlobalObject* aOwner)
|
||||
: mWorkerPrivate(nullptr)
|
||||
, mOwner(aOwner)
|
||||
, mReadableStreamBody(nullptr)
|
||||
, mReadableStreamReader(nullptr)
|
||||
, mBodyUsed(false)
|
||||
{
|
||||
MOZ_ASSERT(aOwner);
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
mWorkerPrivate = GetCurrentThreadWorkerPrivate();
|
||||
MOZ_ASSERT(mWorkerPrivate);
|
||||
} else {
|
||||
mWorkerPrivate = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template
|
||||
FetchBody<Request>::FetchBody();
|
||||
FetchBody<Request>::FetchBody(nsIGlobalObject* aOwner);
|
||||
|
||||
template
|
||||
FetchBody<Response>::FetchBody();
|
||||
FetchBody<Response>::FetchBody(nsIGlobalObject* aOwner);
|
||||
|
||||
template <class Derived>
|
||||
FetchBody<Derived>::~FetchBody()
|
||||
{
|
||||
}
|
||||
|
||||
template
|
||||
FetchBody<Request>::~FetchBody();
|
||||
|
||||
template
|
||||
FetchBody<Response>::~FetchBody();
|
||||
|
||||
template <class Derived>
|
||||
bool
|
||||
FetchBody<Derived>::BodyUsed() const
|
||||
{
|
||||
if (mBodyUsed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this object is disturbed or locked, return false.
|
||||
if (mReadableStreamBody) {
|
||||
AutoJSAPI jsapi;
|
||||
if (!jsapi.Init(mOwner)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
JS::Rooted<JSObject*> body(cx, mReadableStreamBody);
|
||||
if (JS::ReadableStreamIsDisturbed(body) ||
|
||||
JS::ReadableStreamIsLocked(body) ||
|
||||
!JS::ReadableStreamIsReadable(body)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template
|
||||
bool
|
||||
FetchBody<Request>::BodyUsed() const;
|
||||
|
||||
template
|
||||
bool
|
||||
FetchBody<Response>::BodyUsed() const;
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
FetchBody<Derived>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv)
|
||||
{
|
||||
MOZ_ASSERT(aCx);
|
||||
|
||||
if (mBodyUsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBodyUsed = true;
|
||||
|
||||
// If we already have a ReadableStreamBody and it has been created by DOM, we
|
||||
// have to lock it now because it can have been shared with other objects.
|
||||
if (mReadableStreamBody) {
|
||||
JS::Rooted<JSObject*> readableStreamObj(aCx, mReadableStreamBody);
|
||||
if (JS::ReadableStreamGetMode(readableStreamObj) ==
|
||||
JS::ReadableStreamMode::ExternalSource) {
|
||||
LockStream(aCx, readableStreamObj, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// If this is not a native ReadableStream, let's activate the
|
||||
// FetchStreamReader.
|
||||
MOZ_ASSERT(mFetchStreamReader);
|
||||
JS::Rooted<JSObject*> reader(aCx);
|
||||
mFetchStreamReader->StartConsuming(aCx, readableStreamObj, &reader, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
mReadableStreamReader = reader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Request>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Response>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
|
||||
|
||||
template <class Derived>
|
||||
already_AddRefed<Promise>
|
||||
FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
|
||||
FetchBody<Derived>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv)
|
||||
{
|
||||
RefPtr<AbortSignal> signal = DerivedClass()->GetSignal();
|
||||
if (signal && signal->Aborted()) {
|
||||
|
|
@ -952,11 +1007,15 @@ FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
SetBodyUsed();
|
||||
SetBodyUsed(aCx, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> global = DerivedClass()->GetParentObject();
|
||||
|
||||
RefPtr<Promise> promise =
|
||||
FetchBodyConsumer<Derived>::Create(DerivedClass()->GetParentObject(),
|
||||
this, signal, aType, aRv);
|
||||
FetchBodyConsumer<Derived>::Create(global, this, signal, aType, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -966,11 +1025,11 @@ FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
|
|||
|
||||
template
|
||||
already_AddRefed<Promise>
|
||||
FetchBody<Request>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
|
||||
FetchBody<Request>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
|
||||
|
||||
template
|
||||
already_AddRefed<Promise>
|
||||
FetchBody<Response>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
|
||||
FetchBody<Response>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
|
|
@ -1000,5 +1059,170 @@ template
|
|||
void
|
||||
FetchBody<Response>::SetMimeType();
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
FetchBody<Derived>::SetReadableStreamBody(JSObject* aBody)
|
||||
{
|
||||
MOZ_ASSERT(!mReadableStreamBody);
|
||||
MOZ_ASSERT(aBody);
|
||||
mReadableStreamBody = aBody;
|
||||
}
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Request>::SetReadableStreamBody(JSObject* aBody);
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Response>::SetReadableStreamBody(JSObject* aBody);
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
FetchBody<Derived>::GetBody(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aBodyOut,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (mReadableStreamBody) {
|
||||
aBodyOut.set(mReadableStreamBody);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
DerivedClass()->GetBody(getter_AddRefs(inputStream));
|
||||
|
||||
if (!inputStream) {
|
||||
aBodyOut.set(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> body(aCx);
|
||||
FetchStream::Create(aCx, this, DerivedClass()->GetParentObject(),
|
||||
inputStream, &body, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(body);
|
||||
|
||||
// If the body has been already consumed, we lock the stream.
|
||||
if (BodyUsed()) {
|
||||
LockStream(aCx, body, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mReadableStreamBody = body;
|
||||
aBodyOut.set(mReadableStreamBody);
|
||||
}
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Request>::GetBody(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aMessage,
|
||||
ErrorResult& aRv);
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Response>::GetBody(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aMessage,
|
||||
ErrorResult& aRv);
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
FetchBody<Derived>::LockStream(JSContext* aCx,
|
||||
JS::HandleObject aStream,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
MOZ_ASSERT(JS::ReadableStreamGetMode(aStream) ==
|
||||
JS::ReadableStreamMode::ExternalSource);
|
||||
|
||||
// This is native stream, creating a reader will not execute any JS code.
|
||||
JS::Rooted<JSObject*> reader(aCx,
|
||||
JS::ReadableStreamGetReader(aCx, aStream,
|
||||
JS::ReadableStreamReaderMode::Default));
|
||||
if (!reader) {
|
||||
aRv.StealExceptionFromJSContext(aCx);
|
||||
return;
|
||||
}
|
||||
|
||||
mReadableStreamReader = reader;
|
||||
}
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Request>::LockStream(JSContext* aCx,
|
||||
JS::HandleObject aStream,
|
||||
ErrorResult& aRv);
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Response>::LockStream(JSContext* aCx,
|
||||
JS::HandleObject aStream,
|
||||
ErrorResult& aRv);
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
FetchBody<Derived>::MaybeTeeReadableStreamBody(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aBodyOut,
|
||||
FetchStreamReader** aStreamReader,
|
||||
nsIInputStream** aInputStream,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aStreamReader);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aInputStream);
|
||||
MOZ_DIAGNOSTIC_ASSERT(!BodyUsed());
|
||||
|
||||
aBodyOut.set(nullptr);
|
||||
*aStreamReader = nullptr;
|
||||
*aInputStream = nullptr;
|
||||
|
||||
if (!mReadableStreamBody) {
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> stream(aCx, mReadableStreamBody);
|
||||
|
||||
// If this is a ReadableStream with an external source, this has been
|
||||
// generated by a Fetch. In this case, Fetch will be able to recreate it
|
||||
// again when GetBody() is called.
|
||||
if (JS::ReadableStreamGetMode(stream) == JS::ReadableStreamMode::ExternalSource) {
|
||||
aBodyOut.set(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> branch1(aCx);
|
||||
JS::Rooted<JSObject*> branch2(aCx);
|
||||
|
||||
if (!JS::ReadableStreamTee(aCx, stream, &branch1, &branch2)) {
|
||||
aRv.StealExceptionFromJSContext(aCx);
|
||||
return;
|
||||
}
|
||||
|
||||
mReadableStreamBody = branch1;
|
||||
aBodyOut.set(branch2);
|
||||
|
||||
aRv = FetchStreamReader::Create(aCx, mOwner, aStreamReader, aInputStream);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Request>::MaybeTeeReadableStreamBody(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aMessage,
|
||||
FetchStreamReader** aStreamReader,
|
||||
nsIInputStream** aInputStream,
|
||||
ErrorResult& aRv);
|
||||
|
||||
template
|
||||
void
|
||||
FetchBody<Response>::MaybeTeeReadableStreamBody(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aMessage,
|
||||
FetchStreamReader** aStreamReader,
|
||||
nsIInputStream** aInputStream,
|
||||
ErrorResult& aRv);
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
|||
|
|
@ -17,17 +17,23 @@
|
|||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/FetchStreamReader.h"
|
||||
// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None
|
||||
#undef None
|
||||
#include "mozilla/dom/RequestBinding.h"
|
||||
|
||||
class nsIGlobalObject;
|
||||
class nsIEventTarget;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
|
||||
class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
|
||||
class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString;
|
||||
class BlobImpl;
|
||||
class InternalRequest;
|
||||
class OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
|
||||
class OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
|
||||
struct ReadableStream;
|
||||
class RequestOrUSVString;
|
||||
|
||||
namespace workers {
|
||||
|
|
@ -41,13 +47,20 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
|
|||
nsresult
|
||||
UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest);
|
||||
|
||||
/* Deal with unwieldy long webIDL-generated type names */
|
||||
namespace fetch {
|
||||
typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString BodyInit;
|
||||
typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString ResponseBodyInit;
|
||||
typedef OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString OwningBodyInit;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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 OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
|
||||
ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentType,
|
||||
uint64_t& aContentLength);
|
||||
|
|
@ -56,7 +69,17 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDa
|
|||
* Non-owning version.
|
||||
*/
|
||||
nsresult
|
||||
ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
|
||||
ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentType,
|
||||
uint64_t& aContentLength);
|
||||
|
||||
/*
|
||||
* Non-owning version. This method should go away when BodyInit will contain
|
||||
* ReadableStream.
|
||||
*/
|
||||
nsresult
|
||||
ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit,
|
||||
nsIInputStream** aStream,
|
||||
nsCString& aContentType,
|
||||
uint64_t& aContentLength);
|
||||
|
|
@ -72,6 +95,19 @@ enum FetchConsumeType
|
|||
CONSUME_TEXT,
|
||||
};
|
||||
|
||||
class FetchStreamHolder
|
||||
{
|
||||
public:
|
||||
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
|
||||
|
||||
virtual void NullifyStream() = 0;
|
||||
|
||||
virtual void MarkAsRead() = 0;
|
||||
|
||||
virtual JSObject* ReadableStreamBody() = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* FetchBody's body consumption uses nsIInputStreamPump to read from the
|
||||
* underlying stream to a block of memory, which is then adopted by
|
||||
|
|
@ -106,54 +142,82 @@ enum FetchConsumeType
|
|||
* The pump is always released on the main thread.
|
||||
*/
|
||||
template <class Derived>
|
||||
class FetchBody
|
||||
class FetchBody : public FetchStreamHolder
|
||||
{
|
||||
public:
|
||||
friend class FetchBodyConsumer<Derived>;
|
||||
|
||||
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
|
||||
|
||||
bool
|
||||
BodyUsed() const { return mBodyUsed; }
|
||||
BodyUsed() const;
|
||||
|
||||
already_AddRefed<Promise>
|
||||
ArrayBuffer(ErrorResult& aRv)
|
||||
ArrayBuffer(JSContext* aCx, ErrorResult& aRv)
|
||||
{
|
||||
return ConsumeBody(CONSUME_ARRAYBUFFER, aRv);
|
||||
return ConsumeBody(aCx, CONSUME_ARRAYBUFFER, aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Blob(ErrorResult& aRv)
|
||||
Blob(JSContext* aCx, ErrorResult& aRv)
|
||||
{
|
||||
return ConsumeBody(CONSUME_BLOB, aRv);
|
||||
return ConsumeBody(aCx, CONSUME_BLOB, aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
FormData(ErrorResult& aRv)
|
||||
FormData(JSContext* aCx, ErrorResult& aRv)
|
||||
{
|
||||
return ConsumeBody(CONSUME_FORMDATA, aRv);
|
||||
return ConsumeBody(aCx, CONSUME_FORMDATA, aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Json(ErrorResult& aRv)
|
||||
Json(JSContext* aCx, ErrorResult& aRv)
|
||||
{
|
||||
return ConsumeBody(CONSUME_JSON, aRv);
|
||||
return ConsumeBody(aCx, CONSUME_JSON, aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Text(ErrorResult& aRv)
|
||||
Text(JSContext* aCx, ErrorResult& aRv)
|
||||
{
|
||||
return ConsumeBody(CONSUME_TEXT, aRv);
|
||||
return ConsumeBody(aCx, CONSUME_TEXT, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
GetBody(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aBodyOut,
|
||||
ErrorResult& aRv);
|
||||
|
||||
// If the body contains a ReadableStream body object, this method produces a
|
||||
// tee() of it.
|
||||
void
|
||||
MaybeTeeReadableStreamBody(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aBodyOut,
|
||||
FetchStreamReader** aStreamReader,
|
||||
nsIInputStream** aInputStream,
|
||||
ErrorResult& aRv);
|
||||
|
||||
// Utility public methods accessed by various runnables.
|
||||
|
||||
// This method _must_ be called in order to set the body as used. If the body
|
||||
// is a ReadableStream, this method will start reading the stream.
|
||||
// More in details, this method does:
|
||||
// 1) It uses an internal flag to track if the body is used. This is tracked
|
||||
// separately from the ReadableStream disturbed state due to purely native
|
||||
// streams.
|
||||
// 2) If there is a ReadableStream reflector for the native stream it is
|
||||
// Locked.
|
||||
// 3) If there is a JS ReadableStream then we begin pumping it into the native
|
||||
// body stream. This effectively locks and disturbs the stream.
|
||||
//
|
||||
// Note that JSContext is used only if there is a ReadableStream (this can
|
||||
// happen because the body is a ReadableStream or because attribute body has
|
||||
// already been used by content). If something goes wrong using
|
||||
// ReadableStream, errors will be reported via ErrorResult and not as JS
|
||||
// exceptions in JSContext. This is done in order to have a centralized error
|
||||
// reporting way.
|
||||
//
|
||||
// Exceptions generated when reading from the ReadableStream are directly sent
|
||||
// to the Console.
|
||||
void
|
||||
SetBodyUsed()
|
||||
{
|
||||
mBodyUsed = true;
|
||||
}
|
||||
SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
|
||||
|
||||
const nsCString&
|
||||
MimeType() const
|
||||
|
|
@ -161,19 +225,54 @@ public:
|
|||
return mMimeType;
|
||||
}
|
||||
|
||||
// FetchStreamHolder
|
||||
void
|
||||
NullifyStream() override
|
||||
{
|
||||
mReadableStreamBody = nullptr;
|
||||
mReadableStreamReader = nullptr;
|
||||
mFetchStreamReader = nullptr;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
ReadableStreamBody() override
|
||||
{
|
||||
MOZ_ASSERT(mReadableStreamBody);
|
||||
return mReadableStreamBody;
|
||||
}
|
||||
|
||||
void
|
||||
MarkAsRead() override
|
||||
{
|
||||
mBodyUsed = true;
|
||||
}
|
||||
|
||||
virtual AbortSignal*
|
||||
GetSignal() const = 0;
|
||||
|
||||
protected:
|
||||
FetchBody();
|
||||
nsCOMPtr<nsIGlobalObject> mOwner;
|
||||
|
||||
explicit FetchBody(nsIGlobalObject* aOwner);
|
||||
|
||||
// Always set whenever the FetchBody is created on the worker thread.
|
||||
workers::WorkerPrivate* mWorkerPrivate;
|
||||
|
||||
// This is the ReadableStream exposed to content. Its underlying source is a FetchStream object.
|
||||
JS::Heap<JSObject*> mReadableStreamBody;
|
||||
|
||||
// This is the Reader used to retrieve data from the body.
|
||||
JS::Heap<JSObject*> mReadableStreamReader;
|
||||
RefPtr<FetchStreamReader> mFetchStreamReader;
|
||||
|
||||
virtual ~FetchBody();
|
||||
|
||||
void
|
||||
SetMimeType();
|
||||
|
||||
void
|
||||
SetReadableStreamBody(JSObject* aBody);
|
||||
|
||||
private:
|
||||
Derived*
|
||||
DerivedClass() const
|
||||
|
|
@ -182,7 +281,10 @@ private:
|
|||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
|
||||
ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
|
||||
|
||||
void
|
||||
LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv);
|
||||
|
||||
bool
|
||||
IsOnTargetThread()
|
||||
|
|
@ -199,6 +301,9 @@ private:
|
|||
// Only ever set once, always on target thread.
|
||||
bool mBodyUsed;
|
||||
nsCString mMimeType;
|
||||
|
||||
// The main-thread event target for runnable dispatching.
|
||||
nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
|||
640
dom/fetch/FetchStream.cpp
Normal file
640
dom/fetch/FetchStream.cpp
Normal file
|
|
@ -0,0 +1,640 @@
|
|||
/* -*- Mode: C; tab-width: 8; 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 "FetchStream.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "nsITransport.h"
|
||||
#include "nsIStreamTransportService.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "WorkerPrivate.h"
|
||||
#include "WorkerRunnable.h"
|
||||
#include "Workers.h"
|
||||
|
||||
#include "mozilla/dom/DOMError.h"
|
||||
|
||||
#define FETCH_STREAM_FLAG 0
|
||||
|
||||
static NS_DEFINE_CID(kStreamTransportServiceCID,
|
||||
NS_STREAMTRANSPORTSERVICE_CID);
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
using namespace workers;
|
||||
|
||||
namespace {
|
||||
|
||||
class FetchStreamWorkerHolder final : public WorkerHolder
|
||||
{
|
||||
public:
|
||||
explicit FetchStreamWorkerHolder(FetchStream* aStream)
|
||||
: WorkerHolder()
|
||||
, mStream(aStream)
|
||||
, mWasNotified(false)
|
||||
{}
|
||||
|
||||
bool Notify(Status aStatus) override
|
||||
{
|
||||
if (!mWasNotified) {
|
||||
mWasNotified = true;
|
||||
mStream->Close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WorkerPrivate* GetWorkerPrivate() const
|
||||
{
|
||||
return mWorkerPrivate;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<FetchStream> mStream;
|
||||
bool mWasNotified;
|
||||
};
|
||||
|
||||
class FetchStreamWorkerHolderShutdown final : public WorkerControlRunnable
|
||||
{
|
||||
public:
|
||||
FetchStreamWorkerHolderShutdown(WorkerPrivate* aWorkerPrivate,
|
||||
UniquePtr<WorkerHolder>&& aHolder,
|
||||
nsCOMPtr<nsIGlobalObject>&& aGlobal,
|
||||
RefPtr<FetchStreamHolder>&& aStreamHolder)
|
||||
: WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
|
||||
, mHolder(Move(aHolder))
|
||||
, mGlobal(Move(aGlobal))
|
||||
, mStreamHolder(Move(aStreamHolder))
|
||||
{}
|
||||
|
||||
bool
|
||||
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
|
||||
{
|
||||
mHolder = nullptr;
|
||||
mGlobal = nullptr;
|
||||
|
||||
mStreamHolder->NullifyStream();
|
||||
mStreamHolder = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This runnable starts from a JS Thread. We need to disable a couple of
|
||||
// assertions by overriding the following methods.
|
||||
|
||||
bool
|
||||
PreDispatch(WorkerPrivate* aWorkerPrivate) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
|
||||
{}
|
||||
|
||||
private:
|
||||
UniquePtr<WorkerHolder> mHolder;
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
RefPtr<FetchStreamHolder> mStreamHolder;
|
||||
};
|
||||
|
||||
} // anonymous
|
||||
|
||||
NS_IMPL_ISUPPORTS(FetchStream, nsIInputStreamCallback, nsIObserver,
|
||||
nsISupportsWeakReference)
|
||||
|
||||
/* static */ void
|
||||
FetchStream::Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
|
||||
nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
|
||||
JS::MutableHandle<JSObject*> aStream, ErrorResult& aRv)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aCx);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aInputStream);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
|
||||
|
||||
RefPtr<FetchStream> stream = new FetchStream(aGlobal, aStreamHolder, aInputStream);
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (NS_WARN_IF(!os)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
aRv = os->AddObserver(stream, DOM_WINDOW_DESTROYED_TOPIC, true);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
|
||||
UniquePtr<FetchStreamWorkerHolder> holder(
|
||||
new FetchStreamWorkerHolder(stream));
|
||||
if (NS_WARN_IF(!holder->HoldWorker(workerPrivate, Closing))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note, this will create a ref-cycle between the holder and the stream.
|
||||
// The cycle is broken when the stream is closed or the worker begins
|
||||
// shutting down.
|
||||
stream->mWorkerHolder = Move(holder);
|
||||
}
|
||||
|
||||
if (!JS::HasReadableStreamCallbacks(aCx)) {
|
||||
JS::SetReadableStreamCallbacks(aCx,
|
||||
&FetchStream::RequestDataCallback,
|
||||
&FetchStream::WriteIntoReadRequestCallback,
|
||||
&FetchStream::CancelCallback,
|
||||
&FetchStream::ClosedCallback,
|
||||
&FetchStream::ErroredCallback,
|
||||
&FetchStream::FinalizeCallback);
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> body(aCx,
|
||||
JS::NewReadableExternalSourceStreamObject(aCx, stream, FETCH_STREAM_FLAG));
|
||||
if (!body) {
|
||||
aRv.StealExceptionFromJSContext(aCx);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will be released in FetchStream::FinalizeCallback(). We are
|
||||
// guaranteed the jsapi will call FinalizeCallback when ReadableStream
|
||||
// js object is finalized.
|
||||
NS_ADDREF(stream.get());
|
||||
|
||||
aStream.set(body);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FetchStream::RequestDataCallback(JSContext* aCx,
|
||||
JS::HandleObject aStream,
|
||||
void* aUnderlyingSource,
|
||||
uint8_t aFlags,
|
||||
size_t aDesiredSize)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
|
||||
MOZ_DIAGNOSTIC_ASSERT(JS::ReadableStreamIsDisturbed(aStream));
|
||||
|
||||
RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
|
||||
stream->AssertIsOnOwningThread();
|
||||
|
||||
MutexAutoLock lock(stream->mMutex);
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(stream->mState == eInitializing ||
|
||||
stream->mState == eWaiting ||
|
||||
stream->mState == eChecking ||
|
||||
stream->mState == eReading);
|
||||
|
||||
if (stream->mState == eReading) {
|
||||
// We are already reading data.
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream->mState == eChecking) {
|
||||
// If we are looking for more data, there is nothing else we should do:
|
||||
// let's move this checking operation in a reading.
|
||||
MOZ_ASSERT(stream->mInputStream);
|
||||
stream->mState = eReading;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream->mState == eInitializing) {
|
||||
// The stream has been used for the first time.
|
||||
stream->mStreamHolder->MarkAsRead();
|
||||
}
|
||||
|
||||
stream->mState = eReading;
|
||||
|
||||
if (!stream->mInputStream) {
|
||||
// This is the first use of the stream. Let's convert the
|
||||
// mOriginalInputStream into an nsIAsyncInputStream.
|
||||
MOZ_ASSERT(stream->mOriginalInputStream);
|
||||
|
||||
bool nonBlocking = false;
|
||||
nsresult rv = stream->mOriginalInputStream->IsNonBlocking(&nonBlocking);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
stream->ErrorPropagation(aCx, lock, aStream, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIAsyncInputStream> asyncStream =
|
||||
do_QueryInterface(stream->mOriginalInputStream);
|
||||
if (!nonBlocking || !asyncStream) {
|
||||
nsCOMPtr<nsIStreamTransportService> sts =
|
||||
do_GetService(kStreamTransportServiceCID, &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
stream->ErrorPropagation(aCx, lock, aStream, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsITransport> transport;
|
||||
rv = sts->CreateInputTransport(stream->mOriginalInputStream,
|
||||
/* aStartOffset */ 0,
|
||||
/* aReadLimit */ -1,
|
||||
/* aCloseWhenDone */ true,
|
||||
getter_AddRefs(transport));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
stream->ErrorPropagation(aCx, lock, aStream, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIInputStream> wrapper;
|
||||
rv = transport->OpenInputStream(/* aFlags */ 0,
|
||||
/* aSegmentSize */ 0,
|
||||
/* aSegmentCount */ 0,
|
||||
getter_AddRefs(wrapper));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
stream->ErrorPropagation(aCx, lock, aStream, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
asyncStream = do_QueryInterface(wrapper);
|
||||
}
|
||||
|
||||
stream->mInputStream = asyncStream;
|
||||
stream->mOriginalInputStream = nullptr;
|
||||
}
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
|
||||
MOZ_DIAGNOSTIC_ASSERT(!stream->mOriginalInputStream);
|
||||
|
||||
nsresult rv =
|
||||
stream->mInputStream->AsyncWait(stream, 0, 0, stream->mOwningEventTarget);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
stream->ErrorPropagation(aCx, lock, aStream, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
// All good.
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FetchStream::WriteIntoReadRequestCallback(JSContext* aCx,
|
||||
JS::HandleObject aStream,
|
||||
void* aUnderlyingSource,
|
||||
uint8_t aFlags, void* aBuffer,
|
||||
size_t aLength, size_t* aByteWritten)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aBuffer);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aByteWritten);
|
||||
|
||||
RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
|
||||
stream->AssertIsOnOwningThread();
|
||||
|
||||
MutexAutoLock lock(stream->mMutex);
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
|
||||
MOZ_DIAGNOSTIC_ASSERT(stream->mState == eWriting);
|
||||
stream->mState = eChecking;
|
||||
|
||||
uint32_t written;
|
||||
nsresult rv =
|
||||
stream->mInputStream->Read(static_cast<char*>(aBuffer), aLength, &written);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
stream->ErrorPropagation(aCx, lock, aStream, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
*aByteWritten = written;
|
||||
|
||||
if (written == 0) {
|
||||
stream->CloseAndReleaseObjects(aCx, lock, aStream);
|
||||
return;
|
||||
}
|
||||
|
||||
rv = stream->mInputStream->AsyncWait(stream, 0, 0, stream->mOwningEventTarget);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
stream->ErrorPropagation(aCx, lock, aStream, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
// All good.
|
||||
}
|
||||
|
||||
/* static */ JS::Value
|
||||
FetchStream::CancelCallback(JSContext* aCx, JS::HandleObject aStream,
|
||||
void* aUnderlyingSource, uint8_t aFlags,
|
||||
JS::HandleValue aReason)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
|
||||
|
||||
// This is safe because we created an extra reference in FetchStream::Create()
|
||||
// that won't be released until FetchStream::FinalizeCallback() is called.
|
||||
// We are guaranteed that won't happen until the js ReadableStream object
|
||||
// is finalized.
|
||||
FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
|
||||
stream->AssertIsOnOwningThread();
|
||||
|
||||
if (stream->mInputStream) {
|
||||
stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
|
||||
}
|
||||
|
||||
stream->ReleaseObjects();
|
||||
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FetchStream::ClosedCallback(JSContext* aCx, JS::HandleObject aStream,
|
||||
void* aUnderlyingSource, uint8_t aFlags)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FetchStream::ErroredCallback(JSContext* aCx, JS::HandleObject aStream,
|
||||
void* aUnderlyingSource, uint8_t aFlags,
|
||||
JS::HandleValue aReason)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
|
||||
|
||||
// This is safe because we created an extra reference in FetchStream::Create()
|
||||
// that won't be released until FetchStream::FinalizeCallback() is called.
|
||||
// We are guaranteed that won't happen until the js ReadableStream object
|
||||
// is finalized.
|
||||
FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
|
||||
stream->AssertIsOnOwningThread();
|
||||
|
||||
if (stream->mState == eInitializing) {
|
||||
// The stream has been used for the first time.
|
||||
stream->mStreamHolder->MarkAsRead();
|
||||
}
|
||||
|
||||
if (stream->mInputStream) {
|
||||
stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
|
||||
}
|
||||
|
||||
stream->ReleaseObjects();
|
||||
}
|
||||
|
||||
void
|
||||
FetchStream::FinalizeCallback(void* aUnderlyingSource, uint8_t aFlags)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
|
||||
|
||||
// This can be called in any thread.
|
||||
|
||||
// This takes ownership of the ref created in FetchStream::Create().
|
||||
RefPtr<FetchStream> stream =
|
||||
dont_AddRef(static_cast<FetchStream*>(aUnderlyingSource));
|
||||
|
||||
stream->ReleaseObjects();
|
||||
}
|
||||
|
||||
FetchStream::FetchStream(nsIGlobalObject* aGlobal,
|
||||
FetchStreamHolder* aStreamHolder,
|
||||
nsIInputStream* aInputStream)
|
||||
: mMutex("FetchStream::mMutex")
|
||||
, mState(eInitializing)
|
||||
, mGlobal(aGlobal)
|
||||
, mStreamHolder(aStreamHolder)
|
||||
, mOriginalInputStream(aInputStream)
|
||||
// TODO: Replace with mGlobal->EventTargetFor(TaskCategory::Other)
|
||||
// When we have the Dispatcher API in the tree, see Issue #1442
|
||||
, mOwningEventTarget(NS_GetCurrentThread())
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(aInputStream);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
|
||||
}
|
||||
|
||||
FetchStream::~FetchStream()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
FetchStream::ErrorPropagation(JSContext* aCx,
|
||||
const MutexAutoLock& aProofOfLock,
|
||||
JS::HandleObject aStream,
|
||||
nsresult aError)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
// Nothing to do.
|
||||
if (mState == eClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's close the stream.
|
||||
if (aError == NS_BASE_STREAM_CLOSED) {
|
||||
CloseAndReleaseObjects(aCx, aProofOfLock, aStream);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
|
||||
|
||||
// Let's use a generic error.
|
||||
RefPtr<DOMError> error = new DOMError(window, NS_ERROR_DOM_TYPE_ERR);
|
||||
|
||||
JS::Rooted<JS::Value> errorValue(aCx);
|
||||
if (ToJSValue(aCx, error, &errorValue)) {
|
||||
MutexAutoUnlock unlock(mMutex);
|
||||
JS::ReadableStreamError(aCx, aStream, errorValue);
|
||||
}
|
||||
|
||||
ReleaseObjects(aProofOfLock);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
FetchStream::OnInputStreamReady(nsIAsyncInputStream* aStream)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_DIAGNOSTIC_ASSERT(aStream);
|
||||
|
||||
Maybe<MutexAutoLock> lock;
|
||||
lock.emplace(mMutex);
|
||||
|
||||
// Already closed. We have nothing else to do here.
|
||||
if (mState == eClosed) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoMicroTask mt;
|
||||
AutoEntryScript aes(mGlobal, "fetch body data available");
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(mInputStream);
|
||||
MOZ_DIAGNOSTIC_ASSERT(mState == eReading || mState == eChecking);
|
||||
|
||||
JSContext* cx = aes.cx();
|
||||
JS::Rooted<JSObject*> stream(cx, mStreamHolder->ReadableStreamBody());
|
||||
|
||||
uint64_t size = 0;
|
||||
nsresult rv = mInputStream->Available(&size);
|
||||
if (NS_SUCCEEDED(rv) && size == 0) {
|
||||
// In theory this should not happen. If size is 0, the stream should be
|
||||
// considered closed.
|
||||
rv = NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
// No warning for stream closed.
|
||||
if (rv == NS_BASE_STREAM_CLOSED || NS_WARN_IF(NS_FAILED(rv))) {
|
||||
ErrorPropagation(cx, *lock, stream, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// This extra checking is completed. Let's wait for the next read request.
|
||||
if (mState == eChecking) {
|
||||
mState = eWaiting;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mState = eWriting;
|
||||
|
||||
lock.reset();
|
||||
|
||||
JS::ReadableStreamUpdateDataAvailableFromSource(cx, stream, size);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
FetchStream::RetrieveInputStream(void* aUnderlyingReadableStreamSource,
|
||||
nsIInputStream** aInputStream)
|
||||
{
|
||||
MOZ_ASSERT(aUnderlyingReadableStreamSource);
|
||||
MOZ_ASSERT(aInputStream);
|
||||
|
||||
RefPtr<FetchStream> stream =
|
||||
static_cast<FetchStream*>(aUnderlyingReadableStreamSource);
|
||||
stream->AssertIsOnOwningThread();
|
||||
|
||||
// if mOriginalInputStream is null, the reading already started. We don't want
|
||||
// to expose the internal inputStream.
|
||||
if (NS_WARN_IF(!stream->mOriginalInputStream)) {
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = stream->mOriginalInputStream;
|
||||
inputStream.forget(aInputStream);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
FetchStream::Close()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (mState == eClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
|
||||
ReleaseObjects(lock);
|
||||
return;
|
||||
}
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::Rooted<JSObject*> stream(cx, mStreamHolder->ReadableStreamBody());
|
||||
|
||||
CloseAndReleaseObjects(cx, lock, stream);
|
||||
}
|
||||
|
||||
void
|
||||
FetchStream::CloseAndReleaseObjects(JSContext* aCx,
|
||||
const MutexAutoLock& aProofOfLock,
|
||||
JS::HandleObject aStream)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_DIAGNOSTIC_ASSERT(mState != eClosed);
|
||||
|
||||
ReleaseObjects(aProofOfLock);
|
||||
|
||||
MutexAutoUnlock unlock(mMutex);
|
||||
|
||||
if (JS::ReadableStreamIsReadable(aStream)) {
|
||||
JS::ReadableStreamClose(aCx, aStream);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FetchStream::ReleaseObjects()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
ReleaseObjects(lock);
|
||||
}
|
||||
|
||||
void
|
||||
FetchStream::ReleaseObjects(const MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
// This method can be called on 2 possible threads: the owning one and a JS
|
||||
// thread used to release resources. If we are on the JS thread, we need to
|
||||
// dispatch a runnable to go back to the owning thread in order to release
|
||||
// resources correctly.
|
||||
|
||||
if (mState == eClosed) {
|
||||
// Already gone. Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
mState = eClosed;
|
||||
|
||||
if (mWorkerHolder) {
|
||||
RefPtr<FetchStreamWorkerHolderShutdown> r =
|
||||
new FetchStreamWorkerHolderShutdown(
|
||||
static_cast<FetchStreamWorkerHolder*>(mWorkerHolder.get())->GetWorkerPrivate(),
|
||||
Move(mWorkerHolder), Move(mGlobal), Move(mStreamHolder));
|
||||
r->Dispatch();
|
||||
} else {
|
||||
RefPtr<FetchStream> self = this;
|
||||
RefPtr<Runnable> r = NS_NewRunnableFunction(
|
||||
[self] () {
|
||||
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
|
||||
if (os) {
|
||||
os->RemoveObserver(self, DOM_WINDOW_DESTROYED_TOPIC);
|
||||
}
|
||||
self->mGlobal = nullptr;
|
||||
|
||||
self->mStreamHolder->NullifyStream();
|
||||
self->mStreamHolder = nullptr;
|
||||
});
|
||||
|
||||
NS_DispatchToMainThread(r);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void
|
||||
FetchStream::AssertIsOnOwningThread()
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(FetchStream);
|
||||
}
|
||||
#endif
|
||||
|
||||
// nsIObserver
|
||||
// -----------
|
||||
|
||||
NS_IMETHODIMP
|
||||
FetchStream::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
MOZ_ASSERT(strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0);
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
|
||||
if (SameCOMIdentity(aSubject, window)) {
|
||||
Close();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
158
dom/fetch/FetchStream.h
Normal file
158
dom/fetch/FetchStream.h
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/* -*- Mode: C; tab-width: 8; 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_FetchStream_h
|
||||
#define mozilla_dom_FetchStream_h
|
||||
|
||||
#include "Fetch.h"
|
||||
#include "jsapi.h"
|
||||
#include "nsIAsyncInputStream.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsWeakReference.h"
|
||||
|
||||
class nsIGlobalObject;
|
||||
|
||||
class nsIInputStream;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
namespace workers {
|
||||
class WorkerHolder;
|
||||
}
|
||||
|
||||
class FetchStreamHolder;
|
||||
|
||||
class FetchStream final : public nsIInputStreamCallback
|
||||
, public nsIObserver
|
||||
, public nsSupportsWeakReference
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIINPUTSTREAMCALLBACK
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
static void
|
||||
Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
|
||||
nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
|
||||
JS::MutableHandle<JSObject*> aStream, ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Close();
|
||||
|
||||
static nsresult
|
||||
RetrieveInputStream(void* aUnderlyingReadableStreamSource,
|
||||
nsIInputStream** aInputStream);
|
||||
private:
|
||||
FetchStream(nsIGlobalObject* aGlobal,
|
||||
FetchStreamHolder* aStreamHolder,
|
||||
nsIInputStream* aInputStream);
|
||||
~FetchStream();
|
||||
|
||||
#ifdef DEBUG
|
||||
void
|
||||
AssertIsOnOwningThread();
|
||||
#else
|
||||
void
|
||||
AssertIsOnOwningThread() {}
|
||||
#endif
|
||||
|
||||
static void
|
||||
RequestDataCallback(JSContext* aCx, JS::HandleObject aStream,
|
||||
void* aUnderlyingSource, uint8_t aFlags,
|
||||
size_t aDesiredSize);
|
||||
|
||||
static void
|
||||
WriteIntoReadRequestCallback(JSContext* aCx, JS::HandleObject aStream,
|
||||
void* aUnderlyingSource, uint8_t aFlags,
|
||||
void* aBuffer, size_t aLength,
|
||||
size_t* aByteWritten);
|
||||
|
||||
static JS::Value
|
||||
CancelCallback(JSContext* aCx, JS::HandleObject aStream,
|
||||
void* aUnderlyingSource, uint8_t aFlags,
|
||||
JS::HandleValue aReason);
|
||||
|
||||
static void
|
||||
ClosedCallback(JSContext* aCx, JS::HandleObject aStream,
|
||||
void* aUnderlyingSource, uint8_t aFlags);
|
||||
|
||||
static void
|
||||
ErroredCallback(JSContext* aCx, JS::HandleObject aStream,
|
||||
void* aUnderlyingSource, uint8_t aFlags,
|
||||
JS::HandleValue reason);
|
||||
|
||||
static void
|
||||
FinalizeCallback(void* aUnderlyingSource, uint8_t aFlags);
|
||||
|
||||
void
|
||||
ErrorPropagation(JSContext* aCx,
|
||||
const MutexAutoLock& aProofOfLock,
|
||||
JS::HandleObject aStream, nsresult aRv);
|
||||
|
||||
void
|
||||
CloseAndReleaseObjects(JSContext* aCx,
|
||||
const MutexAutoLock& aProofOfLock,
|
||||
JS::HandleObject aSteam);
|
||||
|
||||
class WorkerShutdown;
|
||||
|
||||
void
|
||||
ReleaseObjects(const MutexAutoLock& aProofOfLock);
|
||||
|
||||
void
|
||||
ReleaseObjects();
|
||||
|
||||
// Common methods
|
||||
|
||||
enum State {
|
||||
// This is the beginning state before any reading operation.
|
||||
eInitializing,
|
||||
|
||||
// RequestDataCallback has not been called yet. We haven't started to read
|
||||
// data from the stream yet.
|
||||
eWaiting,
|
||||
|
||||
// We are reading data in a separate I/O thread.
|
||||
eReading,
|
||||
|
||||
// We are ready to write something in the JS Buffer.
|
||||
eWriting,
|
||||
|
||||
// After a writing, we want to check if the stream is closed. After the
|
||||
// check, we go back to eWaiting. If a reading request happens in the
|
||||
// meantime, we move to eReading state.
|
||||
eChecking,
|
||||
|
||||
// Operation completed.
|
||||
eClosed,
|
||||
};
|
||||
|
||||
// We need a mutex because JS engine can release FetchStream on a non-owning
|
||||
// thread. We must be sure that the releasing of resources doesn't trigger
|
||||
// race conditions.
|
||||
Mutex mMutex;
|
||||
|
||||
// Protected by mutex.
|
||||
State mState;
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
RefPtr<FetchStreamHolder> mStreamHolder;
|
||||
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
|
||||
|
||||
// This is the original inputStream received during the CTOR. It will be
|
||||
// converted into an nsIAsyncInputStream and stored into mInputStream at the
|
||||
// first use.
|
||||
nsCOMPtr<nsIInputStream> mOriginalInputStream;
|
||||
nsCOMPtr<nsIAsyncInputStream> mInputStream;
|
||||
|
||||
UniquePtr<workers::WorkerHolder> mWorkerHolder;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_dom_FetchStream_h
|
||||
405
dom/fetch/FetchStreamReader.cpp
Normal file
405
dom/fetch/FetchStreamReader.cpp
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FetchStreamReader.h"
|
||||
#include "InternalResponse.h"
|
||||
#include "mozilla/dom/PromiseBinding.h"
|
||||
#include "mozilla/dom/DOMException.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
using namespace workers;
|
||||
|
||||
namespace {
|
||||
|
||||
class FetchStreamReaderWorkerHolder final : public WorkerHolder
|
||||
{
|
||||
public:
|
||||
explicit FetchStreamReaderWorkerHolder(FetchStreamReader* aReader)
|
||||
: WorkerHolder(WorkerHolder::Behavior::AllowIdleShutdownStart)
|
||||
, mReader(aReader)
|
||||
, mWasNotified(false)
|
||||
{}
|
||||
|
||||
bool Notify(Status aStatus) override
|
||||
{
|
||||
if (!mWasNotified) {
|
||||
mWasNotified = true;
|
||||
// The WorkerPrivate does have a context available, and we could pass it
|
||||
// here to trigger cancellation of the reader, but the author of this
|
||||
// comment chickened out.
|
||||
mReader->CloseAndRelease(nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<FetchStreamReader> mReader;
|
||||
bool mWasNotified;
|
||||
};
|
||||
|
||||
} // anonymous
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchStreamReader)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchStreamReader)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(FetchStreamReader)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FetchStreamReader)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FetchStreamReader)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FetchStreamReader)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReader)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchStreamReader)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStreamCallback)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
/* static */ nsresult
|
||||
FetchStreamReader::Create(JSContext* aCx, nsIGlobalObject* aGlobal,
|
||||
FetchStreamReader** aStreamReader,
|
||||
nsIInputStream** aInputStream)
|
||||
{
|
||||
MOZ_ASSERT(aCx);
|
||||
MOZ_ASSERT(aGlobal);
|
||||
MOZ_ASSERT(aStreamReader);
|
||||
MOZ_ASSERT(aInputStream);
|
||||
|
||||
RefPtr<FetchStreamReader> streamReader = new FetchStreamReader(aGlobal);
|
||||
|
||||
nsCOMPtr<nsIAsyncInputStream> pipeIn;
|
||||
|
||||
nsresult rv = NS_NewPipe2(getter_AddRefs(pipeIn),
|
||||
getter_AddRefs(streamReader->mPipeOut),
|
||||
true, true, 0, 0);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
|
||||
// We need to know when the worker goes away.
|
||||
UniquePtr<FetchStreamReaderWorkerHolder> holder(
|
||||
new FetchStreamReaderWorkerHolder(streamReader));
|
||||
if (NS_WARN_IF(!holder->HoldWorker(workerPrivate, Closing))) {
|
||||
streamReader->mPipeOut->CloseWithStatus(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
}
|
||||
|
||||
// These 2 objects create a ref-cycle here that is broken when the stream is
|
||||
// closed or the worker shutsdown.
|
||||
streamReader->mWorkerHolder = Move(holder);
|
||||
}
|
||||
|
||||
pipeIn.forget(aInputStream);
|
||||
streamReader.forget(aStreamReader);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
FetchStreamReader::FetchStreamReader(nsIGlobalObject* aGlobal)
|
||||
: mGlobal(aGlobal)
|
||||
// TODO: Replace with mGlobal->EventTargetFor(TaskCategory::Other)
|
||||
// When we have the Dispatcher API in the tree, see Issue #1442
|
||||
, mOwningEventTarget(NS_GetCurrentThread())
|
||||
, mBufferRemaining(0)
|
||||
, mBufferOffset(0)
|
||||
, mStreamClosed(false)
|
||||
{
|
||||
MOZ_ASSERT(aGlobal);
|
||||
}
|
||||
|
||||
FetchStreamReader::~FetchStreamReader()
|
||||
{
|
||||
CloseAndRelease(nullptr, NS_BASE_STREAM_CLOSED);
|
||||
}
|
||||
|
||||
// If a context is provided, an attempt will be made to cancel the reader. The
|
||||
// only situation where we don't expect to have a context is when closure is
|
||||
// being triggered from the destructor or the WorkerHolder is notifying. If
|
||||
// we're at the destructor, it's far too late to cancel anything. And if the
|
||||
// WorkerHolder is being notified, the global is going away, so there's also
|
||||
// no need to do further JS work.
|
||||
void
|
||||
FetchStreamReader::CloseAndRelease(JSContext* aCx, nsresult aStatus)
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
|
||||
|
||||
if (mStreamClosed) {
|
||||
// Already closed.
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<FetchStreamReader> kungFuDeathGrip = this;
|
||||
|
||||
if (aCx) {
|
||||
MOZ_ASSERT(mReader);
|
||||
|
||||
RefPtr<DOMException> error = DOMException::Create(aStatus);
|
||||
|
||||
JS::Rooted<JS::Value> errorValue(aCx);
|
||||
if (ToJSValue(aCx, error, &errorValue)) {
|
||||
JS::Rooted<JSObject*> reader(aCx, mReader);
|
||||
// It's currently safe to cancel an already closed reader because, per the
|
||||
// comments in ReadableStream::cancel() conveying the spec, step 2 of
|
||||
// 3.4.3 that specified ReadableStreamCancel is: If stream.[[state]] is
|
||||
// "closed", return a new promise resolved with undefined.
|
||||
JS::ReadableStreamReaderCancel(aCx, reader, errorValue);
|
||||
}
|
||||
}
|
||||
mStreamClosed = true;
|
||||
|
||||
mGlobal = nullptr;
|
||||
|
||||
mPipeOut->CloseWithStatus(aStatus);
|
||||
mPipeOut = nullptr;
|
||||
|
||||
mWorkerHolder = nullptr;
|
||||
|
||||
mReader = nullptr;
|
||||
mBuffer = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
FetchStreamReader::StartConsuming(JSContext* aCx,
|
||||
JS::HandleObject aStream,
|
||||
JS::MutableHandle<JSObject*> aReader,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mReader);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aStream);
|
||||
|
||||
JS::Rooted<JSObject*> reader(aCx,
|
||||
JS::ReadableStreamGetReader(aCx, aStream,
|
||||
JS::ReadableStreamReaderMode::Default));
|
||||
if (!reader) {
|
||||
aRv.StealExceptionFromJSContext(aCx);
|
||||
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
mReader = reader;
|
||||
aReader.set(reader);
|
||||
|
||||
aRv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// nsIOutputStreamCallback interface
|
||||
|
||||
NS_IMETHODIMP
|
||||
FetchStreamReader::OnOutputStreamReady(nsIAsyncOutputStream* aStream)
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
|
||||
MOZ_ASSERT(aStream == mPipeOut);
|
||||
MOZ_ASSERT(mReader);
|
||||
|
||||
if (mStreamClosed) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (mBuffer) {
|
||||
return WriteBuffer();
|
||||
}
|
||||
|
||||
// TODO: We need to verify this is the correct global per the spec.
|
||||
// See bug 1385890.
|
||||
AutoEntryScript aes(mGlobal, "ReadableStreamReader.read", !mWorkerHolder);
|
||||
|
||||
JS::Rooted<JSObject*> reader(aes.cx(), mReader);
|
||||
JS::Rooted<JSObject*> promise(aes.cx(),
|
||||
JS::ReadableStreamDefaultReaderRead(aes.cx(),
|
||||
reader));
|
||||
if (NS_WARN_IF(!promise)) {
|
||||
// Let's close the stream.
|
||||
CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RefPtr<Promise> domPromise = Promise::CreateFromExisting(mGlobal, promise);
|
||||
if (NS_WARN_IF(!domPromise)) {
|
||||
// Let's close the stream.
|
||||
CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Let's wait.
|
||||
domPromise->AppendNativeHandler(this);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
FetchStreamReader::ResolvedCallback(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
if (mStreamClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This promise should be resolved with { done: boolean, value: something },
|
||||
// "value" is interesting only if done is false.
|
||||
|
||||
// We don't want to play with JS api, let's WebIDL bindings doing it for us.
|
||||
// FetchReadableStreamReadDataDone is a dictionary with just a boolean, if the
|
||||
// parsing succeeded, we can proceed with the parsing of the "value", which it
|
||||
// must be a Uint8Array.
|
||||
FetchReadableStreamReadDataDone valueDone;
|
||||
if (!valueDone.Init(aCx, aValue)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueDone.mDone) {
|
||||
// Stream is completed.
|
||||
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
UniquePtr<FetchReadableStreamReadDataArray> value(
|
||||
new FetchReadableStreamReadDataArray);
|
||||
if (!value->Init(aCx, aValue) || !value->mValue.WasPassed()) {
|
||||
JS_ClearPendingException(aCx);
|
||||
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
Uint8Array& array = value->mValue.Value();
|
||||
array.ComputeLengthAndData();
|
||||
uint32_t len = array.Length();
|
||||
|
||||
if (len == 0) {
|
||||
// If there is nothing to read, let's do another reading.
|
||||
OnOutputStreamReady(mPipeOut);
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mBuffer);
|
||||
mBuffer = Move(value);
|
||||
|
||||
mBufferOffset = 0;
|
||||
mBufferRemaining = len;
|
||||
|
||||
nsresult rv = WriteBuffer();
|
||||
if (NS_FAILED(rv)) {
|
||||
// DOMException only understands errors from domerr.msg, so we normalize to
|
||||
// identifying an abort if the write fails.
|
||||
CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
FetchStreamReader::WriteBuffer()
|
||||
{
|
||||
MOZ_ASSERT(mBuffer);
|
||||
MOZ_ASSERT(mBuffer->mValue.WasPassed());
|
||||
|
||||
Uint8Array& array = mBuffer->mValue.Value();
|
||||
char* data = reinterpret_cast<char*>(array.Data());
|
||||
|
||||
while (1) {
|
||||
uint32_t written = 0;
|
||||
nsresult rv =
|
||||
mPipeOut->Write(data + mBufferOffset, mBufferRemaining, &written);
|
||||
|
||||
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(written <= mBufferRemaining);
|
||||
mBufferRemaining -= written;
|
||||
mBufferOffset += written;
|
||||
|
||||
if (mBufferRemaining == 0) {
|
||||
mBuffer = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult rv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
FetchStreamReader::RejectedCallback(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
ReportErrorToConsole(aCx, aValue);
|
||||
CloseAndRelease(aCx, NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
void
|
||||
FetchStreamReader::ReportErrorToConsole(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
nsCString sourceSpec;
|
||||
uint32_t line = 0;
|
||||
uint32_t column = 0;
|
||||
nsString valueString;
|
||||
|
||||
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line,
|
||||
&column, valueString);
|
||||
|
||||
nsTArray<nsString> params;
|
||||
params.AppendElement(valueString);
|
||||
|
||||
RefPtr<ConsoleReportCollector> reporter = new ConsoleReportCollector();
|
||||
reporter->AddConsoleReport(nsIScriptError::errorFlag,
|
||||
NS_LITERAL_CSTRING("ReadableStreamReader.read"),
|
||||
nsContentUtils::eDOM_PROPERTIES,
|
||||
sourceSpec, line, column,
|
||||
NS_LITERAL_CSTRING("ReadableStreamReadingFailed"),
|
||||
params);
|
||||
|
||||
uint64_t innerWindowId = 0;
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
|
||||
if (window) {
|
||||
innerWindowId = window->WindowID();
|
||||
}
|
||||
reporter->FlushReportsByWindowId(innerWindowId);
|
||||
return;
|
||||
}
|
||||
|
||||
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
|
||||
if (workerPrivate) {
|
||||
innerWindowId = workerPrivate->WindowID();
|
||||
}
|
||||
|
||||
RefPtr<Runnable> r = NS_NewRunnableFunction(
|
||||
[reporter, innerWindowId] () {
|
||||
reporter->FlushReportsByWindowId(innerWindowId);
|
||||
});
|
||||
|
||||
workerPrivate->DispatchToMainThread(r.forget());
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
85
dom/fetch/FetchStreamReader.h
Normal file
85
dom/fetch/FetchStreamReader.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_FetchStreamReader_h
|
||||
#define mozilla_dom_FetchStreamReader_h
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "mozilla/dom/FetchBinding.h"
|
||||
#include "mozilla/dom/PromiseNativeHandler.h"
|
||||
#include "nsIAsyncOutputStream.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
namespace workers {
|
||||
class WorkerHolder;
|
||||
}
|
||||
|
||||
class FetchStreamReader final : public nsIOutputStreamCallback
|
||||
, public PromiseNativeHandler
|
||||
{
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(FetchStreamReader, nsIOutputStreamCallback)
|
||||
NS_DECL_NSIOUTPUTSTREAMCALLBACK
|
||||
|
||||
// This creates a nsIInputStream able to retrieve data from the ReadableStream
|
||||
// object. The reading starts when StartConsuming() is called.
|
||||
static nsresult
|
||||
Create(JSContext* aCx, nsIGlobalObject* aGlobal,
|
||||
FetchStreamReader** aStreamReader,
|
||||
nsIInputStream** aInputStream);
|
||||
|
||||
void
|
||||
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
|
||||
|
||||
void
|
||||
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
|
||||
|
||||
// Idempotently close the output stream and null out all state. If aCx is
|
||||
// provided, the reader will also be canceled. aStatus must be a DOM error
|
||||
// as understood by DOMException because it will be provided as the
|
||||
// cancellation reason.
|
||||
void
|
||||
CloseAndRelease(JSContext* aCx, nsresult aStatus);
|
||||
|
||||
void
|
||||
StartConsuming(JSContext* aCx,
|
||||
JS::HandleObject aStream,
|
||||
JS::MutableHandle<JSObject*> aReader,
|
||||
ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
explicit FetchStreamReader(nsIGlobalObject* aGlobal);
|
||||
~FetchStreamReader();
|
||||
|
||||
nsresult
|
||||
WriteBuffer();
|
||||
|
||||
void
|
||||
ReportErrorToConsole(JSContext* aCx, JS::Handle<JS::Value> aValue);
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
|
||||
|
||||
nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
|
||||
|
||||
UniquePtr<workers::WorkerHolder> mWorkerHolder;
|
||||
|
||||
JS::Heap<JSObject*> mReader;
|
||||
|
||||
UniquePtr<FetchReadableStreamReadDataArray> mBuffer;
|
||||
uint32_t mBufferRemaining;
|
||||
uint32_t mBufferOffset;
|
||||
|
||||
bool mStreamClosed;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_dom_FetchStreamReader_h
|
||||
|
|
@ -139,18 +139,18 @@ InternalResponse::ToIPC(IPCInternalResponse* aIPCResponse,
|
|||
}
|
||||
|
||||
already_AddRefed<InternalResponse>
|
||||
InternalResponse::Clone()
|
||||
InternalResponse::Clone(CloneType aCloneType)
|
||||
{
|
||||
RefPtr<InternalResponse> clone = CreateIncompleteCopy();
|
||||
|
||||
clone->mHeaders = new InternalHeaders(*mHeaders);
|
||||
if (mWrappedResponse) {
|
||||
clone->mWrappedResponse = mWrappedResponse->Clone();
|
||||
clone->mWrappedResponse = mWrappedResponse->Clone(aCloneType);
|
||||
MOZ_ASSERT(!mBody);
|
||||
return clone.forget();
|
||||
}
|
||||
|
||||
if (!mBody) {
|
||||
if (!mBody || aCloneType == eDontCloneInputStream) {
|
||||
return clone.forget();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,13 @@ public:
|
|||
M* aManager,
|
||||
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
|
||||
|
||||
already_AddRefed<InternalResponse> Clone();
|
||||
enum CloneType
|
||||
{
|
||||
eCloneInputStream,
|
||||
eDontCloneInputStream,
|
||||
};
|
||||
|
||||
already_AddRefed<InternalResponse> Clone(CloneType eCloneType);
|
||||
|
||||
static already_AddRefed<InternalResponse>
|
||||
NetworkError()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,25 @@ using namespace workers;
|
|||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(Request)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(Request)
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner, mHeaders)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(Request)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Request)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Request)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Request)
|
||||
MOZ_DIAGNOSTIC_ASSERT(!tmp->mReadableStreamReader);
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
|
|
@ -37,8 +55,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
|
|||
NS_INTERFACE_MAP_END
|
||||
|
||||
Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest, AbortSignal* aSignal)
|
||||
: FetchBody<Request>()
|
||||
, mOwner(aOwner)
|
||||
: FetchBody<Request>(aOwner)
|
||||
, mRequest(aRequest)
|
||||
, mSignal(aSignal)
|
||||
{
|
||||
|
|
@ -557,17 +574,15 @@ Request::Constructor(const GlobalObject& aGlobal,
|
|||
}
|
||||
|
||||
if (aInit.mBody.WasPassed()) {
|
||||
const Nullable<OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& bodyInitNullable =
|
||||
aInit.mBody.Value();
|
||||
const Nullable<fetch::OwningBodyInit>& bodyInitNullable = aInit.mBody.Value();
|
||||
if (!bodyInitNullable.IsNull()) {
|
||||
const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit =
|
||||
bodyInitNullable.Value();
|
||||
const fetch::OwningBodyInit& bodyInit = bodyInitNullable.Value();
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
nsAutoCString contentType;
|
||||
nsAutoCString contentTypeWithCharset;
|
||||
uint64_t contentLengthUnused;
|
||||
aRv = ExtractByteStreamFromBody(bodyInit,
|
||||
getter_AddRefs(stream),
|
||||
contentType,
|
||||
contentTypeWithCharset,
|
||||
contentLengthUnused);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
|
|
@ -575,10 +590,10 @@ Request::Constructor(const GlobalObject& aGlobal,
|
|||
|
||||
temporaryBody = stream;
|
||||
|
||||
if (!contentType.IsVoid() &&
|
||||
if (!contentTypeWithCharset.IsVoid() &&
|
||||
!requestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
|
||||
requestHeaders->Append(NS_LITERAL_CSTRING("Content-Type"),
|
||||
contentType, aRv);
|
||||
contentTypeWithCharset, aRv);
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
|
|
@ -599,7 +614,10 @@ Request::Constructor(const GlobalObject& aGlobal,
|
|||
inputReq->GetBody(getter_AddRefs(body));
|
||||
if (body) {
|
||||
inputReq->SetBody(nullptr);
|
||||
inputReq->SetBodyUsed();
|
||||
inputReq->SetBodyUsed(aGlobal.Context(), aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return domRequest.forget();
|
||||
|
|
|
|||
|
|
@ -128,6 +128,8 @@ public:
|
|||
void
|
||||
GetBody(nsIInputStream** aStream) { return mRequest->GetBody(aStream); }
|
||||
|
||||
using FetchBody::GetBody;
|
||||
|
||||
void
|
||||
SetBody(nsIInputStream* aStream) { return mRequest->SetBody(aStream); }
|
||||
|
||||
|
|
@ -162,7 +164,6 @@ public:
|
|||
private:
|
||||
~Request();
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mOwner;
|
||||
RefPtr<InternalRequest> mRequest;
|
||||
|
||||
// Lazily created.
|
||||
|
|
|
|||
|
|
@ -12,12 +12,16 @@
|
|||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/FetchBinding.h"
|
||||
#include "mozilla/dom/ResponseBinding.h"
|
||||
#include "mozilla/dom/Headers.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/URL.h"
|
||||
|
||||
#include "nsDOMString.h"
|
||||
|
||||
#include "BodyExtractor.h"
|
||||
#include "FetchStream.h"
|
||||
#include "FetchStreamReader.h"
|
||||
#include "InternalResponse.h"
|
||||
#include "WorkerPrivate.h"
|
||||
|
||||
|
|
@ -26,7 +30,31 @@ namespace dom {
|
|||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(Response)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(Response)
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Response, mOwner, mHeaders)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(Response)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Response)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
|
||||
|
||||
tmp->mReadableStreamBody = nullptr;
|
||||
tmp->mReadableStreamReader = nullptr;
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Response)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Response)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamBody)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
|
|
@ -34,18 +62,21 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
|
|||
NS_INTERFACE_MAP_END
|
||||
|
||||
Response::Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse, AbortSignal* aSignal)
|
||||
: FetchBody<Response>()
|
||||
, mOwner(aGlobal)
|
||||
: FetchBody<Response>(aGlobal)
|
||||
, mInternalResponse(aInternalResponse)
|
||||
, mSignal(aSignal)
|
||||
{
|
||||
MOZ_ASSERT(aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
|
||||
aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response);
|
||||
SetMimeType();
|
||||
|
||||
// Keep a firm grip!
|
||||
mozilla::HoldJSObjects(this);
|
||||
}
|
||||
|
||||
Response::~Response()
|
||||
{
|
||||
mozilla::DropJSObjects(this);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<Response>
|
||||
|
|
@ -106,7 +137,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>> body;
|
||||
Optional<fetch::ResponseBodyInit> body;
|
||||
ResponseInit init;
|
||||
init.mStatus = aStatus;
|
||||
RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv);
|
||||
|
|
@ -127,7 +158,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
|
|||
|
||||
/*static*/ already_AddRefed<Response>
|
||||
Response::Constructor(const GlobalObject& aGlobal,
|
||||
const Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>>& aBody,
|
||||
const Optional<fetch::ResponseBodyInit>& aBody,
|
||||
const ResponseInit& aInit, ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
|
|
@ -193,29 +224,86 @@ Response::Constructor(const GlobalObject& aGlobal,
|
|||
}
|
||||
}
|
||||
|
||||
if (aBody.WasPassed() && !aBody.Value().IsNull()) {
|
||||
if (aBody.WasPassed()) {
|
||||
if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) {
|
||||
aRv.ThrowTypeError<MSG_RESPONSE_NULL_STATUS_WITH_BODY>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCString contentTypeWithCharset;
|
||||
nsCOMPtr<nsIInputStream> bodyStream;
|
||||
nsCString contentType;
|
||||
uint64_t bodySize = 0;
|
||||
aRv = ExtractByteStreamFromBody(aBody.Value().Value(),
|
||||
getter_AddRefs(bodyStream),
|
||||
contentType,
|
||||
bodySize);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE;
|
||||
|
||||
if (aBody.Value().IsReadableStream()) {
|
||||
const ReadableStream& readableStream =
|
||||
aBody.Value().GetAsReadableStream();
|
||||
|
||||
JS::Rooted<JSObject*> readableStreamObj(aGlobal.Context(),
|
||||
readableStream.Obj());
|
||||
|
||||
if (JS::ReadableStreamIsDisturbed(readableStreamObj) ||
|
||||
JS::ReadableStreamIsLocked(readableStreamObj) ||
|
||||
!JS::ReadableStreamIsReadable(readableStreamObj)) {
|
||||
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
r->SetReadableStreamBody(readableStreamObj);
|
||||
|
||||
if (JS::ReadableStreamGetMode(readableStreamObj) ==
|
||||
JS::ReadableStreamMode::ExternalSource) {
|
||||
// If this is a DOM generated ReadableStream, we can extract the
|
||||
// inputStream directly.
|
||||
void* underlyingSource = nullptr;
|
||||
if (!JS::ReadableStreamGetExternalUnderlyingSource(aGlobal.Context(),
|
||||
readableStreamObj,
|
||||
&underlyingSource)) {
|
||||
aRv.StealExceptionFromJSContext(aGlobal.Context());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(underlyingSource);
|
||||
|
||||
aRv = FetchStream::RetrieveInputStream(underlyingSource,
|
||||
getter_AddRefs(bodyStream));
|
||||
|
||||
// The releasing of the external source is needed in order to avoid an
|
||||
// extra stream lock.
|
||||
JS::ReadableStreamReleaseExternalUnderlyingSource(readableStreamObj);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// If this is a JS-created ReadableStream, let's create a
|
||||
// FetchStreamReader.
|
||||
aRv = FetchStreamReader::Create(aGlobal.Context(), global,
|
||||
getter_AddRefs(r->mFetchStreamReader),
|
||||
getter_AddRefs(bodyStream));
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint64_t size = 0;
|
||||
aRv = ExtractByteStreamFromBody(aBody.Value(),
|
||||
getter_AddRefs(bodyStream),
|
||||
contentTypeWithCharset,
|
||||
size);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bodySize = size;
|
||||
}
|
||||
|
||||
internalResponse->SetBody(bodyStream, bodySize);
|
||||
|
||||
if (!contentType.IsVoid() &&
|
||||
if (!contentTypeWithCharset.IsVoid() &&
|
||||
!internalResponse->Headers()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
|
||||
// Ignore Append() failing here.
|
||||
ErrorResult error;
|
||||
internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, error);
|
||||
internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"),
|
||||
contentTypeWithCharset, error);
|
||||
error.SuppressException();
|
||||
}
|
||||
|
||||
|
|
@ -229,29 +317,87 @@ Response::Constructor(const GlobalObject& aGlobal,
|
|||
}
|
||||
|
||||
already_AddRefed<Response>
|
||||
Response::Clone(ErrorResult& aRv) const
|
||||
Response::Clone(JSContext* aCx, ErrorResult& aRv)
|
||||
{
|
||||
if (BodyUsed()) {
|
||||
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<InternalResponse> ir = mInternalResponse->Clone();
|
||||
RefPtr<Response> response = new Response(mOwner, ir, mSignal);
|
||||
RefPtr<FetchStreamReader> streamReader;
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
|
||||
JS::Rooted<JSObject*> body(aCx);
|
||||
MaybeTeeReadableStreamBody(aCx, &body,
|
||||
getter_AddRefs(streamReader),
|
||||
getter_AddRefs(inputStream), aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_IF(body, streamReader);
|
||||
MOZ_ASSERT_IF(body, inputStream);
|
||||
|
||||
RefPtr<InternalResponse> ir =
|
||||
mInternalResponse->Clone(body
|
||||
? InternalResponse::eDontCloneInputStream
|
||||
: InternalResponse::eCloneInputStream);
|
||||
|
||||
RefPtr<Response> response = new Response(mOwner, ir, nullptr);
|
||||
|
||||
if (body) {
|
||||
// Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
|
||||
// if this body is a native stream. In this case, the InternalResponse will
|
||||
// have a clone of the native body and the ReadableStream will be created
|
||||
// lazily if needed.
|
||||
response->SetReadableStreamBody(body);
|
||||
response->mFetchStreamReader = streamReader;
|
||||
ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
|
||||
}
|
||||
|
||||
return response.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Response>
|
||||
Response::CloneUnfiltered(ErrorResult& aRv) const
|
||||
Response::CloneUnfiltered(JSContext* aCx, ErrorResult& aRv)
|
||||
{
|
||||
if (BodyUsed()) {
|
||||
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<InternalResponse> clone = mInternalResponse->Clone();
|
||||
RefPtr<FetchStreamReader> streamReader;
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
|
||||
JS::Rooted<JSObject*> body(aCx);
|
||||
MaybeTeeReadableStreamBody(aCx, &body,
|
||||
getter_AddRefs(streamReader),
|
||||
getter_AddRefs(inputStream), aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_IF(body, streamReader);
|
||||
MOZ_ASSERT_IF(body, inputStream);
|
||||
|
||||
RefPtr<InternalResponse> clone =
|
||||
mInternalResponse->Clone(body
|
||||
? InternalResponse::eDontCloneInputStream
|
||||
: InternalResponse::eCloneInputStream);
|
||||
|
||||
RefPtr<InternalResponse> ir = clone->Unfiltered();
|
||||
RefPtr<Response> ref = new Response(mOwner, ir, mSignal);
|
||||
RefPtr<Response> ref = new Response(mOwner, ir, nullptr);
|
||||
|
||||
if (body) {
|
||||
// Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
|
||||
// if this body is a native stream. In this case, the InternalResponse will
|
||||
// have a clone of the native body and the ReadableStream will be created
|
||||
// lazily if needed.
|
||||
ref->SetReadableStreamBody(body);
|
||||
ref->mFetchStreamReader = streamReader;
|
||||
ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
|
||||
}
|
||||
|
||||
return ref.forget();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ public:
|
|||
void
|
||||
GetBody(nsIInputStream** aStream) { return mInternalResponse->GetBody(aStream); }
|
||||
|
||||
using FetchBody::GetBody;
|
||||
|
||||
static already_AddRefed<Response>
|
||||
Error(const GlobalObject& aGlobal);
|
||||
|
||||
|
|
@ -113,7 +115,7 @@ public:
|
|||
|
||||
static already_AddRefed<Response>
|
||||
Constructor(const GlobalObject& aGlobal,
|
||||
const Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>>& aBody,
|
||||
const Optional<fetch::ResponseBodyInit>& aBody,
|
||||
const ResponseInit& aInit, ErrorResult& rv);
|
||||
|
||||
nsIGlobalObject* GetParentObject() const
|
||||
|
|
@ -122,10 +124,10 @@ public:
|
|||
}
|
||||
|
||||
already_AddRefed<Response>
|
||||
Clone(ErrorResult& aRv) const;
|
||||
Clone(JSContext* aCx, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Response>
|
||||
CloneUnfiltered(ErrorResult& aRv) const;
|
||||
CloneUnfiltered(JSContext* aCx, ErrorResult& aRv);
|
||||
|
||||
void
|
||||
SetBody(nsIInputStream* aBody, int64_t aBodySize);
|
||||
|
|
@ -142,7 +144,6 @@ public:
|
|||
private:
|
||||
~Response();
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mOwner;
|
||||
RefPtr<InternalResponse> mInternalResponse;
|
||||
|
||||
// Lazily created
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'BodyExtractor.h',
|
||||
'ChannelInfo.h',
|
||||
'Fetch.h',
|
||||
'FetchDriver.h',
|
||||
'FetchIPCTypes.h',
|
||||
'FetchObserver.h',
|
||||
'FetchStreamReader.h',
|
||||
'FetchUtil.h',
|
||||
'Headers.h',
|
||||
'InternalHeaders.h',
|
||||
|
|
@ -19,11 +21,14 @@ EXPORTS.mozilla.dom += [
|
|||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'BodyExtractor.cpp',
|
||||
'ChannelInfo.cpp',
|
||||
'Fetch.cpp',
|
||||
'FetchConsumer.cpp',
|
||||
'FetchDriver.cpp',
|
||||
'FetchObserver.cpp',
|
||||
'FetchStream.cpp',
|
||||
'FetchStreamReader.cpp',
|
||||
'FetchUtil.cpp',
|
||||
'Headers.cpp',
|
||||
'InternalHeaders.cpp',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue