diff --git a/dom/fetch/FetchStreamReader.cpp b/dom/fetch/FetchStreamReader.cpp index e79c502d3b86..3ebb3bc2c90c 100644 --- a/dom/fetch/FetchStreamReader.cpp +++ b/dom/fetch/FetchStreamReader.cpp @@ -72,9 +72,17 @@ nsresult OutputStreamHolder::AsyncWait(uint32_t aFlags, uint32_t aRequestedCount, nsIEventTarget* aEventTarget) { mAsyncWaitWorkerRef = mWorkerRef; + // Grab the strong reference for the reader but only when we are waiting for + // the output stream, because it means we still have things to write. + // (WAIT_CLOSURE_ONLY happens when waiting for ReadableStream to respond, at + // which point the pull callback should get an indirect strong reference via + // the controller argument.) + mAsyncWaitReader = + aFlags == nsIAsyncOutputStream::WAIT_CLOSURE_ONLY ? nullptr : mReader; nsresult rv = mOutput->AsyncWait(this, aFlags, aRequestedCount, aEventTarget); if (NS_WARN_IF(NS_FAILED(rv))) { mAsyncWaitWorkerRef = nullptr; + mAsyncWaitReader = nullptr; } return rv; } @@ -84,10 +92,16 @@ NS_IMETHODIMP OutputStreamHolder::OnOutputStreamReady( // We may get called back after ::Shutdown() if (!mReader) { mAsyncWaitWorkerRef = nullptr; + MOZ_ASSERT(!mAsyncWaitReader); return NS_OK; } - if (!mReader->OnOutputStreamReady()) { + + // mAsyncWaitReader may be reset during OnOutputStreamReady, make sure to let + // it live during the call + RefPtr reader = mReader.get(); + if (!reader->OnOutputStreamReady()) { mAsyncWaitWorkerRef = nullptr; + mAsyncWaitReader = nullptr; return NS_OK; } return NS_OK; diff --git a/dom/fetch/FetchStreamReader.h b/dom/fetch/FetchStreamReader.h index bc1c102c0339..86a271764106 100644 --- a/dom/fetch/FetchStreamReader.h +++ b/dom/fetch/FetchStreamReader.h @@ -51,6 +51,7 @@ class OutputStreamHolder final : public nsIOutputStreamCallback { private: ~OutputStreamHolder(); + RefPtr mAsyncWaitReader; // WeakPtr to avoid cycles WeakPtr mReader; // To ensure the worker sticks around diff --git a/testing/web-platform/tests/fetch/api/basic/gc.any.js b/testing/web-platform/tests/fetch/api/basic/gc.any.js new file mode 100644 index 000000000000..70362ff39ce7 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/basic/gc.any.js @@ -0,0 +1,19 @@ +// META: global=window,worker +// META: script=/common/gc.js + +promise_test(async () => { + let i = 0; + const repeat = 5; + const buffer = await new Response(new ReadableStream({ + pull(c) { + if (i >= repeat) { + c.close(); + return; + } + ++i; + c.enqueue(new Uint8Array([0])) + garbageCollect(); + } + })).arrayBuffer(); + assert_equals(buffer.byteLength, repeat, `The buffer should be ${repeat}-byte long`); +}, "GC/CC should not abruptly close the stream while being consumed by Response");