This commit is contained in:
Brian Smith 2023-09-28 14:05:55 -05:00 • committed by roytam1
commit a8a75090c0
14 changed files with 413 additions and 72 deletions

View file

@ -860,7 +860,7 @@ BeaconStreamListener::OnDataAvailable(nsIRequest *aRequest,
bool bool
Navigator::SendBeacon(const nsAString& aUrl, Navigator::SendBeacon(const nsAString& aUrl,
const Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& aData, const Nullable<fetch::BodyInit>& aData,
ErrorResult& aRv) ErrorResult& aRv)
{ {
if (aData.IsNull()) { if (aData.IsNull()) {

View file

@ -7,6 +7,7 @@
#define mozilla_dom_Navigator_h #define mozilla_dom_Navigator_h
#include "mozilla/MemoryReporting.h" #include "mozilla/MemoryReporting.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/Nullable.h" #include "mozilla/dom/Nullable.h"
#include "mozilla/ErrorResult.h" #include "mozilla/ErrorResult.h"
#include "nsIDOMNavigator.h" #include "nsIDOMNavigator.h"
@ -197,7 +198,7 @@ public:
#endif // MOZ_AUDIO_CHANNEL_MANAGER #endif // MOZ_AUDIO_CHANNEL_MANAGER
bool SendBeacon(const nsAString& aUrl, bool SendBeacon(const nsAString& aUrl,
const Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& aData, const Nullable<fetch::BodyInit>& aData,
ErrorResult& aRv); ErrorResult& aRv);
void MozGetUserMedia(const MediaStreamConstraints& aConstraints, void MozGetUserMedia(const MediaStreamConstraints& aConstraints,

View file

@ -747,7 +747,8 @@ DOMInterfaces = {
'Response': { 'Response': {
'binaryNames': { 'headers': 'headers_' }, 'binaryNames': { 'headers': 'headers_' },
'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text' ], 'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text',
'clone', 'cloneUnfiltered' ],
}, },
'RGBColor': { 'RGBColor': {

View file

@ -14,6 +14,7 @@
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "nsIDOMDocument.h" #include "nsIDOMDocument.h"
#include "nsIDOMSerializer.h" #include "nsIDOMSerializer.h"
#include "nsIGlobalObject.h"
#include "nsIInputStream.h" #include "nsIInputStream.h"
#include "nsIOutputStream.h" #include "nsIOutputStream.h"
#include "nsIStorageStream.h" #include "nsIStorageStream.h"

View file

@ -7,9 +7,11 @@
#ifndef mozilla_dom_BodyExtractor_h #ifndef mozilla_dom_BodyExtractor_h
#define mozilla_dom_BodyExtractor_h #define mozilla_dom_BodyExtractor_h
#include "jsapi.h"
#include "nsString.h" #include "nsString.h"
class nsIInputStream; class nsIInputStream;
class nsIGlobalObject;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -25,7 +27,7 @@ public:
// The implementation versions of this template are: // The implementation versions of this template are:
// ArrayBuffer, ArrayBufferView, nsIXHRSendable (Blob, FormData, // ArrayBuffer, ArrayBufferView, nsIXHRSendable (Blob, FormData,
// URLSearchParams), nsAString, nsIDocument, nsIInputStream // URLSearchParams), nsAString, nsIDocument, nsIInputStream.
template<typename Type> template<typename Type>
class BodyExtractor final : public BodyExtractorBase class BodyExtractor final : public BodyExtractorBase
{ {

View file

@ -725,7 +725,7 @@ WorkerFetchResolver::FlushConsoleReport()
} }
nsresult nsresult
ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
nsIInputStream** aStream, nsIInputStream** aStream,
nsCString& aContentTypeWithCharset, nsCString& aContentTypeWithCharset,
uint64_t& aContentLength) uint64_t& aContentLength)
@ -773,7 +773,7 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDa
} }
nsresult nsresult
ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
nsIInputStream** aStream, nsIInputStream** aStream,
nsCString& aContentTypeWithCharset, nsCString& aContentTypeWithCharset,
uint64_t& aContentLength) uint64_t& aContentLength)
@ -819,6 +819,63 @@ ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUS
return NS_ERROR_FAILURE; 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> template <class Derived>
FetchBody<Derived>::FetchBody() FetchBody<Derived>::FetchBody()
: mWorkerPrivate(nullptr) : mWorkerPrivate(nullptr)
@ -862,7 +919,8 @@ FetchBody<Derived>::BodyUsed() const
JS::Rooted<JSObject*> body(cx, mReadableStreamBody); JS::Rooted<JSObject*> body(cx, mReadableStreamBody);
if (JS::ReadableStreamIsDisturbed(body) || if (JS::ReadableStreamIsDisturbed(body) ||
JS::ReadableStreamIsLocked(body)) { JS::ReadableStreamIsLocked(body) ||
!JS::ReadableStreamIsReadable(body)) {
return true; return true;
} }
} }
@ -895,14 +953,18 @@ FetchBody<Derived>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorRes
SetBodyUsed(); SetBodyUsed();
nsCOMPtr<nsIGlobalObject> global = DerivedClass()->GetParentObject(); // If we already created a ReadableStreamBody we have to lock it now because
// it may have been shared with other objects..
// If we already created a ReadableStreamBody we have to close it now.
if (mReadableStreamBody) { if (mReadableStreamBody) {
JS::Rooted<JSObject*> body(aCx, mReadableStreamBody); JS::Rooted<JSObject*> body(aCx, mReadableStreamBody);
JS::ReadableStreamClose(aCx, body); LockStream(aCx, body, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
} }
nsCOMPtr<nsIGlobalObject> global = DerivedClass()->GetParentObject();
RefPtr<Promise> promise = RefPtr<Promise> promise =
FetchBodyConsumer<Derived>::Create(global, this, signal, aType, aRv); FetchBodyConsumer<Derived>::Create(global, this, signal, aType, aRv);
if (NS_WARN_IF(aRv.Failed())) { if (NS_WARN_IF(aRv.Failed())) {
@ -948,6 +1010,23 @@ template
void void
FetchBody<Response>::SetMimeType(); 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> template <class Derived>
void void
FetchBody<Derived>::GetBody(JSContext* aCx, FetchBody<Derived>::GetBody(JSContext* aCx,
@ -965,6 +1044,7 @@ FetchBody<Derived>::GetBody(JSContext* aCx,
if (!mReadableStreamBody) { if (!mReadableStreamBody) {
JS::Rooted<JSObject*> body(aCx, JS::Rooted<JSObject*> body(aCx,
FetchStream::Create(aCx, FetchStream::Create(aCx,
this,
DerivedClass()->GetParentObject(), DerivedClass()->GetParentObject(),
inputStream, inputStream,
aRv)); aRv));
@ -975,9 +1055,11 @@ FetchBody<Derived>::GetBody(JSContext* aCx,
MOZ_ASSERT(body); MOZ_ASSERT(body);
// If the body has been already consumed, we close the stream. // If the body has been already consumed, we close the stream.
if (BodyUsed() && !JS::ReadableStreamClose(aCx, body)) { if (BodyUsed()) {
aRv.StealExceptionFromJSContext(aCx); LockStream(aCx, body, aRv);
return; if (NS_WARN_IF(aRv.Failed())) {
return;
}
} }
mReadableStreamBody = body; mReadableStreamBody = body;
@ -998,6 +1080,72 @@ FetchBody<Response>::GetBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage, JS::MutableHandle<JSObject*> aMessage,
ErrorResult& aRv); ErrorResult& aRv);
template <class Derived>
void
FetchBody<Derived>::LockStream(JSContext* aCx,
JS::HandleObject aStream,
ErrorResult& aRv)
{
// XXXMC: TODO
}
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,
ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(!BodyUsed());
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);
}
template
void
FetchBody<Request>::MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
ErrorResult& aRv);
template
void
FetchBody<Response>::MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
ErrorResult& aRv);
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

View file

@ -24,10 +24,12 @@ class nsIGlobalObject;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams; class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString;
class BlobImpl; class BlobImpl;
class InternalRequest; class InternalRequest;
class OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams; class OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
struct ReadableStream;
class RequestOrUSVString; class RequestOrUSVString;
namespace workers { namespace workers {
@ -41,13 +43,20 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
nsresult nsresult
UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest); 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 * Creates an nsIInputStream based on the fetch specifications 'extract a byte
* stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract. * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract.
* Stores content type in out param aContentType. * Stores content type in out param aContentType.
*/ */
nsresult nsresult
ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
nsIInputStream** aStream, nsIInputStream** aStream,
nsCString& aContentType, nsCString& aContentType,
uint64_t& aContentLength); uint64_t& aContentLength);
@ -56,7 +65,17 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDa
* Non-owning version. * Non-owning version.
*/ */
nsresult 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, nsIInputStream** aStream,
nsCString& aContentType, nsCString& aContentType,
uint64_t& aContentLength); uint64_t& aContentLength);
@ -72,6 +91,15 @@ enum FetchConsumeType
CONSUME_TEXT, CONSUME_TEXT,
}; };
class FetchStreamHolder
{
public:
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
virtual void NullifyStream() = 0;
};
/* /*
* FetchBody's body consumption uses nsIInputStreamPump to read from the * FetchBody's body consumption uses nsIInputStreamPump to read from the
* underlying stream to a block of memory, which is then adopted by * underlying stream to a block of memory, which is then adopted by
@ -106,14 +134,11 @@ enum FetchConsumeType
* The pump is always released on the main thread. * The pump is always released on the main thread.
*/ */
template <class Derived> template <class Derived>
class FetchBody class FetchBody : public FetchStreamHolder
{ {
public: public:
friend class FetchBodyConsumer<Derived>; friend class FetchBodyConsumer<Derived>;
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
bool bool
BodyUsed() const; BodyUsed() const;
@ -152,6 +177,13 @@ public:
JS::MutableHandle<JSObject*> aBodyOut, JS::MutableHandle<JSObject*> aBodyOut,
ErrorResult& aRv); 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,
ErrorResult& aRv);
// Utility public methods accessed by various runnables. // Utility public methods accessed by various runnables.
void void
@ -166,6 +198,12 @@ public:
return mMimeType; return mMimeType;
} }
void
NullifyStream() override
{
mReadableStreamBody = nullptr;
}
virtual AbortSignal* virtual AbortSignal*
GetSignal() const = 0; GetSignal() const = 0;
@ -182,6 +220,10 @@ protected:
void void
SetMimeType(); SetMimeType();
void
SetReadableStreamBody(JSObject* aBody);
private: private:
Derived* Derived*
DerivedClass() const DerivedClass() const
@ -192,6 +234,9 @@ private:
already_AddRefed<Promise> already_AddRefed<Promise>
ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv); ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
void
LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv);
bool bool
IsOnTargetThread() IsOnTargetThread()
{ {

View file

@ -60,10 +60,12 @@ class FetchStreamWorkerHolderShutdown final : public WorkerControlRunnable
public: public:
FetchStreamWorkerHolderShutdown(WorkerPrivate* aWorkerPrivate, FetchStreamWorkerHolderShutdown(WorkerPrivate* aWorkerPrivate,
UniquePtr<WorkerHolder>&& aHolder, UniquePtr<WorkerHolder>&& aHolder,
nsCOMPtr<nsIGlobalObject>&& aGlobal) nsCOMPtr<nsIGlobalObject>&& aGlobal,
: WorkerControlRunnable(aWorkerPrivate) RefPtr<FetchStreamHolder>&& aStreamHolder)
: WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
, mHolder(Move(aHolder)) , mHolder(Move(aHolder))
, mGlobal(Move(aGlobal)) , mGlobal(Move(aGlobal))
, mStreamHolder(Move(aStreamHolder))
{} {}
bool bool
@ -71,12 +73,30 @@ public:
{ {
mHolder = nullptr; mHolder = nullptr;
mGlobal = nullptr; mGlobal = nullptr;
mStreamHolder->NullifyStream();
mStreamHolder = nullptr;
return true; 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: private:
UniquePtr<WorkerHolder> mHolder; UniquePtr<WorkerHolder> mHolder;
nsCOMPtr<nsIGlobalObject> mGlobal; nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<FetchStreamHolder> mStreamHolder;
}; };
} // anonymous } // anonymous
@ -85,13 +105,15 @@ NS_IMPL_ISUPPORTS(FetchStream, nsIInputStreamCallback, nsIObserver,
nsISupportsWeakReference) nsISupportsWeakReference)
/* static */ JSObject* /* static */ JSObject*
FetchStream::Create(JSContext* aCx, nsIGlobalObject* aGlobal, FetchStream::Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
nsIInputStream* aInputStream, ErrorResult& aRv) nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
ErrorResult& aRv)
{ {
MOZ_DIAGNOSTIC_ASSERT(aCx); MOZ_DIAGNOSTIC_ASSERT(aCx);
MOZ_DIAGNOSTIC_ASSERT(aInputStream); MOZ_DIAGNOSTIC_ASSERT(aInputStream);
MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
RefPtr<FetchStream> stream = new FetchStream(aGlobal, aInputStream); RefPtr<FetchStream> stream = new FetchStream(aGlobal, aStreamHolder, aInputStream);
if (NS_IsMainThread()) { if (NS_IsMainThread()) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
@ -226,7 +248,7 @@ FetchStream::RequestDataCallback(JSContext* aCx,
MOZ_DIAGNOSTIC_ASSERT(!stream->mOriginalInputStream); MOZ_DIAGNOSTIC_ASSERT(!stream->mOriginalInputStream);
nsresult rv = nsresult rv =
stream->mInputStream->AsyncWait(stream, 0, 0, nullptr); stream->mInputStream->AsyncWait(stream, 0, 0, stream->mOwningEventTarget);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
stream->ErrorPropagation(aCx, aStream, rv); stream->ErrorPropagation(aCx, aStream, rv);
return; return;
@ -264,12 +286,11 @@ FetchStream::WriteIntoReadRequestCallback(JSContext* aCx,
*aByteWritten = written; *aByteWritten = written;
if (written == 0) { if (written == 0) {
stream->mState = eClosed; stream->CloseAndReleaseObjects(aCx, aStream);
JS::ReadableStreamClose(aCx, aStream);
return; return;
} }
rv = stream->mInputStream->AsyncWait(stream, 0, 0, nullptr); rv = stream->mInputStream->AsyncWait(stream, 0, 0, stream->mOwningEventTarget);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
stream->ErrorPropagation(aCx, aStream, rv); stream->ErrorPropagation(aCx, aStream, rv);
return; return;
@ -292,7 +313,7 @@ FetchStream::CancelCallback(JSContext* aCx, JS::HandleObject aStream,
stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED); stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
} }
stream->mState = eClosed; stream->ReleaseObjects();
return JS::UndefinedValue(); return JS::UndefinedValue();
} }
@ -325,22 +346,21 @@ FetchStream::FinalizeCallback(void* aUnderlyingSource, uint8_t aFlags)
RefPtr<FetchStream> stream = RefPtr<FetchStream> stream =
dont_AddRef(static_cast<FetchStream*>(aUnderlyingSource)); dont_AddRef(static_cast<FetchStream*>(aUnderlyingSource));
if (stream->mState == eClosed) { stream->ReleaseObjects();
return;
}
stream->CloseAndReleaseObjects();
} }
FetchStream::FetchStream(nsIGlobalObject* aGlobal, FetchStream::FetchStream(nsIGlobalObject* aGlobal,
FetchStreamHolder* aStreamHolder,
nsIInputStream* aInputStream) nsIInputStream* aInputStream)
: mState(eWaiting) : mState(eWaiting)
, mGlobal(aGlobal) , mGlobal(aGlobal)
, mStreamHolder(aStreamHolder)
, mOriginalInputStream(aInputStream) , mOriginalInputStream(aInputStream)
, mOwningEventTarget(nullptr) , mOwningEventTarget(nullptr)
, mReadableStream(nullptr) , mReadableStream(nullptr)
{ {
MOZ_DIAGNOSTIC_ASSERT(aInputStream); MOZ_DIAGNOSTIC_ASSERT(aInputStream);
MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
} }
FetchStream::~FetchStream() FetchStream::~FetchStream()
@ -356,12 +376,9 @@ FetchStream::ErrorPropagation(JSContext* aCx, JS::HandleObject aStream,
return; return;
} }
// We cannot continue with any other operation.
mState = eClosed;
// Let's close the stream. // Let's close the stream.
if (aError == NS_BASE_STREAM_CLOSED) { if (aError == NS_BASE_STREAM_CLOSED) {
JS::ReadableStreamClose(aCx, aStream); CloseAndReleaseObjects(aCx, aStream);
return; return;
} }
@ -428,6 +445,27 @@ FetchStream::OnInputStreamReady(nsIAsyncInputStream* aStream)
return NS_OK; return NS_OK;
} }
/* static */ nsresult
FetchStream::RetrieveInputStream(void* aUnderlyingReadableStreamSource,
nsIInputStream** aInputStream)
{
MOZ_ASSERT(aUnderlyingReadableStreamSource);
MOZ_ASSERT(aInputStream);
RefPtr<FetchStream> stream =
static_cast<FetchStream*>(aUnderlyingReadableStreamSource);
// 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 void
FetchStream::Close() FetchStream::Close()
{ {
@ -437,27 +475,42 @@ FetchStream::Close()
AutoJSAPI jsapi; AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mGlobal))) { if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
ReleaseObjects();
return; return;
} }
JSContext* cx = jsapi.cx(); JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> stream(cx, mReadableStream); JS::Rooted<JSObject*> stream(cx, mReadableStream);
JS::ReadableStreamClose(cx, stream);
CloseAndReleaseObjects(); CloseAndReleaseObjects(cx, stream);
} }
void void
FetchStream::CloseAndReleaseObjects() FetchStream::CloseAndReleaseObjects(JSContext* aCx, JS::HandleObject aStream)
{ {
MOZ_DIAGNOSTIC_ASSERT(mState != eClosed); MOZ_DIAGNOSTIC_ASSERT(mState != eClosed);
ReleaseObjects();
if (JS::ReadableStreamIsReadable(aStream)) {
JS::ReadableStreamClose(aCx, aStream);
}
}
void
FetchStream::ReleaseObjects()
{
if (mState == eClosed) {
return;
}
mState = eClosed; mState = eClosed;
if (mWorkerHolder) { if (mWorkerHolder) {
RefPtr<FetchStreamWorkerHolderShutdown> r = RefPtr<FetchStreamWorkerHolderShutdown> r =
new FetchStreamWorkerHolderShutdown( new FetchStreamWorkerHolderShutdown(
static_cast<FetchStreamWorkerHolder*>(mWorkerHolder.get())->GetWorkerPrivate(), static_cast<FetchStreamWorkerHolder*>(mWorkerHolder.get())->GetWorkerPrivate(),
Move(mWorkerHolder), Move(mGlobal)); Move(mWorkerHolder), Move(mGlobal), Move(mStreamHolder));
r->Dispatch(); r->Dispatch();
} else { } else {
RefPtr<FetchStream> self = this; RefPtr<FetchStream> self = this;
@ -468,6 +521,9 @@ FetchStream::CloseAndReleaseObjects()
os->RemoveObserver(self, DOM_WINDOW_DESTROYED_TOPIC); os->RemoveObserver(self, DOM_WINDOW_DESTROYED_TOPIC);
} }
self->mGlobal = nullptr; self->mGlobal = nullptr;
self->mStreamHolder->NullifyStream();
self->mStreamHolder = nullptr;
}); });
NS_DispatchToMainThread(r); NS_DispatchToMainThread(r);

