@@ -599,8 +599,12 @@ class ReadableStream {
599599 ! controller [ kState ] . queue . length ) {
600600 readableStreamDefaultControllerClearAlgorithms ( controller ) ;
601601 readableStreamClose ( stream ) ;
602- } else {
603- readableStreamDefaultControllerCallPullIfNeeded ( controller ) ;
602+ } else if ( ! controller [ kState ] . closeRequested &&
603+ controller [ kState ] . started &&
604+ controller [ kState ] . highWaterMark -
605+ controller [ kState ] . queueTotalSize > 0 ) {
606+ // Reduced ShouldCallPull, as in the read() fast path.
607+ readableStreamDefaultControllerPull ( controller ) ;
604608 }
605609
606610 return PromiseResolve ( { done : false , value : chunk } ) ;
@@ -949,8 +953,14 @@ class ReadableStreamDefaultReader {
949953 if ( controller [ kState ] . closeRequested && ! controller [ kState ] . queue . length ) {
950954 readableStreamDefaultControllerClearAlgorithms ( controller ) ;
951955 readableStreamClose ( stream ) ;
952- } else {
953- readableStreamDefaultControllerCallPullIfNeeded ( controller ) ;
956+ } else if ( ! controller [ kState ] . closeRequested &&
957+ controller [ kState ] . started &&
958+ controller [ kState ] . highWaterMark -
959+ controller [ kState ] . queueTotalSize > 0 ) {
960+ // ShouldCallPull reduced to the conditions not already
961+ // established on this path: the state is readable and the
962+ // queue was non-empty, so no read requests can be parked.
963+ readableStreamDefaultControllerPull ( controller ) ;
954964 }
955965
956966 return PromiseResolve ( { done : false , value : chunk } ) ;
@@ -2524,13 +2534,28 @@ function readableStreamDefaultControllerEnqueue(controller, chunk) {
25242534 reader [ kState ] !== undefined &&
25252535 reader [ kType ] === 'ReadableStreamDefaultReader' &&
25262536 reader [ kState ] . readRequests . length ) {
2537+ // Fulfilling a read request can run user code synchronously (the
2538+ // pipeTo read request invokes the sink's write algorithm), so the
2539+ // full ShouldCallPull predicate has to be re-evaluated afterwards.
25272540 readableStreamFulfillReadRequest ( stream , chunk , false ) ;
25282541 } else if ( controllerState . sizeAlgorithm === defaultSizeAlgorithm ) {
25292542 // The internal default size algorithm is never observable by user
25302543 // code, always returns 1, and cannot throw: enqueue with the
2531- // constant instead of calling it.
2532- enqueueValueWithSize ( controller , chunk , 1 ) ;
2544+ // constant size instead of calling it or validating the result.
2545+ // No user code runs between the guards at the top of this function
2546+ // and this point, so ShouldCallPull reduces to the started flag and
2547+ // the desired size (this branch implies no parked read requests,
2548+ // ruling out the reader arm of the predicate).
2549+ materializeQueue ( controllerState ) . pushPair ( chunk , 1 ) ;
2550+ controllerState . queueTotalSize ++ ;
2551+ if ( controllerState . started &&
2552+ controllerState . highWaterMark - controllerState . queueTotalSize > 0 ) {
2553+ readableStreamDefaultControllerPull ( controller ) ;
2554+ }
2555+ return ;
25332556 } else {
2557+ // The user-supplied size algorithm may run arbitrary code, so the
2558+ // full ShouldCallPull predicate has to be re-evaluated afterwards.
25342559 try {
25352560 const chunkSize =
25362561 FunctionPrototypeCall (
@@ -2599,6 +2624,13 @@ function readableStreamDefaultControllerShouldCallPull(controller) {
25992624function readableStreamDefaultControllerCallPullIfNeeded ( controller ) {
26002625 if ( ! readableStreamDefaultControllerShouldCallPull ( controller ) )
26012626 return ;
2627+ readableStreamDefaultControllerPull ( controller ) ;
2628+ }
2629+
2630+ // The ShouldCallPull half of CallPullIfNeeded, split out so that callers
2631+ // that have already established the predicate from state in scope (the
2632+ // enqueue path above) can skip re-running it.
2633+ function readableStreamDefaultControllerPull ( controller ) {
26022634 if ( controller [ kState ] . pulling ) {
26032635 controller [ kState ] . pullAgain = true ;
26042636 return ;
@@ -2659,14 +2691,24 @@ function readableStreamDefaultControllerPullSteps(controller, readRequest) {
26592691 if ( controller [ kState ] . closeRequested && ! queue . length ) {
26602692 readableStreamDefaultControllerClearAlgorithms ( controller ) ;
26612693 readableStreamClose ( stream ) ;
2662- } else {
2663- readableStreamDefaultControllerCallPullIfNeeded ( controller ) ;
2694+ } else if ( ! controller [ kState ] . closeRequested &&
2695+ controller [ kState ] . started &&
2696+ controller [ kState ] . highWaterMark -
2697+ controller [ kState ] . queueTotalSize > 0 ) {
2698+ // Reduced ShouldCallPull: the state is known to be readable and the
2699+ // queue was non-empty, so no read requests can be parked.
2700+ readableStreamDefaultControllerPull ( controller ) ;
26642701 }
26652702 readRequest [ kChunk ] ( chunk ) ;
26662703 return ;
26672704 }
26682705 readableStreamAddReadRequest ( stream , readRequest ) ;
2669- readableStreamDefaultControllerCallPullIfNeeded ( controller ) ;
2706+ // Reduced ShouldCallPull: the state is known to be readable, an empty
2707+ // queue in that state implies close has not been requested (close with
2708+ // an empty queue closes the stream immediately), and the read request
2709+ // parked above already satisfies the reader-with-pending-reads arm.
2710+ if ( controller [ kState ] . started )
2711+ readableStreamDefaultControllerPull ( controller ) ;
26702712}
26712713
26722714function setupReadableStreamDefaultController (
0 commit comments