diff options
Diffstat (limited to 'dom')
-rw-r--r-- | dom/fetch/Fetch.cpp | 100 | ||||
-rw-r--r-- | dom/fetch/Fetch.h | 10 | ||||
-rw-r--r-- | dom/fetch/FetchStreamReader.cpp | 307 | ||||
-rw-r--r-- | dom/fetch/FetchStreamReader.h | 77 | ||||
-rw-r--r-- | dom/fetch/InternalResponse.cpp | 6 | ||||
-rw-r--r-- | dom/fetch/InternalResponse.h | 8 | ||||
-rw-r--r-- | dom/fetch/Request.cpp | 3 | ||||
-rw-r--r-- | dom/fetch/Response.cpp | 106 | ||||
-rw-r--r-- | dom/fetch/moz.build | 2 | ||||
-rw-r--r-- | dom/webidl/Fetch.webidl | 12 |
10 files changed, 569 insertions, 62 deletions
diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index 99e26e8511..ce21cd391a 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -881,6 +881,7 @@ FetchBody<Derived>::FetchBody(nsIGlobalObject* aOwner) : mWorkerPrivate(nullptr) , mOwner(aOwner) , mReadableStreamBody(nullptr) + , mReadableStreamReader(nullptr) , mBodyUsed(false) { MOZ_ASSERT(aOwner); @@ -904,6 +905,12 @@ FetchBody<Derived>::~FetchBody() { } +template +FetchBody<Request>::~FetchBody(); + +template +FetchBody<Response>::~FetchBody(); + template <class Derived> bool FetchBody<Derived>::BodyUsed() const @@ -958,13 +965,27 @@ FetchBody<Derived>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorRes SetBodyUsed(); - // If we already created a ReadableStreamBody we have to lock it now because - // it may 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<JSObject*> body(aCx, mReadableStreamBody); - LockStream(aCx, body, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; + JS::Rooted<JSObject*> 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<JSObject*> reader(aCx); + mFetchStreamReader->StartConsuming(aCx, readableStreamObj, &reader, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + mReadableStreamReader = reader; } } @@ -1038,6 +1059,11 @@ 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)); @@ -1046,30 +1072,27 @@ FetchBody<Derived>::GetBody(JSContext* aCx, return; } - if (!mReadableStreamBody) { - JS::Rooted<JSObject*> body(aCx, - FetchStream::Create(aCx, - this, - DerivedClass()->GetParentObject(), - inputStream, - aRv)); - if (NS_WARN_IF(aRv.Failed())) { - return; - } + JS::Rooted<JSObject*> body(aCx, + FetchStream::Create(aCx, + this, + DerivedClass()->GetParentObject(), + inputStream, + aRv)); + if (NS_WARN_IF(aRv.Failed())) { + return; + } - MOZ_ASSERT(body); + 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; - } + // 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; } + mReadableStreamBody = body; aBodyOut.set(mReadableStreamBody); } @@ -1091,7 +1114,15 @@ FetchBody<Derived>::LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv) { - // XXXMC: TODO + JS::Rooted<JSObject*> reader(aCx, + JS::ReadableStreamGetReader(aCx, aStream, + JS::ReadableStreamReaderMode::Default)); + if (!reader) { + aRv.StealExceptionFromJSContext(aCx); + return; + } + + mReadableStreamReader = reader; } template @@ -1110,10 +1141,18 @@ 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; } @@ -1138,18 +1177,27 @@ FetchBody<Derived>::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<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 diff --git a/dom/fetch/Fetch.h b/dom/fetch/Fetch.h index 32c6671e08..f95a061bd9 100644 --- a/dom/fetch/Fetch.h +++ b/dom/fetch/Fetch.h @@ -17,6 +17,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;
@@ -183,6 +184,8 @@ public: void
MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aBodyOut,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream,
ErrorResult& aRv);
// Utility public methods accessed by various runnables.
@@ -199,10 +202,13 @@ public: return mMimeType;
}
+ // FetchStreamHolder
void
NullifyStream() override
{
mReadableStreamBody = nullptr;
+ mReadableStreamReader = nullptr;
+ mFetchStreamReader = nullptr;
}
virtual AbortSignal*
@@ -219,6 +225,10 @@ protected: // 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
diff --git a/dom/fetch/FetchStreamReader.cpp b/dom/fetch/FetchStreamReader.cpp new file mode 100644 index 0000000000..98d2588a0e --- /dev/null +++ b/dom/fetch/FetchStreamReader.cpp @@ -0,0 +1,307 @@ +/* -*- 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<FetchStreamReader> 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<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) + , 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<FetchStreamReader> 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<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(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<JSObject*> reader(cx, mReader); + JS::Rooted<JSObject*> 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<Promise> 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<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(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + if (valueDone.mDone) { + // Stream is completed. + CloseAndRelease(NS_BASE_STREAM_CLOSED); + return; + } + + UniquePtr<FetchReadableStreamReadDataArray> 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<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))) { + 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<JS::Value> 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 0000000000..8984a2ea73 --- /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<JS::Value> aValue) override; + + void + RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; + + void + CloseAndRelease(nsresult aStatus); + + void + StartConsuming(JSContext* aCx, + JS::HandleObject aStream, + JS::MutableHandle<JSObject*> aReader, + ErrorResult& aRv); + +private: + explicit FetchStreamReader(nsIGlobalObject* aGlobal); + ~FetchStreamReader(); + + nsresult + WriteBuffer(); + + 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 diff --git a/dom/fetch/InternalResponse.cpp b/dom/fetch/InternalResponse.cpp index ec32c4e216..f4fd1912f9 100644 --- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -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(); } diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h index e4b4a0ab62..d5c759d577 100644 --- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -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() diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index eec4828131..ec140a22d4 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -44,7 +44,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Request) 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 b92e918e7c..5055db4e24 100644 --- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -21,6 +21,7 @@ #include "BodyExtractor.h" #include "FetchStream.h" +#include "FetchStreamReader.h" #include "InternalResponse.h" #include "WorkerPrivate.h" @@ -37,6 +38,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 @@ -48,6 +50,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 @@ -227,7 +230,7 @@ Response::Constructor(const GlobalObject& aGlobal, nsCString contentTypeWithCharset; nsCOMPtr<nsIInputStream> bodyStream; - uint64_t bodySize = 0; + int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE; if (aBody.Value().IsReadableStream()) { const ReadableStream& readableStream = @@ -245,37 +248,50 @@ Response::Constructor(const GlobalObject& aGlobal, 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; + 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; + } } - - 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 { + 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 +322,35 @@ Response::Clone(JSContext* aCx, ErrorResult& aRv) 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, 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<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(); @@ -334,22 +364,36 @@ Response::CloneUnfiltered(JSContext* aCx, ErrorResult& aRv) return nullptr; } - RefPtr<InternalResponse> clone = mInternalResponse->Clone(); - RefPtr<InternalResponse> ir = clone->Unfiltered(); - RefPtr<Response> ref = new Response(mOwner, ir, mSignal); + RefPtr<FetchStreamReader> streamReader; + nsCOMPtr<nsIInputStream> inputStream; JS::Rooted<JSObject*> 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<InternalResponse> clone = + mInternalResponse->Clone(body + ? InternalResponse::eDontCloneInputStream + : InternalResponse::eCloneInputStream); + + RefPtr<InternalResponse> ir = clone->Unfiltered(); + 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(); diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build index d08bdf0cf2..f01254e042 100644 --- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -10,6 +10,7 @@ EXPORTS.mozilla.dom += [ 'FetchDriver.h', 'FetchIPCTypes.h', 'FetchObserver.h', + 'FetchStreamReader.h', 'FetchUtil.h', 'Headers.h', 'InternalHeaders.h', @@ -27,6 +28,7 @@ UNIFIED_SOURCES += [ 'FetchDriver.cpp', 'FetchObserver.cpp', 'FetchStream.cpp', + 'FetchStreamReader.cpp', 'FetchUtil.cpp', 'Headers.cpp', 'InternalHeaders.cpp', diff --git a/dom/webidl/Fetch.webidl b/dom/webidl/Fetch.webidl index 137cbde354..bbb1faf7f7 100644 --- a/dom/webidl/Fetch.webidl +++ b/dom/webidl/Fetch.webidl @@ -24,3 +24,15 @@ interface Body { [Throws] Promise<USVString> 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; +}; |