diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index 6509d8231f..febe1f1a3a 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -860,7 +860,7 @@ BeaconStreamListener::OnDataAvailable(nsIRequest *aRequest, bool Navigator::SendBeacon(const nsAString& aUrl, - const Nullable& aData, + const Nullable& aData, ErrorResult& aRv) { if (aData.IsNull()) { diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index f121af383c..5738cd2b5a 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -7,6 +7,7 @@ #define mozilla_dom_Navigator_h #include "mozilla/MemoryReporting.h" +#include "mozilla/dom/Fetch.h" #include "mozilla/dom/Nullable.h" #include "mozilla/ErrorResult.h" #include "nsIDOMNavigator.h" @@ -197,7 +198,7 @@ public: #endif // MOZ_AUDIO_CHANNEL_MANAGER bool SendBeacon(const nsAString& aUrl, - const Nullable& aData, + const Nullable& aData, ErrorResult& aRv); void MozGetUserMedia(const MediaStreamConstraints& aConstraints, diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 39fa0d7dc9..0907a0faf7 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -747,7 +747,8 @@ DOMInterfaces = { 'Response': { 'binaryNames': { 'headers': 'headers_' }, - 'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text' ], + 'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text', + 'clone', 'cloneUnfiltered' ], }, 'RGBColor': { diff --git a/dom/fetch/BodyExtractor.cpp b/dom/fetch/BodyExtractor.cpp index 89c9c20ddf..b840641214 100644 --- a/dom/fetch/BodyExtractor.cpp +++ b/dom/fetch/BodyExtractor.cpp @@ -14,6 +14,7 @@ #include "nsContentUtils.h" #include "nsIDOMDocument.h" #include "nsIDOMSerializer.h" +#include "nsIGlobalObject.h" #include "nsIInputStream.h" #include "nsIOutputStream.h" #include "nsIStorageStream.h" diff --git a/dom/fetch/BodyExtractor.h b/dom/fetch/BodyExtractor.h index d6b79b0349..73dfef6aff 100644 --- a/dom/fetch/BodyExtractor.h +++ b/dom/fetch/BodyExtractor.h @@ -7,9 +7,11 @@ #ifndef mozilla_dom_BodyExtractor_h #define mozilla_dom_BodyExtractor_h +#include "jsapi.h" #include "nsString.h" class nsIInputStream; +class nsIGlobalObject; namespace mozilla { namespace dom { @@ -25,7 +27,7 @@ public: // The implementation versions of this template are: // ArrayBuffer, ArrayBufferView, nsIXHRSendable (Blob, FormData, -// URLSearchParams), nsAString, nsIDocument, nsIInputStream +// URLSearchParams), nsAString, nsIDocument, nsIInputStream. template class BodyExtractor final : public BodyExtractorBase { diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index a831843a96..141e7c3043 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -725,7 +725,7 @@ WorkerFetchResolver::FlushConsoleReport() } nsresult -ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, +ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit, nsIInputStream** aStream, nsCString& aContentTypeWithCharset, uint64_t& aContentLength) @@ -773,7 +773,7 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDa } nsresult -ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, +ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit, nsIInputStream** aStream, nsCString& aContentTypeWithCharset, uint64_t& aContentLength) @@ -819,6 +819,63 @@ ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUS 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 body(&aBodyInit.GetAsArrayBuffer()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); + } + + if (aBodyInit.IsArrayBufferView()) { + BodyExtractor body(&aBodyInit.GetAsArrayBufferView()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); + } + + if (aBodyInit.IsBlob()) { + BodyExtractor body(&aBodyInit.GetAsBlob()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); + } + + if (aBodyInit.IsFormData()) { + BodyExtractor body(&aBodyInit.GetAsFormData()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); + } + + if (aBodyInit.IsUSVString()) { + BodyExtractor body(&aBodyInit.GetAsUSVString()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); + } + + if (aBodyInit.IsURLSearchParams()) { + BodyExtractor body(&aBodyInit.GetAsURLSearchParams()); + return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, + charset); + } + + NS_NOTREACHED("Should never reach here"); + return NS_ERROR_FAILURE; +} + + template FetchBody::FetchBody() : mWorkerPrivate(nullptr) @@ -862,7 +919,8 @@ FetchBody::BodyUsed() const JS::Rooted body(cx, mReadableStreamBody); if (JS::ReadableStreamIsDisturbed(body) || - JS::ReadableStreamIsLocked(body)) { + JS::ReadableStreamIsLocked(body) || + !JS::ReadableStreamIsReadable(body)) { return true; } } @@ -895,14 +953,18 @@ FetchBody::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorRes SetBodyUsed(); - nsCOMPtr global = DerivedClass()->GetParentObject(); - - // If we already created a ReadableStreamBody we have to close it now. + // If we already created a ReadableStreamBody we have to lock it now because + // it may have been shared with other objects.. if (mReadableStreamBody) { JS::Rooted body(aCx, mReadableStreamBody); - JS::ReadableStreamClose(aCx, body); + LockStream(aCx, body, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } } + nsCOMPtr global = DerivedClass()->GetParentObject(); + RefPtr promise = FetchBodyConsumer::Create(global, this, signal, aType, aRv); if (NS_WARN_IF(aRv.Failed())) { @@ -948,6 +1010,23 @@ template void FetchBody::SetMimeType(); +template +void +FetchBody::SetReadableStreamBody(JSObject* aBody) +{ + MOZ_ASSERT(!mReadableStreamBody); + MOZ_ASSERT(aBody); + mReadableStreamBody = aBody; +} + +template +void +FetchBody::SetReadableStreamBody(JSObject* aBody); + +template +void +FetchBody::SetReadableStreamBody(JSObject* aBody); + template void FetchBody::GetBody(JSContext* aCx, @@ -965,6 +1044,7 @@ FetchBody::GetBody(JSContext* aCx, if (!mReadableStreamBody) { JS::Rooted body(aCx, FetchStream::Create(aCx, + this, DerivedClass()->GetParentObject(), inputStream, aRv)); @@ -975,9 +1055,11 @@ FetchBody::GetBody(JSContext* aCx, MOZ_ASSERT(body); // If the body has been already consumed, we close the stream. - if (BodyUsed() && !JS::ReadableStreamClose(aCx, body)) { - aRv.StealExceptionFromJSContext(aCx); - return; + if (BodyUsed()) { + LockStream(aCx, body, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } } mReadableStreamBody = body; @@ -998,6 +1080,72 @@ FetchBody::GetBody(JSContext* aCx, JS::MutableHandle aMessage, ErrorResult& aRv); +template +void +FetchBody::LockStream(JSContext* aCx, + JS::HandleObject aStream, + ErrorResult& aRv) +{ + // XXXMC: TODO +} + +template +void +FetchBody::LockStream(JSContext* aCx, + JS::HandleObject aStream, + ErrorResult& aRv); + +template +void +FetchBody::LockStream(JSContext* aCx, + JS::HandleObject aStream, + ErrorResult& aRv); + +template +void +FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, + JS::MutableHandle aBodyOut, + ErrorResult& aRv) +{ + MOZ_DIAGNOSTIC_ASSERT(!BodyUsed()); + + if (!mReadableStreamBody) { + return; + } + + JS::Rooted 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 branch1(aCx); + JS::Rooted branch2(aCx); + + if (!JS::ReadableStreamTee(aCx, stream, &branch1, &branch2)) { + aRv.StealExceptionFromJSContext(aCx); + return; + } + + mReadableStreamBody = branch1; + aBodyOut.set(branch2); +} + +template +void +FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, + JS::MutableHandle aMessage, + ErrorResult& aRv); + +template +void +FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, + JS::MutableHandle aMessage, + ErrorResult& aRv); } // namespace dom } // namespace mozilla diff --git a/dom/fetch/Fetch.h b/dom/fetch/Fetch.h index 821bc3643f..d3a7ac598e 100644 --- a/dom/fetch/Fetch.h +++ b/dom/fetch/Fetch.h @@ -24,10 +24,12 @@ class nsIGlobalObject; 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 +43,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 +65,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 +91,15 @@ enum FetchConsumeType 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 * 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. */ template -class FetchBody +class FetchBody : public FetchStreamHolder { public: friend class FetchBodyConsumer; - NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; - NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; - bool BodyUsed() const; @@ -152,6 +177,13 @@ public: JS::MutableHandle aBodyOut, ErrorResult& aRv); + // If the body contains a ReadableStream body object, this method produces a + // tee() of it. + void + MaybeTeeReadableStreamBody(JSContext* aCx, + JS::MutableHandle aBodyOut, + ErrorResult& aRv); + // Utility public methods accessed by various runnables. void @@ -166,6 +198,12 @@ public: return mMimeType; } + void + NullifyStream() override + { + mReadableStreamBody = nullptr; + } + virtual AbortSignal* GetSignal() const = 0; @@ -182,6 +220,10 @@ protected: void SetMimeType(); + + void + SetReadableStreamBody(JSObject* aBody); + private: Derived* DerivedClass() const @@ -192,6 +234,9 @@ private: already_AddRefed ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv); + void + LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv); + bool IsOnTargetThread() { diff --git a/dom/fetch/FetchStream.cpp b/dom/fetch/FetchStream.cpp index e57991fcc2..94837504f1 100644 --- a/dom/fetch/FetchStream.cpp +++ b/dom/fetch/FetchStream.cpp @@ -60,10 +60,12 @@ class FetchStreamWorkerHolderShutdown final : public WorkerControlRunnable public: FetchStreamWorkerHolderShutdown(WorkerPrivate* aWorkerPrivate, UniquePtr&& aHolder, - nsCOMPtr&& aGlobal) - : WorkerControlRunnable(aWorkerPrivate) + nsCOMPtr&& aGlobal, + RefPtr&& aStreamHolder) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) , mHolder(Move(aHolder)) , mGlobal(Move(aGlobal)) + , mStreamHolder(Move(aStreamHolder)) {} bool @@ -71,12 +73,30 @@ public: { 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 mHolder; nsCOMPtr mGlobal; + RefPtr mStreamHolder; }; } // anonymous @@ -85,13 +105,15 @@ NS_IMPL_ISUPPORTS(FetchStream, nsIInputStreamCallback, nsIObserver, nsISupportsWeakReference) /* static */ JSObject* -FetchStream::Create(JSContext* aCx, nsIGlobalObject* aGlobal, - nsIInputStream* aInputStream, ErrorResult& aRv) +FetchStream::Create(JSContext* aCx, FetchStreamHolder* aStreamHolder, + nsIGlobalObject* aGlobal, nsIInputStream* aInputStream, + ErrorResult& aRv) { MOZ_DIAGNOSTIC_ASSERT(aCx); MOZ_DIAGNOSTIC_ASSERT(aInputStream); + MOZ_DIAGNOSTIC_ASSERT(aStreamHolder); - RefPtr stream = new FetchStream(aGlobal, aInputStream); + RefPtr stream = new FetchStream(aGlobal, aStreamHolder, aInputStream); if (NS_IsMainThread()) { nsCOMPtr os = mozilla::services::GetObserverService(); @@ -226,7 +248,7 @@ FetchStream::RequestDataCallback(JSContext* aCx, MOZ_DIAGNOSTIC_ASSERT(!stream->mOriginalInputStream); nsresult rv = - stream->mInputStream->AsyncWait(stream, 0, 0, nullptr); + stream->mInputStream->AsyncWait(stream, 0, 0, stream->mOwningEventTarget); if (NS_WARN_IF(NS_FAILED(rv))) { stream->ErrorPropagation(aCx, aStream, rv); return; @@ -264,12 +286,11 @@ FetchStream::WriteIntoReadRequestCallback(JSContext* aCx, *aByteWritten = written; if (written == 0) { - stream->mState = eClosed; - JS::ReadableStreamClose(aCx, aStream); + stream->CloseAndReleaseObjects(aCx, aStream); 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))) { stream->ErrorPropagation(aCx, aStream, rv); return; @@ -292,7 +313,7 @@ FetchStream::CancelCallback(JSContext* aCx, JS::HandleObject aStream, stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED); } - stream->mState = eClosed; + stream->ReleaseObjects(); return JS::UndefinedValue(); } @@ -325,22 +346,21 @@ FetchStream::FinalizeCallback(void* aUnderlyingSource, uint8_t aFlags) RefPtr stream = dont_AddRef(static_cast(aUnderlyingSource)); - if (stream->mState == eClosed) { - return; - } - - stream->CloseAndReleaseObjects(); + stream->ReleaseObjects(); } FetchStream::FetchStream(nsIGlobalObject* aGlobal, + FetchStreamHolder* aStreamHolder, nsIInputStream* aInputStream) : mState(eWaiting) , mGlobal(aGlobal) + , mStreamHolder(aStreamHolder) , mOriginalInputStream(aInputStream) , mOwningEventTarget(nullptr) , mReadableStream(nullptr) { MOZ_DIAGNOSTIC_ASSERT(aInputStream); + MOZ_DIAGNOSTIC_ASSERT(aStreamHolder); } FetchStream::~FetchStream() @@ -356,12 +376,9 @@ FetchStream::ErrorPropagation(JSContext* aCx, JS::HandleObject aStream, return; } - // We cannot continue with any other operation. - mState = eClosed; - // Let's close the stream. if (aError == NS_BASE_STREAM_CLOSED) { - JS::ReadableStreamClose(aCx, aStream); + CloseAndReleaseObjects(aCx, aStream); return; } @@ -428,6 +445,27 @@ FetchStream::OnInputStreamReady(nsIAsyncInputStream* aStream) return NS_OK; } +/* static */ nsresult +FetchStream::RetrieveInputStream(void* aUnderlyingReadableStreamSource, + nsIInputStream** aInputStream) +{ + MOZ_ASSERT(aUnderlyingReadableStreamSource); + MOZ_ASSERT(aInputStream); + + RefPtr stream = + static_cast(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 inputStream = stream->mOriginalInputStream; + inputStream.forget(aInputStream); + return NS_OK; +} + void FetchStream::Close() { @@ -437,27 +475,42 @@ FetchStream::Close() AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(mGlobal))) { + ReleaseObjects(); return; } JSContext* cx = jsapi.cx(); JS::Rooted stream(cx, mReadableStream); - JS::ReadableStreamClose(cx, stream); - CloseAndReleaseObjects(); + CloseAndReleaseObjects(cx, stream); } void -FetchStream::CloseAndReleaseObjects() +FetchStream::CloseAndReleaseObjects(JSContext* aCx, JS::HandleObject aStream) { MOZ_DIAGNOSTIC_ASSERT(mState != eClosed); + + ReleaseObjects(); + + if (JS::ReadableStreamIsReadable(aStream)) { + JS::ReadableStreamClose(aCx, aStream); + } +} + +void +FetchStream::ReleaseObjects() +{ + if (mState == eClosed) { + return; + } + mState = eClosed; if (mWorkerHolder) { RefPtr r = new FetchStreamWorkerHolderShutdown( static_cast(mWorkerHolder.get())->GetWorkerPrivate(), - Move(mWorkerHolder), Move(mGlobal)); + Move(mWorkerHolder), Move(mGlobal), Move(mStreamHolder)); r->Dispatch(); } else { RefPtr self = this; @@ -468,6 +521,9 @@ FetchStream::CloseAndReleaseObjects() os->RemoveObserver(self, DOM_WINDOW_DESTROYED_TOPIC); } self->mGlobal = nullptr; + + self->mStreamHolder->NullifyStream(); + self->mStreamHolder = nullptr; }); NS_DispatchToMainThread(r); diff --git a/dom/fetch/FetchStream.h b/dom/fetch/FetchStream.h index b18b7356ff..b27588eda0 100644 --- a/dom/fetch/FetchStream.h +++ b/dom/fetch/FetchStream.h @@ -24,6 +24,8 @@ namespace workers { class WorkerHolder; } +class FetchStreamHolder; + class FetchStream final : public nsIInputStreamCallback , public nsIObserver , public nsSupportsWeakReference @@ -34,14 +36,20 @@ public: NS_DECL_NSIOBSERVER static JSObject* - Create(JSContext* aCx, nsIGlobalObject* aGlobal, - nsIInputStream* aInputStream, ErrorResult& aRv); + Create(JSContext* aCx, FetchStreamHolder* aStreamHolder, + nsIGlobalObject* aGlobal, nsIInputStream* aInputStream, + ErrorResult& aRv); void Close(); + static nsresult + RetrieveInputStream(void* aUnderlyingReadableStreamSource, + nsIInputStream** aInputStream); private: - FetchStream(nsIGlobalObject* aGlobal, nsIInputStream* aInputStream); + FetchStream(nsIGlobalObject* aGlobal, + FetchStreamHolder* aStreamHolder, + nsIInputStream* aInputStream); ~FetchStream(); static void @@ -76,7 +84,10 @@ private: ErrorPropagation(JSContext* aCx, JS::HandleObject aStream, nsresult aRv); void - CloseAndReleaseObjects(); + CloseAndReleaseObjects(JSContext* aCx, JS::HandleObject aStream); + + void + ReleaseObjects(); // Common methods @@ -104,6 +115,8 @@ private: State mState; nsCOMPtr mGlobal; + RefPtr mStreamHolder; + nsCOMPtr mOwningEventTarget; // This is the original inputStream received during the CTOR. It will be // converted into an nsIAsyncInputStream and stored into mInputStream at the @@ -111,8 +124,6 @@ private: nsCOMPtr mOriginalInputStream; nsCOMPtr mInputStream; - nsCOMPtr mOwningEventTarget; - UniquePtr mWorkerHolder; JS::Heap mReadableStream; diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index 4d9f6fc342..03df3021c5 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -574,11 +574,9 @@ Request::Constructor(const GlobalObject& aGlobal, } if (aInit.mBody.WasPassed()) { - const Nullable& bodyInitNullable = - aInit.mBody.Value(); + const Nullable& bodyInitNullable = aInit.mBody.Value(); if (!bodyInitNullable.IsNull()) { - const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit = - bodyInitNullable.Value(); + const fetch::OwningBodyInit& bodyInit = bodyInitNullable.Value(); nsCOMPtr stream; nsAutoCString contentTypeWithCharset; uint64_t contentLengthUnused; diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp index 337139e3d2..d69a199076 100644 --- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -12,12 +12,15 @@ #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 "InternalResponse.h" #include "WorkerPrivate.h" @@ -130,7 +133,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl, return nullptr; } - Optional> body; + Optional body; ResponseInit init; init.mStatus = aStatus; RefPtr r = Response::Constructor(aGlobal, body, init, aRv); @@ -151,7 +154,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl, /*static*/ already_AddRefed Response::Constructor(const GlobalObject& aGlobal, - const Optional>& aBody, + const Optional& aBody, const ResponseInit& aInit, ErrorResult& aRv) { nsCOMPtr 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) { aRv.ThrowTypeError(); return nullptr; } - nsCOMPtr bodyStream; nsCString contentTypeWithCharset; + nsCOMPtr bodyStream; uint64_t bodySize = 0; - aRv = ExtractByteStreamFromBody(aBody.Value().Value(), - getter_AddRefs(bodyStream), - contentTypeWithCharset, - bodySize); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; + + if (aBody.Value().IsReadableStream()) { + const ReadableStream& readableStream = + aBody.Value().GetAsReadableStream(); + + JS::Rooted readableStreamObj(aGlobal.Context(), + readableStream.Obj()); + + if (JS::ReadableStreamIsDisturbed(readableStreamObj) || + JS::ReadableStreamIsLocked(readableStreamObj) || + !JS::ReadableStreamIsReadable(readableStreamObj)) { + aRv.ThrowTypeError(); + 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); if (!contentTypeWithCharset.IsVoid() && @@ -254,7 +300,7 @@ Response::Constructor(const GlobalObject& aGlobal, } already_AddRefed -Response::Clone(ErrorResult& aRv) const +Response::Clone(JSContext* aCx, ErrorResult& aRv) { if (BodyUsed()) { aRv.ThrowTypeError(); @@ -263,11 +309,26 @@ Response::Clone(ErrorResult& aRv) const RefPtr ir = mInternalResponse->Clone(); RefPtr response = new Response(mOwner, ir, mSignal); + + JS::Rooted 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(); } already_AddRefed -Response::CloneUnfiltered(ErrorResult& aRv) const +Response::CloneUnfiltered(JSContext* aCx, ErrorResult& aRv) { if (BodyUsed()) { aRv.ThrowTypeError(); @@ -277,6 +338,21 @@ Response::CloneUnfiltered(ErrorResult& aRv) const RefPtr clone = mInternalResponse->Clone(); RefPtr ir = clone->Unfiltered(); RefPtr ref = new Response(mOwner, ir, mSignal); + + JS::Rooted 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(); } diff --git a/dom/fetch/Response.h b/dom/fetch/Response.h index 61f1427f93..9b29dff998 100644 --- a/dom/fetch/Response.h +++ b/dom/fetch/Response.h @@ -115,7 +115,7 @@ public: static already_AddRefed Constructor(const GlobalObject& aGlobal, - const Optional>& aBody, + const Optional& aBody, const ResponseInit& aInit, ErrorResult& rv); nsIGlobalObject* GetParentObject() const @@ -124,10 +124,10 @@ public: } already_AddRefed - Clone(ErrorResult& aRv) const; + Clone(JSContext* aCx, ErrorResult& aRv); already_AddRefed - CloneUnfiltered(ErrorResult& aRv) const; + CloneUnfiltered(JSContext* aCx, ErrorResult& aRv); void SetBody(nsIInputStream* aBody, int64_t aBodySize); diff --git a/dom/webidl/Fetch.webidl b/dom/webidl/Fetch.webidl index 4b2a0af7d0..137cbde354 100644 --- a/dom/webidl/Fetch.webidl +++ b/dom/webidl/Fetch.webidl @@ -8,7 +8,7 @@ */ 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)] interface Body { diff --git a/dom/webidl/Response.webidl b/dom/webidl/Response.webidl index d02b714f7d..0f4c99dbac 100644 --- a/dom/webidl/Response.webidl +++ b/dom/webidl/Response.webidl @@ -7,7 +7,9 @@ * 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)] interface Response { [NewObject] static Response error();