View file

@ -24,6 +24,8 @@ namespace workers {
class WorkerHolder; class WorkerHolder;
} }
class FetchStreamHolder;
class FetchStream final : public nsIInputStreamCallback class FetchStream final : public nsIInputStreamCallback
, public nsIObserver , public nsIObserver
, public nsSupportsWeakReference , public nsSupportsWeakReference
@ -34,14 +36,20 @@ public:
NS_DECL_NSIOBSERVER NS_DECL_NSIOBSERVER
static JSObject* static JSObject*
Create(JSContext* aCx, nsIGlobalObject* aGlobal, Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
nsIInputStream* aInputStream, ErrorResult& aRv); nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
ErrorResult& aRv);
void void
Close(); Close();
static nsresult
RetrieveInputStream(void* aUnderlyingReadableStreamSource,
nsIInputStream** aInputStream);
private: private:
FetchStream(nsIGlobalObject* aGlobal, nsIInputStream* aInputStream); FetchStream(nsIGlobalObject* aGlobal,
FetchStreamHolder* aStreamHolder,
nsIInputStream* aInputStream);
~FetchStream(); ~FetchStream();
static void static void
@ -76,7 +84,10 @@ private:
ErrorPropagation(JSContext* aCx, JS::HandleObject aStream, nsresult aRv); ErrorPropagation(JSContext* aCx, JS::HandleObject aStream, nsresult aRv);
void void
CloseAndReleaseObjects(); CloseAndReleaseObjects(JSContext* aCx, JS::HandleObject aStream);
void
ReleaseObjects();
// Common methods // Common methods
@ -104,6 +115,8 @@ private:
State mState; State mState;
nsCOMPtr<nsIGlobalObject> mGlobal; nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<FetchStreamHolder> mStreamHolder;
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
// This is the original inputStream received during the CTOR. It will be // This is the original inputStream received during the CTOR. It will be
// converted into an nsIAsyncInputStream and stored into mInputStream at the // converted into an nsIAsyncInputStream and stored into mInputStream at the
@ -111,8 +124,6 @@ private:
nsCOMPtr<nsIInputStream> mOriginalInputStream; nsCOMPtr<nsIInputStream> mOriginalInputStream;
nsCOMPtr<nsIAsyncInputStream> mInputStream; nsCOMPtr<nsIAsyncInputStream> mInputStream;
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
UniquePtr<workers::WorkerHolder> mWorkerHolder; UniquePtr<workers::WorkerHolder> mWorkerHolder;
JS::Heap<JSObject*> mReadableStream; JS::Heap<JSObject*> mReadableStream;

