diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index f680f344fa1a..5ff4a4fb25f1 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -900,6 +900,7 @@ FetchBody::FetchBody(nsIGlobalObject* aOwner) : mOwner(aOwner) , mWorkerPrivate(nullptr) , mReadableStreamBody(nullptr) + , mReadableStreamReader(nullptr) , mBodyUsed(false) { MOZ_ASSERT(aOwner); @@ -926,6 +927,12 @@ FetchBody::~FetchBody() { } +template +FetchBody::~FetchBody(); + +template +FetchBody::~FetchBody(); + template bool FetchBody::BodyUsed() const @@ -974,13 +981,27 @@ FetchBody::ConsumeBody(JSContext* aCx, FetchConsumeType aType, SetBodyUsed(); - // If we already created a ReadableStreamBody we have to lock it now because - // it can have been shared with other objects. + // 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 body(aCx, mReadableStreamBody); - LockStream(aCx, body, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; + JS::Rooted readableStreamObj(aCx, mReadableStreamBody); + if (JS::ReadableStreamGetMode(readableStreamObj) == + JS::ReadableStreamMode::ExternalSource) { + LockStream(aCx, readableStreamObj, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } else { + // If this is not a native ReadableStream, let's activate the + // FetchStreamReader. + MOZ_ASSERT(mFetchStreamReader); + JS::Rooted reader(aCx); + mFetchStreamReader->StartConsuming(aCx, readableStreamObj, &reader, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + mReadableStreamReader = reader; } } @@ -1057,6 +1078,11 @@ FetchBody::GetBody(JSContext* aCx, JS::MutableHandle aBodyOut, ErrorResult& aRv) { + if (mReadableStreamBody) { + aBodyOut.set(mReadableStreamBody); + return; + } + nsCOMPtr inputStream; DerivedClass()->GetBody(getter_AddRefs(inputStream)); @@ -1065,30 +1091,27 @@ FetchBody::GetBody(JSContext* aCx, return; } - if (!mReadableStreamBody) { - JS::Rooted body(aCx, - FetchStream::Create(aCx, - this, - DerivedClass()->GetParentObject(), - inputStream, - aRv)); + JS::Rooted body(aCx, + FetchStream::Create(aCx, + this, + DerivedClass()->GetParentObject(), + inputStream, + 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; } - - MOZ_ASSERT(body); - - // If the body has been already consumed, we close the stream. - if (BodyUsed()) { - LockStream(aCx, body, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return; - } - } - - mReadableStreamBody = body; } + mReadableStreamBody = body; aBodyOut.set(mReadableStreamBody); } @@ -1110,7 +1133,15 @@ FetchBody::LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv) { - // TODO: next patch. + JS::Rooted reader(aCx, + JS::ReadableStreamGetReader(aCx, aStream, + JS::ReadableStreamReaderMode::Default)); + if (!reader) { + aRv.StealExceptionFromJSContext(aCx); + return; + } + + mReadableStreamReader = reader; } template @@ -1129,10 +1160,18 @@ template void FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, JS::MutableHandle 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; } @@ -1157,18 +1196,27 @@ FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, mReadableStreamBody = branch1; aBodyOut.set(branch2); + + aRv = FetchStreamReader::Create(aCx, mOwner, aStreamReader, aInputStream); + if (NS_WARN_IF(aRv.Failed())) { + return; + } } template void FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, JS::MutableHandle aMessage, + FetchStreamReader** aStreamReader, + nsIInputStream** aInputStream, ErrorResult& aRv); template void FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, JS::MutableHandle aMessage, + FetchStreamReader** aStreamReader, + nsIInputStream** aInputStream, ErrorResult& aRv); } // namespace dom diff --git a/dom/fetch/Fetch.h b/dom/fetch/Fetch.h index ade498ec615f..cce0d735b755 100644 --- a/dom/fetch/Fetch.h +++ b/dom/fetch/Fetch.h @@ -18,6 +18,7 @@ #include "mozilla/DebugOnly.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/Promise.h" +#include "mozilla/dom/FetchStreamReader.h" #include "mozilla/dom/RequestBinding.h" class nsIGlobalObject; @@ -185,6 +186,8 @@ public: void MaybeTeeReadableStreamBody(JSContext* aCx, JS::MutableHandle aBodyOut, + FetchStreamReader** aStreamReader, + nsIInputStream** aInputStream, ErrorResult& aRv); // Utility public methods accessed by various runnables. @@ -201,10 +204,13 @@ public: return mMimeType; } + // FetchStreamHolder void NullifyStream() override { mReadableStreamBody = nullptr; + mReadableStreamReader = nullptr; + mFetchStreamReader = nullptr; } protected: @@ -217,6 +223,10 @@ protected: // FetchStream object. JS::Heap mReadableStreamBody; + // This is the Reader used to retrieve data from the body. + JS::Heap mReadableStreamReader; + RefPtr mFetchStreamReader; + explicit FetchBody(nsIGlobalObject* aOwner); virtual ~FetchBody(); diff --git a/dom/fetch/FetchStreamReader.cpp b/dom/fetch/FetchStreamReader.cpp new file mode 100644 index 000000000000..136912bcf57b --- /dev/null +++ b/dom/fetch/FetchStreamReader.cpp @@ -0,0 +1,308 @@ +/* -*- 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" + +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; + mReader->CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR); + } + + return true; + } + +private: + RefPtr mReader; + bool mWasNotified; +}; + +} // anonymous + +NS_IMPL_ISUPPORTS(FetchStreamReader, nsIOutputStreamCallback) + +/* 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 streamReader = new FetchStreamReader(aGlobal); + + nsCOMPtr 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 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) + , mOwningEventTarget(mGlobal->EventTargetFor(TaskCategory::Other)) + , mBufferRemaining(0) + , mBufferOffset(0) + , mStreamClosed(false) +{ + MOZ_ASSERT(aGlobal); +} + +FetchStreamReader::~FetchStreamReader() +{ + CloseAndRelease(NS_BASE_STREAM_CLOSED); +} + +void +FetchStreamReader::CloseAndRelease(nsresult aStatus) +{ + NS_ASSERT_OWNINGTHREAD(FetchStreamReader); + + if (mStreamClosed) { + // Already closed. + return; + } + + RefPtr kungFuDeathGrip = this; + + 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 aReader, + ErrorResult& aRv) +{ + MOZ_DIAGNOSTIC_ASSERT(!mReader); + MOZ_DIAGNOSTIC_ASSERT(aStream); + + JS::Rooted reader(aCx, + JS::ReadableStreamGetReader(aCx, aStream, + JS::ReadableStreamReaderMode::Default)); + if (!reader) { + aRv.StealExceptionFromJSContext(aCx); + CloseAndRelease(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(); + } + + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(mGlobal))) { + CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR); + return NS_ERROR_FAILURE; + } + + JSContext* cx = jsapi.cx(); + + JS::Rooted reader(cx, mReader); + JS::Rooted promise(cx, + JS::ReadableStreamDefaultReaderRead(cx, + reader)); + if (NS_WARN_IF(!promise)) { + // Let's close the stream. + CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR); + return NS_ERROR_FAILURE; + } + + RefPtr domPromise = Promise::CreateFromExisting(mGlobal, promise); + if (NS_WARN_IF(!domPromise)) { + // Let's close the stream. + CloseAndRelease(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 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(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + if (valueDone.mDone) { + // Stream is completed. + CloseAndRelease(NS_BASE_STREAM_CLOSED); + return; + } + + UniquePtr value( + new FetchReadableStreamReadDataArray); + if (!value->Init(aCx, aValue) || !value->mValue.WasPassed()) { + JS_ClearPendingException(aCx); + CloseAndRelease(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; + + WriteBuffer(); +} + +nsresult +FetchStreamReader::WriteBuffer() +{ + MOZ_ASSERT(mBuffer); + MOZ_ASSERT(mBuffer->mValue.WasPassed()); + + Uint8Array& array = mBuffer->mValue.Value(); + char* data = reinterpret_cast(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))) { + CloseAndRelease(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))) { + CloseAndRelease(rv); + return rv; + } + + return NS_OK; +} + +void +FetchStreamReader::RejectedCallback(JSContext* aCx, + JS::Handle aValue) +{ + CloseAndRelease(NS_ERROR_FAILURE); +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/fetch/FetchStreamReader.h b/dom/fetch/FetchStreamReader.h new file mode 100644 index 000000000000..8984a2ea7374 --- /dev/null +++ b/dom/fetch/FetchStreamReader.h @@ -0,0 +1,77 @@ +/* -*- 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_ISUPPORTS + 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 aValue) override; + + void + RejectedCallback(JSContext* aCx, JS::Handle aValue) override; + + void + CloseAndRelease(nsresult aStatus); + + void + StartConsuming(JSContext* aCx, + JS::HandleObject aStream, + JS::MutableHandle aReader, + ErrorResult& aRv); + +private: + explicit FetchStreamReader(nsIGlobalObject* aGlobal); + ~FetchStreamReader(); + + nsresult + WriteBuffer(); + + nsCOMPtr mGlobal; + nsCOMPtr mOwningEventTarget; + + nsCOMPtr mPipeOut; + + UniquePtr mWorkerHolder; + + JS::Heap mReader; + + UniquePtr mBuffer; + uint32_t mBufferRemaining; + uint32_t mBufferOffset; + + bool mStreamClosed; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_FetchStreamReader_h diff --git a/dom/fetch/InternalResponse.cpp b/dom/fetch/InternalResponse.cpp index 1c6c53c2b2f5..ed2f7a7544f6 100644 --- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -137,18 +137,18 @@ InternalResponse::ToIPC(IPCInternalResponse* aIPCResponse, } already_AddRefed -InternalResponse::Clone() +InternalResponse::Clone(CloneType aCloneType) { RefPtr 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(); } diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h index 6c34db96ff44..26ae456dde3b 100644 --- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -44,7 +44,13 @@ public: M* aManager, UniquePtr& aAutoStream); - already_AddRefed Clone(); + enum CloneType + { + eCloneInputStream, + eDontCloneInputStream, + }; + + already_AddRefed Clone(CloneType eCloneType); static already_AddRefed NetworkError() diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index 85fe82caf46e..0d260b36852c 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -41,6 +41,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Request) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamBody) + 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 diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp index 0e1f6892d579..50715b225b48 100644 --- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -22,6 +22,7 @@ #include "BodyExtractor.h" #include "FetchStream.h" +#include "FetchStreamReader.h" #include "InternalResponse.h" #include "WorkerPrivate.h" @@ -38,6 +39,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Response) NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders) tmp->mReadableStreamBody = nullptr; + tmp->mReadableStreamReader = nullptr; NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END @@ -49,6 +51,7 @@ 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 @@ -225,7 +228,7 @@ Response::Constructor(const GlobalObject& aGlobal, nsCString contentTypeWithCharset; nsCOMPtr bodyStream; - uint64_t bodySize = 0; + int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE; if (aBody.Value().IsReadableStream()) { const ReadableStream& readableStream = @@ -243,38 +246,50 @@ Response::Constructor(const GlobalObject& aGlobal, r->SetReadableStreamBody(readableStreamObj); - // TODO: see next patches - MOZ_ASSERT(JS::ReadableStreamGetMode(readableStreamObj) != - JS::ReadableStreamMode::ExternalSource); + 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; + } - void* underlyingSource = nullptr; - if (!JS::ReadableStreamGetExternalUnderlyingSource(aGlobal.Context(), - readableStreamObj, - &underlyingSource)) { - aRv.StealExceptionFromJSContext(aGlobal.Context()); - return nullptr; - } + MOZ_ASSERT(underlyingSource); - bodySize = InternalResponse::UNKNOWN_BODY_SIZE; + aRv = FetchStream::RetrieveInputStream(underlyingSource, + getter_AddRefs(bodyStream)); - 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; + // 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, - bodySize); + size); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } + + bodySize = size; } internalResponse->SetBody(bodyStream, bodySize); @@ -306,21 +321,35 @@ Response::Clone(JSContext* aCx, ErrorResult& aRv) return nullptr; } - RefPtr ir = mInternalResponse->Clone(); - RefPtr response = new Response(mOwner, ir); + RefPtr streamReader; + nsCOMPtr inputStream; JS::Rooted body(aCx); - MaybeTeeReadableStreamBody(aCx, &body, aRv); + 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 ir = + mInternalResponse->Clone(body + ? InternalResponse::eDontCloneInputStream + : InternalResponse::eCloneInputStream); + + RefPtr response = new Response(mOwner, ir); + 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(); @@ -334,22 +363,36 @@ Response::CloneUnfiltered(JSContext* aCx, ErrorResult& aRv) return nullptr; } - RefPtr clone = mInternalResponse->Clone(); - RefPtr ir = clone->Unfiltered(); - RefPtr ref = new Response(mOwner, ir); + RefPtr streamReader; + nsCOMPtr inputStream; JS::Rooted body(aCx); - MaybeTeeReadableStreamBody(aCx, &body, aRv); + 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 clone = + mInternalResponse->Clone(body + ? InternalResponse::eDontCloneInputStream + : InternalResponse::eCloneInputStream); + + RefPtr ir = clone->Unfiltered(); + RefPtr ref = new Response(mOwner, ir); + 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(); diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build index 894b8909e3f7..d989c45874f0 100644 --- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -16,6 +16,7 @@ EXPORTS.mozilla.dom += [ 'FetchIPCTypes.h', 'FetchObserver.h', 'FetchSignal.h', + 'FetchStreamReader.h', 'FetchUtil.h', 'Headers.h', 'InternalHeaders.h', @@ -35,6 +36,7 @@ UNIFIED_SOURCES += [ 'FetchObserver.cpp', 'FetchSignal.cpp', 'FetchStream.cpp', + 'FetchStreamReader.cpp', 'FetchUtil.cpp', 'Headers.cpp', 'InternalHeaders.cpp', diff --git a/dom/webidl/Fetch.webidl b/dom/webidl/Fetch.webidl index 137cbde35441..bbb1faf7f7d7 100644 --- a/dom/webidl/Fetch.webidl +++ b/dom/webidl/Fetch.webidl @@ -24,3 +24,15 @@ interface Body { [Throws] Promise text(); }; + +// These are helper dictionaries for the parsing of a +// getReader().read().then(data) parsing. +// See more about how these 2 helpers are used in +// dom/fetch/FetchStreamReader.cpp +dictionary FetchReadableStreamReadDataDone { + boolean done = false; +}; + +dictionary FetchReadableStreamReadDataArray { + Uint8Array value; +};