Skip to content

Commit

Permalink
Bug 1750298 - Use custom ReadRequest for FetchStreamReader. r=smaug
Browse files Browse the repository at this point in the history
  • Loading branch information
evilpie committed Mar 18, 2022
1 parent d62adda commit 1aeddf5
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 60 deletions.
109 changes: 93 additions & 16 deletions dom/fetch/FetchStreamReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,43 @@ void FetchStreamReader::StartConsuming(JSContext* aCx, JS::HandleObject aStream,
}
#endif

#ifdef MOZ_DOM_STREAMS
struct FetchReadRequest : public ReadRequest {
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchReadRequest, ReadRequest)

explicit FetchReadRequest(FetchStreamReader* aReader)
: mFetchStreamReader(aReader) {}

void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv) override {
mFetchStreamReader->ChunkSteps(aCx, aChunk, aRv);
}

void CloseSteps(JSContext* aCx, ErrorResult& aRv) override {
mFetchStreamReader->CloseAndRelease(aCx, NS_BASE_STREAM_CLOSED);
}

void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
ErrorResult& aRv) override {
mFetchStreamReader->ErrorSteps(aCx, aError, aRv);
}

protected:
virtual ~FetchReadRequest() = default;

RefPtr<FetchStreamReader> mFetchStreamReader;
};

NS_IMPL_CYCLE_COLLECTION_INHERITED(FetchReadRequest, ReadRequest,
mFetchStreamReader)
NS_IMPL_ADDREF_INHERITED(FetchReadRequest, ReadRequest)
NS_IMPL_RELEASE_INHERITED(FetchReadRequest, ReadRequest)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchReadRequest)
NS_INTERFACE_MAP_END_INHERITING(ReadRequest)
#endif

// nsIOutputStreamCallback interface
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHODIMP
Expand All @@ -273,22 +310,10 @@ FetchStreamReader::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
#ifdef MOZ_DOM_STREAMS
IgnoredErrorResult rv;

// https://fetch.spec.whatwg.org/#incrementally-read-loop
// The below very loosely tries to implement the incrementally-read-loop from
// the fetch spec: However, because of the structure of the surrounding code,
// it makes use of the Read_ReadRequest with one modification: For the
// purposes of this read, we use `aForAuthorCode=false` in constructing the
// read request. This makes the value resolve have a null prototype, hiding
// this code from potential interference via `Object.prototype.then`.
RefPtr<Promise> domPromise = Promise::Create(mGlobal, rv);
if (NS_WARN_IF(rv.Failed())) {
// Let's close the stream.
CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
return NS_ERROR_FAILURE;
}

RefPtr<ReadRequest> readRequest =
new Read_ReadRequest(domPromise, /* aForAuthorCode = */ false);

// the fetch spec.
RefPtr<ReadRequest> readRequest = new FetchReadRequest(this);
ReadableStreamDefaultReaderRead(aes.cx(), MOZ_KnownLive(mReader), readRequest,
rv);

Expand All @@ -314,12 +339,61 @@ FetchStreamReader::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
return NS_ERROR_FAILURE;
}
#endif

// Let's wait.
domPromise->AppendNativeHandler(this);
#endif

return NS_OK;
}

#ifdef MOZ_DOM_STREAMS
void FetchStreamReader::ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv) {
// This roughly implements the chunk steps from
// https://fetch.spec.whatwg.org/#incrementally-read-loop.

RootedSpiderMonkeyInterface<Uint8Array> chunk(aCx);
if (!aChunk.isObject() || !chunk.Init(&aChunk.toObject())) {
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
chunk.ComputeState();

uint32_t len = chunk.Length();
if (len == 0) {
// If there is nothing to read, let's do another reading.
OnOutputStreamReady(mPipeOut);
return;
}

MOZ_DIAGNOSTIC_ASSERT(mBuffer.IsEmpty());

// Let's take a copy of the data.
if (!mBuffer.AppendElements(chunk.Data(), len, fallible)) {
CloseAndRelease(aCx, NS_ERROR_OUT_OF_MEMORY);
return;
}

mBufferOffset = 0;
mBufferRemaining = len;

nsresult rv = WriteBuffer();
if (NS_FAILED(rv)) {
// DOMException only understands errors from domerr.msg, so we normalize to
// identifying an abort if the write fails.
CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
}
}

void FetchStreamReader::ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
ErrorResult& aRv) {
ReportErrorToConsole(aCx, aError);
CloseAndRelease(aCx, NS_ERROR_FAILURE);
}

#else

void FetchStreamReader::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
Expand Down Expand Up @@ -382,6 +456,7 @@ void FetchStreamReader::ResolvedCallback(JSContext* aCx,
CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
}
}
#endif

nsresult FetchStreamReader::WriteBuffer() {
MOZ_ASSERT(!mBuffer.IsEmpty());
Expand Down Expand Up @@ -419,12 +494,14 @@ nsresult FetchStreamReader::WriteBuffer() {
return NS_OK;
}

#ifndef MOZ_DOM_STREAMS
void FetchStreamReader::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
ReportErrorToConsole(aCx, aValue);
CloseAndRelease(aCx, NS_ERROR_FAILURE);
}
#endif