View file

@ -574,11 +574,9 @@ Request::Constructor(const GlobalObject& aGlobal,
} }
if (aInit.mBody.WasPassed()) { if (aInit.mBody.WasPassed()) {
const Nullable<OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& bodyInitNullable = const Nullable<fetch::OwningBodyInit>& bodyInitNullable = aInit.mBody.Value();
aInit.mBody.Value();
if (!bodyInitNullable.IsNull()) { if (!bodyInitNullable.IsNull()) {
const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit = const fetch::OwningBodyInit& bodyInit = bodyInitNullable.Value();
bodyInitNullable.Value();
nsCOMPtr<nsIInputStream> stream; nsCOMPtr<nsIInputStream> stream;
nsAutoCString contentTypeWithCharset; nsAutoCString contentTypeWithCharset;
uint64_t contentLengthUnused; uint64_t contentLengthUnused;

View file

@ -12,12 +12,15 @@
#include "mozilla/ErrorResult.h" #include "mozilla/ErrorResult.h"
#include "mozilla/dom/FetchBinding.h" #include "mozilla/dom/FetchBinding.h"
#include "mozilla/dom/ResponseBinding.h"
#include "mozilla/dom/Headers.h" #include "mozilla/dom/Headers.h"
#include "mozilla/dom/Promise.h" #include "mozilla/dom/Promise.h"
#include "mozilla/dom/URL.h" #include "mozilla/dom/URL.h"
#include "nsDOMString.h" #include "nsDOMString.h"
#include "BodyExtractor.h"
#include "FetchStream.h"
#include "InternalResponse.h" #include "InternalResponse.h"
#include "WorkerPrivate.h" #include "WorkerPrivate.h"
@ -130,7 +133,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
return nullptr; return nullptr;
} }
Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>> body; Optional<fetch::ResponseBodyInit> body;
ResponseInit init; ResponseInit init;
init.mStatus = aStatus; init.mStatus = aStatus;
RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv); RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv);
@ -151,7 +154,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
/*static*/ already_AddRefed<Response> /*static*/ already_AddRefed<Response>
Response::Constructor(const GlobalObject& aGlobal, Response::Constructor(const GlobalObject& aGlobal,
const Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>>& aBody, const Optional<fetch::ResponseBodyInit>& aBody,
const ResponseInit& aInit, ErrorResult& aRv) const ResponseInit& aInit, ErrorResult& aRv)
{ {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
@ -217,22 +220,65 @@ Response::Constructor(const GlobalObject& aGlobal,
} }
} }
if (aBody.WasPassed() && !aBody.Value().IsNull()) { if (aBody.WasPassed()) {
if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) { if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) {
aRv.ThrowTypeError<MSG_RESPONSE_NULL_STATUS_WITH_BODY>(); aRv.ThrowTypeError<MSG_RESPONSE_NULL_STATUS_WITH_BODY>();
return nullptr; return nullptr;
} }
nsCOMPtr<nsIInputStream> bodyStream;
nsCString contentTypeWithCharset; nsCString contentTypeWithCharset;
nsCOMPtr<nsIInputStream> bodyStream;
uint64_t bodySize = 0; uint64_t bodySize = 0;
aRv = ExtractByteStreamFromBody(aBody.Value().Value(),
getter_AddRefs(bodyStream), if (aBody.Value().IsReadableStream()) {
contentTypeWithCharset, const ReadableStream& readableStream =
bodySize); aBody.Value().GetAsReadableStream();
if (NS_WARN_IF(aRv.Failed())) {
return nullptr; 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);
// XXXMC: TODO
MOZ_ASSERT(JS::ReadableStreamGetMode(readableStreamObj) !=
JS::ReadableStreamMode::ExternalSource);
void* underlyingSource = nullptr;
if (!JS::ReadableStreamGetExternalUnderlyingSource(aGlobal.Context(),
readableStreamObj,
&underlyingSource)) {
aRv.StealExceptionFromJSContext(aGlobal.Context());
return nullptr;
}
bodySize = InternalResponse::UNKNOWN_BODY_SIZE;
MOZ_ASSERT(underlyingSource);
aRv = FetchStream::RetrieveInputStream(underlyingSource,
getter_AddRefs(bodyStream));
// 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 {
aRv = ExtractByteStreamFromBody(aBody.Value(),
getter_AddRefs(bodyStream),
contentTypeWithCharset,
bodySize);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
} }
internalResponse->SetBody(bodyStream, bodySize); internalResponse->SetBody(bodyStream, bodySize);
if (!contentTypeWithCharset.IsVoid() && if (!contentTypeWithCharset.IsVoid() &&
@ -254,7 +300,7 @@ Response::Constructor(const GlobalObject& aGlobal,
} }
already_AddRefed<Response> already_AddRefed<Response>
Response::Clone(ErrorResult& aRv) const Response::Clone(JSContext* aCx, ErrorResult& aRv)
{ {
if (BodyUsed()) { if (BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
@ -263,11 +309,26 @@ Response::Clone(ErrorResult& aRv) const
RefPtr<InternalResponse> ir = mInternalResponse->Clone(); RefPtr<InternalResponse> ir = mInternalResponse->Clone();
RefPtr<Response> response = new Response(mOwner, ir, mSignal); RefPtr<Response> response = new Response(mOwner, ir, mSignal);
JS::Rooted<JSObject*> body(aCx);
MaybeTeeReadableStreamBody(aCx, &body, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return 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);
}
return response.forget(); return response.forget();
} }
already_AddRefed<Response> already_AddRefed<Response>
Response::CloneUnfiltered(ErrorResult& aRv) const Response::CloneUnfiltered(JSContext* aCx, ErrorResult& aRv)
{ {
if (BodyUsed()) { if (BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
@ -277,6 +338,21 @@ Response::CloneUnfiltered(ErrorResult& aRv) const
RefPtr<InternalResponse> clone = mInternalResponse->Clone(); RefPtr<InternalResponse> clone = mInternalResponse->Clone();
RefPtr<InternalResponse> ir = clone->Unfiltered(); RefPtr<InternalResponse> ir = clone->Unfiltered();
RefPtr<Response> ref = new Response(mOwner, ir, mSignal); RefPtr<Response> ref = new Response(mOwner, ir, mSignal);
JS::Rooted<JSObject*> body(aCx);
MaybeTeeReadableStreamBody(aCx, &body, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return 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);
}
return ref.forget(); return ref.forget();
} }

View file

@ -115,7 +115,7 @@ public:
static already_AddRefed<Response> static already_AddRefed<Response>
Constructor(const GlobalObject& aGlobal, Constructor(const GlobalObject& aGlobal,
const Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>>& aBody, const Optional<fetch::ResponseBodyInit>& aBody,
const ResponseInit& aInit, ErrorResult& rv); const ResponseInit& aInit, ErrorResult& rv);
nsIGlobalObject* GetParentObject() const nsIGlobalObject* GetParentObject() const
@ -124,10 +124,10 @@ public:
} }
already_AddRefed<Response> already_AddRefed<Response>
Clone(ErrorResult& aRv) const; Clone(JSContext* aCx, ErrorResult& aRv);
already_AddRefed<Response> already_AddRefed<Response>
CloneUnfiltered(ErrorResult& aRv) const; CloneUnfiltered(JSContext* aCx, ErrorResult& aRv);
void void
SetBody(nsIInputStream* aBody, int64_t aBodySize); SetBody(nsIInputStream* aBody, int64_t aBodySize);

View file

@ -8,7 +8,7 @@
*/ */
typedef object JSON; typedef object JSON;
typedef (ArrayBuffer or ArrayBufferView or Blob or FormData or USVString or URLSearchParams) BodyInit; typedef (Blob or BufferSource or FormData or URLSearchParams or USVString) BodyInit;
[NoInterfaceObject, Exposed=(Window,Worker)] [NoInterfaceObject, Exposed=(Window,Worker)]
interface Body { interface Body {

View file

@ -7,7 +7,9 @@
* https://fetch.spec.whatwg.org/#response-class * https://fetch.spec.whatwg.org/#response-class
*/ */
[Constructor(optional BodyInit? body, optional ResponseInit init), // This should be Constructor(optional BodyInit... but BodyInit doesn't include
// ReadableStream yet because we don't want to expose the Streams API to Request.
[Constructor(optional (Blob or BufferSource or FormData or URLSearchParams or ReadableStream or USVString) body, optional ResponseInit init),
Exposed=(Window,Worker)] Exposed=(Window,Worker)]
interface Response { interface Response {
[NewObject] static Response error(); [NewObject] static Response error();