void FetchStreamReader::ReportErrorToConsole(JSContext* aCx,
JS::Handle<JS::Value> aValue) {
Expand Down
15 changes: 13 additions & 2 deletions dom/fetch/FetchStreamReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ class ReadableStream;
class ReadableStreamDefaultReader;
class WeakWorkerRef;

class FetchStreamReader final : public nsIOutputStreamCallback,
public PromiseNativeHandler {
class FetchStreamReader final : public nsIOutputStreamCallback
#ifndef MOZ_DOM_STREAMS
,
public PromiseNativeHandler
#endif
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(
Expand All @@ -36,11 +40,18 @@ class FetchStreamReader final : public nsIOutputStreamCallback,
FetchStreamReader** aStreamReader,
nsIInputStream** aInputStream);

#ifdef MOZ_DOM_STREAMS
void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv);
void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
ErrorResult& aRv);
#else
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override;

void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override;
#endif

// Idempotently close the output stream and null out all state. If aCx is
// provided, the reader will also be canceled. aStatus must be a DOM error
Expand Down
2 changes: 1 addition & 1 deletion dom/streams/ReadableStreamBYOBReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ struct Read_ReadIntoRequest final : public ReadIntoRequest {
if (aChunk.isObject()) {
// We need to wrap this as the chunk could have come from
// another compartment.
JS::RootedObject chunk(aCx, &aChunk.toObject());
JS::Rooted<JSObject*> chunk(aCx, &aChunk.toObject());
if (!JS_WrapObject(aCx, &chunk)) {
return;
}
Expand Down
68 changes: 32 additions & 36 deletions dom/streams/ReadableStreamDefaultReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "mozilla/dom/ReadableStreamDefaultReader.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/RootedDictionary.h"
#include "js/PropertyAndElement.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
Expand Down Expand Up @@ -131,61 +132,56 @@ ReadableStreamDefaultReader::Constructor(const GlobalObject& aGlobal,
return reader.forget();
}

static bool CreateValueDonePair(JSContext* aCx, bool forAuthorCode,
JS::HandleValue aValue, bool aDone,
JS::MutableHandleValue aReturnValue) {
JS::RootedObject obj(
aCx, forAuthorCode ? JS_NewPlainObject(aCx)
: JS_NewObjectWithGivenProto(aCx, nullptr, nullptr));
if (!obj) {
return false;
}
void Read_ReadRequest::ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv) {
// https://streams.spec.whatwg.org/#default-reader-read Step 3.
// chunk steps, given chunk:
// Step 1. Resolve promise with «[ "value" → chunk, "done" → false ]».

// Value may need to be wrapped if stream and reader are in different
// compartments.
JS::RootedValue value(aCx, aValue);
if (!JS_WrapValue(aCx, &value)) {
return false;
}

if (!JS_DefineProperty(aCx, obj, "value", value, JSPROP_ENUMERATE)) {
return false;
}
JS::RootedValue done(aCx, JS::BooleanValue(aDone));
if (!JS_DefineProperty(aCx, obj, "done", done, JSPROP_ENUMERATE)) {
return false;
JS::Rooted<JS::Value> chunk(aCx, aChunk);
if (!JS_WrapValue(aCx, &chunk)) {
aRv.StealExceptionFromJSContext(aCx);
return;
}

aReturnValue.setObject(*obj);
return true;
}
RootedDictionary<ReadableStreamDefaultReadResult> result(aCx);
result.mValue = chunk;
result.mDone.Construct(false);

void Read_ReadRequest::ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv) {
// Step 1.
JS::RootedValue resolvedValue(aCx);
if (!CreateValueDonePair(aCx, mForAuthorCode, aChunk, false,
&resolvedValue)) {
// Ensure that the object is created with the current global.
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, std::move(result), &value)) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
mPromise->MaybeResolve(resolvedValue);

mPromise->MaybeResolve(value);
}

void Read_ReadRequest::CloseSteps(JSContext* aCx, ErrorResult& aRv) {
// Step 1.
JS::RootedValue undefined(aCx, JS::UndefinedValue());
JS::RootedValue resolvedValue(aCx);
if (!CreateValueDonePair(aCx, mForAuthorCode, undefined, true,
&resolvedValue)) {
// https://streams.spec.whatwg.org/#default-reader-read Step 3.
// close steps:
// Step 1. Resolve promise with «[ "value" → undefined, "done" → true ]».
RootedDictionary<ReadableStreamDefaultReadResult> result(aCx);
result.mValue.setUndefined();
result.mDone.Construct(true);

JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, std::move(result), &value)) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
mPromise->MaybeResolve(resolvedValue);

mPromise->MaybeResolve(value);
}

void Read_ReadRequest::ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> e,
ErrorResult& aRv) {
// https://streams.spec.whatwg.org/#default-reader-read Step 3.
// error steps:
// Step 1. Reject promise with e.
mPromise->MaybeReject(e);
}

Expand Down
6 changes: 1 addition & 5 deletions dom/streams/ReadableStreamDefaultReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,8 @@ struct Read_ReadRequest : public ReadRequest {
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Read_ReadRequest, ReadRequest)

RefPtr<Promise> mPromise;
/* This allows Gecko Internals to create objects with null prototypes, to hide
* promise resolution from Object.prototype.then */
bool mForAuthorCode = true;

explicit Read_ReadRequest(Promise* aPromise, bool aForAuthorCode = true)
: mPromise(aPromise), mForAuthorCode(aForAuthorCode) {}
explicit Read_ReadRequest(Promise* aPromise) : mPromise(aPromise) {}

void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
ErrorResult& aRv) override;
Expand Down

0 comments on commit 1aeddf5

Please sign in to comment.