Skip to main content

ferrijs_std/stream_web/readable/
default_controller.rs

1use crate::utils::option::{Null, Undefined};
2use rquickjs::{
3    class::{OwnedBorrow, OwnedBorrowMut, Trace},
4    methods,
5    prelude::{Opt, This},
6    Class, Ctx, Error, Exception, JsLifetime, Object, Promise, Result, Value,
7};
8use std::{future, pin::Pin, rc::Rc};
9
10/// Native async pull: returns Ok(Some(chunk)) or Ok(None) for EOF.
11/// Result of a native pull: data ready, EOF, or need async.
12pub enum NativePullResult<'js> {
13    /// Data chunk ready synchronously
14    Ready(Value<'js>),
15    /// EOF — no more data
16    Eof,
17    /// Need async — returns a future for the pending case
18    Pending(Pin<Box<dyn future::Future<Output = Result<Option<Value<'js>>>> + 'js>>),
19}
20
21pub type NativePullFn<'js> = dyn Fn(&Ctx<'js>) -> Result<NativePullResult<'js>> + 'js;
22
23/// Wrapper satisfying JsLifetime/Trace.
24pub struct NativePull<'js>(pub Rc<NativePullFn<'js>>);
25impl<'js> Clone for NativePull<'js> {
26    fn clone(&self) -> Self {
27        Self(self.0.clone())
28    }
29}
30unsafe impl<'js> JsLifetime<'js> for NativePull<'js> {
31    type Changed<'to> = NativePull<'to>;
32}
33impl<'js> Trace<'js> for NativePull<'js> {
34    fn trace<'a>(&self, _: rquickjs::class::Tracer<'a, 'js>) {}
35}
36
37use crate::stream_web::{
38    queuing_strategy::{SizeAlgorithm, SizeValue},
39    readable::{
40        byte_controller::ReadableByteStreamControllerOwned,
41        controller::{
42            ReadableStreamController, ReadableStreamControllerClass, ReadableStreamControllerOwned,
43        },
44        default_reader::{ReadableStreamDefaultReaderOrUndefined, ReadableStreamReadRequest},
45        objects::{
46            ReadableStreamClassObjects, ReadableStreamDefaultControllerObjects,
47            ReadableStreamDefaultReaderObjects, ReadableStreamObjects,
48        },
49        reader::ReadableStreamReader,
50        stream::{
51            algorithms::{CancelAlgorithm, PullAlgorithm, StartAlgorithm},
52            source::UnderlyingSource,
53            ReadableStream, ReadableStreamClass, ReadableStreamOwned, ReadableStreamState,
54        },
55    },
56    utils::{
57        class_from_owned_borrow_mut,
58        promise::{promise_resolved_with, upon_promise},
59        queue::QueueWithSizes,
60        UnwrapOrUndefined,
61    },
62};
63
64#[derive(JsLifetime, Trace)]
65#[rquickjs::class]
66pub struct ReadableStreamDefaultController<'js> {
67    cancel_algorithm: Option<CancelAlgorithm<'js>>,
68    pub(super) close_requested: bool,
69    pull_again: bool,
70    pull_algorithm: Option<PullAlgorithm<'js>>,
71    pub(crate) pulling: bool,
72    pub(crate) container: QueueWithSizes<'js>,
73    started: bool,
74    strategy_hwm: f64,
75    strategy_size_algorithm: Option<SizeAlgorithm<'js>>,
76    pub(super) stream: ReadableStreamClass<'js>,
77    pub native_pull: Option<NativePull<'js>>,
78    /// Whether this stream was created with `{type: 'owning'}`. Owning streams
79    /// accept a non-empty `transfer` array in `controller.enqueue` and
80    /// structurally transfer each buffer before queueing; non-owning streams
81    /// throw when a non-empty `transfer` list is provided.
82    #[qjs(skip_trace)]
83    pub(super) is_owning_type: bool,
84}
85
86impl<'js> Drop for ReadableStreamDefaultController<'js> {
87    fn drop(&mut self) {
88        self.native_pull = None;
89    }
90}
91
92pub type ReadableStreamDefaultControllerClass<'js> =
93    Class<'js, ReadableStreamDefaultController<'js>>;
94pub(super) type ReadableStreamDefaultControllerOwned<'js> =
95    OwnedBorrowMut<'js, ReadableStreamDefaultController<'js>>;
96
97impl<'js> ReadableStreamDefaultController<'js> {
98    pub(super) fn set_up_readable_stream_default_controller_from_underlying_source(
99        ctx: Ctx<'js>,
100        stream: ReadableStreamOwned<'js>,
101        underlying_source: Null<Undefined<Object<'js>>>,
102        underlying_source_dict: UnderlyingSource<'js>,
103        high_water_mark: f64,
104        size_algorithm: SizeAlgorithm<'js>,
105        is_owning_type: bool,
106    ) -> Result<()> {
107        let (start_algorithm, pull_algorithm, cancel_algorithm) = (
108            // If underlyingSourceDict["start"] exists, then set startAlgorithm to an algorithm which returns the result of invoking underlyingSourceDict["start"] with argument list
109            // « controller » and callback this value underlyingSource.
110            underlying_source_dict
111                .start
112                .map(|f| StartAlgorithm::Function {
113                    f,
114                    underlying_source: underlying_source.clone(),
115                })
116                .unwrap_or(StartAlgorithm::ReturnUndefined),
117            // If underlyingSourceDict["pull"] exists, then set pullAlgorithm to an algorithm which returns the result of invoking underlyingSourceDict["pull"] with argument list
118            // « controller » and callback this value underlyingSource.
119            underlying_source_dict
120                .pull
121                .map(|f| PullAlgorithm::Function {
122                    f,
123                    underlying_source: underlying_source.clone(),
124                })
125                .unwrap_or(PullAlgorithm::ReturnPromiseUndefined),
126            // If underlyingSourceDict["cancel"] exists, then set cancelAlgorithm to an algorithm which takes an argument reason and returns the result of invoking underlyingSourceDict["cancel"] with argument list
127            // « reason » and callback this value underlyingSource.
128            underlying_source_dict
129                .cancel
130                .map(|f| CancelAlgorithm::Function {
131                    f,
132                    underlying_source,
133                })
134                .unwrap_or(CancelAlgorithm::ReturnPromiseUndefined),
135        );
136
137        // Perform ? SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm).
138        Self::set_up_readable_stream_default_controller(
139            ctx.clone(),
140            stream,
141            start_algorithm,
142            pull_algorithm,
143            cancel_algorithm,
144            high_water_mark,
145            size_algorithm,
146            is_owning_type,
147        )?;
148
149        Ok(())
150    }
151
152    #[allow(clippy::too_many_arguments)]
153    pub(super) fn set_up_readable_stream_default_controller(
154        ctx: Ctx<'js>,
155        stream: ReadableStreamOwned<'js>,
156        start_algorithm: StartAlgorithm<'js>,
157        pull_algorithm: PullAlgorithm<'js>,
158        cancel_algorithm: CancelAlgorithm<'js>,
159        high_water_mark: f64,
160        size_algorithm: SizeAlgorithm<'js>,
161        is_owning_type: bool,
162    ) -> Result<Class<'js, Self>> {
163        let (stream_class, mut stream) = class_from_owned_borrow_mut(stream);
164
165        let controller = ReadableStreamDefaultController {
166            // Set controller.[[stream]] to stream.
167            stream: stream_class.clone(),
168
169            // Perform ! ResetQueue(controller).
170            container: QueueWithSizes::new(),
171
172            // Set controller.[[started]], controller.[[closeRequested]], controller.[[pullAgain]], and controller.[[pulling]] to false.
173            started: false,
174            close_requested: false,
175            pull_again: false,
176            pulling: false,
177
178            // Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm and controller.[[strategyHWM]] to highWaterMark.
179            strategy_size_algorithm: Some(size_algorithm),
180            strategy_hwm: high_water_mark,
181
182            // Set controller.[[pullAlgorithm]] to pullAlgorithm.
183            pull_algorithm: Some(pull_algorithm),
184            // Set controller.[[cancelAlgorithm]] to cancelAlgorithm.
185            cancel_algorithm: Some(cancel_algorithm),
186            native_pull: None,
187            is_owning_type,
188        };
189
190        let controller_class = Class::instance(ctx.clone(), controller)?;
191
192        // Set stream.[[controller]] to controller.
193        stream.controller = ReadableStreamControllerClass::ReadableStreamDefaultController(
194            controller_class.clone(),
195        );
196
197        let objects = ReadableStreamObjects::new_default(
198            stream,
199            OwnedBorrowMut::from_class(controller_class),
200        );
201
202        let promise_primordials = objects.stream.promise_primordials.clone();
203
204        // Let startResult be the result of performing startAlgorithm. (This might throw an exception.)
205        let (start_result, objects_class) =
206            Self::start_algorithm(ctx.clone(), objects, start_algorithm)?;
207
208        // Let startPromise be a promise resolved with startResult.
209        let start_promise = promise_resolved_with(&ctx, &promise_primordials, Ok(start_result))?;
210
211        let _ = upon_promise::<Value<'js>, _>(ctx.clone(), start_promise, {
212            let objects_class = objects_class.clone();
213            move |ctx, result| {
214                let mut objects =
215                    ReadableStreamObjects::from_class_no_reader(objects_class).refresh_reader();
216
217                match result {
218                    // Upon fulfillment of startPromise,
219                    Ok(_) => {
220                        // Set controller.[[started]] to true.
221                        objects.controller.started = true;
222                        // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
223                        Self::readable_stream_default_controller_call_pull_if_needed(ctx, objects)?;
224                    },
225                    // Upon rejection of startPromise with reason r,
226                    Err(r) => {
227                        // Perform ! ReadableByteStreamControllerError(controller, r).
228                        Self::readable_stream_default_controller_error(objects, r)?;
229                    },
230                }
231                Ok(())
232            }
233        })?;
234
235        Ok(objects_class.controller)
236    }
237
238    fn readable_stream_default_controller_call_pull_if_needed<
239        R: ReadableStreamDefaultReaderOrUndefined<'js>,
240    >(
241        ctx: Ctx<'js>,
242        objects: ReadableStreamDefaultControllerObjects<'js, R>,
243    ) -> Result<ReadableStreamDefaultControllerObjects<'js, R>> {
244        // Let shouldPull be ! ReadableStreamDefaultControllerShouldCallPull(controller).
245
246        let (should_pull, mut objects) =
247            ReadableStreamDefaultController::readable_stream_default_controller_should_call_pull(
248                objects,
249            );
250
251        // If shouldPull is false, return.
252        if !should_pull {
253            return Ok(objects);
254        }
255
256        // If controller.[[pulling]] is true,
257        if objects.controller.pulling {
258            // Set controller.[[pullAgain]] to true.
259            objects.controller.pull_again = true;
260
261            // Return.
262            return Ok(objects);
263        }
264
265        // Set controller.[[pulling]] to true.
266        objects.controller.pulling = true;
267
268        // Let pullPromise be the result of performing controller.[[pullAlgorithm]].
269        let (pull_promise, objects_class) = Self::pull_algorithm(ctx.clone(), objects)?;
270
271        upon_promise::<Value<'js>, _>(ctx.clone(), pull_promise, {
272            let objects_class = objects_class.clone();
273            move |ctx, result| {
274                let mut objects =
275                    ReadableStreamObjects::from_class_no_reader(objects_class).refresh_reader();
276                match result {
277                    // Upon fulfillment of pullPromise,
278                    Ok(_) => {
279                        // Set controller.[[pulling]] to false.
280                        objects.controller.pulling = false;
281                        // If controller.[[pullAgain]] is true,
282                        if objects.controller.pull_again {
283                            // Set controller.[[pullAgain]] to false.
284                            objects.controller.pull_again = false;
285                            // Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
286                            Self::readable_stream_default_controller_call_pull_if_needed(
287                                ctx, objects,
288                            )?;
289                        };
290                        Ok(())
291                    },
292                    // Upon rejection of pullPromise with reason e,
293                    Err(e) => {
294                        // Perform ! ReadableStreamDefaultControllerError(controller, e).
295                        Self::readable_stream_default_controller_error(objects, e)?;
296                        Ok(())
297                    },
298                }
299            }
300        })?;
301
302        Ok(ReadableStreamObjects::from_class(objects_class))
303    }
304
305    pub(super) fn readable_stream_default_controller_error<R: ReadableStreamReader<'js>>(
306        // Let stream be controller.[[stream]].
307        mut objects: ReadableStreamDefaultControllerObjects<'js, R>,
308        e: Value<'js>,
309    ) -> Result<ReadableStreamDefaultControllerObjects<'js, R>> {
310        // If stream.[[state]] is not "readable", return.
311        if !matches!(objects.stream.state, ReadableStreamState::Readable) {
312            return Ok(objects);
313        };
314
315        // Perform ! ResetQueue(controller).
316        objects.controller.container.reset_queue();
317
318        // Perform ! ReadableStreamDefaultControllerClearAlgorithms(controller).
319        objects
320            .controller
321            .readable_stream_default_controller_clear_algorithms();
322
323        // Perform ! ReadableStreamError(stream, e).
324        ReadableStream::readable_stream_error(objects, e)
325    }
326
327    fn readable_stream_default_controller_should_call_pull<
328        R: ReadableStreamDefaultReaderOrUndefined<'js>,
329    >(
330        mut objects: ReadableStreamDefaultControllerObjects<'js, R>,
331    ) -> (bool, ReadableStreamDefaultControllerObjects<'js, R>) {
332        // Let stream be controller.[[stream]].
333        // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is false, return false.
334        if !objects
335            .controller
336            .readable_stream_default_controller_can_close_or_enqueue(&objects.stream)
337        {
338            return (false, objects);
339        }
340
341        // If controller.[[started]] is false, return false.
342        if !objects.controller.started {
343            return (false, objects);
344        }
345
346        {
347            let mut ret = false;
348            // If ! IsReadableStreamLocked(stream) is true and ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
349            objects = objects
350                .with_some_reader(
351                    |objects| {
352                        if ReadableStream::readable_stream_get_num_read_requests(&objects.reader)
353                            > 0
354                        {
355                            ret = true
356                        }
357                        Ok(objects)
358                    },
359                    Ok,
360                )
361                .unwrap();
362            if ret {
363                return (true, objects);
364            }
365        }
366
367        // Let desiredSize be ! ReadableStreamDefaultControllerGetDesiredSize(controller).
368        let desired_size = objects.controller
369            .readable_stream_default_controller_get_desired_size(&objects.stream)
370            .0
371            .expect(
372            "desiredSize should not be null during ReadableStreamDefaultControllerShouldCallPull",
373        );
374        // If desiredSize > 0, return true.
375        if desired_size > 0.0 {
376            return (true, objects);
377        }
378
379        // Return false.
380        (false, objects)
381    }
382
383    fn readable_stream_default_controller_clear_algorithms(&mut self) {
384        self.pull_algorithm = None;
385        self.cancel_algorithm = None;
386        self.strategy_size_algorithm = None;
387        self.native_pull = None;
388    }
389
390    fn readable_stream_default_controller_can_close_or_enqueue(
391        &self,
392        stream: &ReadableStream<'js>,
393    ) -> bool {
394        // Let state be controller.[[stream]].[[state]].
395        match stream.state {
396            // If controller.[[closeRequested]] is false and state is "readable", return true.
397            ReadableStreamState::Readable if !self.close_requested => true,
398            // Otherwise, return false.
399            _ => false,
400        }
401    }
402
403    pub(crate) fn readable_stream_default_controller_get_desired_size(
404        &self,
405        stream: &ReadableStream<'js>,
406    ) -> Null<f64> {
407        // Let state be controller.[[stream]].[[state]].
408        match stream.state {
409            // If state is "errored", return null.
410            ReadableStreamState::Errored(_) => Null(None),
411            // If state is "closed", return 0.
412            ReadableStreamState::Closed => Null(Some(0.0)),
413            // Return controller.[[strategyHWM]] − controller.[[queueTotalSize]].
414            ReadableStreamState::Readable => {
415                Null(Some(self.strategy_hwm - self.container.queue_total_size))
416            },
417        }
418    }
419
420    pub(super) fn readable_stream_default_controller_close<R: ReadableStreamReader<'js>>(
421        ctx: Ctx<'js>,
422        // Let stream be controller.[[stream]].
423        mut objects: ReadableStreamDefaultControllerObjects<'js, R>,
424    ) -> Result<ReadableStreamDefaultControllerObjects<'js, R>> {
425        // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is false, return.
426        if !objects
427            .controller
428            .readable_stream_default_controller_can_close_or_enqueue(&objects.stream)
429        {
430            return Ok(objects);
431        }
432
433        // Set controller.[[closeRequested]] to true.
434        objects.controller.close_requested = true;
435
436        // If controller.[[queue]] is empty,
437        if objects.controller.container.queue.is_empty() {
438            // Perform ! ReadableStreamDefaultControllerClearAlgorithms(controller).
439            objects
440                .controller
441                .readable_stream_default_controller_clear_algorithms();
442            // Perform ! ReadableStreamClose(stream).
443            objects = ReadableStream::readable_stream_close(ctx, objects)?;
444        }
445
446        Ok(objects)
447    }
448
449    pub(super) fn readable_stream_default_controller_enqueue<
450        R: ReadableStreamDefaultReaderOrUndefined<'js>,
451    >(
452        ctx: Ctx<'js>,
453        // Let stream be controller.[[stream]].
454        mut objects: ReadableStreamDefaultControllerObjects<'js, R>,
455        chunk: Value<'js>,
456    ) -> Result<ReadableStreamDefaultControllerObjects<'js, R>> {
457        // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is false, return.
458        if !objects
459            .controller
460            .readable_stream_default_controller_can_close_or_enqueue(&objects.stream)
461        {
462            return Ok(objects);
463        }
464
465        let mut els = true;
466        // If ! IsReadableStreamLocked(stream) is true and ! ReadableStreamGetNumReadRequests(stream) > 0, perform ! ReadableStreamFulfillReadRequest(stream, chunk, false).
467        objects = objects.with_some_reader(
468            |objects| {
469                if ReadableStream::readable_stream_get_num_read_requests(&objects.reader) > 0 {
470                    els = false;
471                    ReadableStream::readable_stream_fulfill_read_request(
472                        &ctx,
473                        objects,
474                        chunk.clone(),
475                        false,
476                    )
477                } else {
478                    Ok(objects)
479                }
480            },
481            Ok,
482        )?;
483
484        if els {
485            // Let result be the result of performing controller.[[strategySizeAlgorithm]], passing in chunk, and interpreting the result as a completion record.
486            let (result, objects_class) =
487                Self::strategy_size_algorithm(ctx.clone(), objects, chunk.clone());
488
489            objects = ReadableStreamObjects::from_class(objects_class);
490
491            match result {
492                // If result is an abrupt completion,
493                Err(Error::Exception) => {
494                    let err = ctx.catch();
495                    // Perform ! ReadableStreamDefaultControllerError(controller, result.[[Value]]).
496                    Self::readable_stream_default_controller_error(objects, err.clone())?;
497
498                    return Err(ctx.throw(err));
499                },
500                // Let chunkSize be result.[[Value]].
501                Ok(chunk_size) => {
502                    // Let enqueueResult be EnqueueValueWithSize(controller, chunk, chunkSize).
503                    let enqueue_result = objects
504                        .controller
505                        .container
506                        .enqueue_value_with_size(&ctx, chunk, chunk_size);
507
508                    match enqueue_result {
509                        // If enqueueResult is an abrupt completion,
510                        Err(Error::Exception) => {
511                            let err = ctx.catch();
512                            // Perform ! ReadableStreamDefaultControllerError(controller, enqueueResult.[[Value]]).
513                            Self::readable_stream_default_controller_error(objects, err.clone())?;
514                            return Err(ctx.throw(err));
515                        },
516                        Err(err) => return Err(err),
517                        Ok(()) => {},
518                    }
519                },
520                Err(err) => return Err(err),
521            }
522        }
523
524        // Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
525        Self::readable_stream_default_controller_call_pull_if_needed(ctx, objects)
526    }
527
528    fn start_algorithm<R: ReadableStreamReader<'js>>(
529        ctx: Ctx<'js>,
530        objects: ReadableStreamDefaultControllerObjects<'js, R>,
531        start_algorithm: StartAlgorithm<'js>,
532    ) -> Result<(
533        Value<'js>,
534        ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>,
535    )> {
536        let objects_class = objects.into_inner();
537
538        Ok((
539            start_algorithm.call(
540                ctx,
541                ReadableStreamControllerClass::ReadableStreamDefaultController(
542                    objects_class.controller.clone(),
543                ),
544            )?,
545            objects_class,
546        ))
547    }
548
549    fn pull_algorithm<R: ReadableStreamReader<'js>>(
550        ctx: Ctx<'js>,
551        objects: ReadableStreamDefaultControllerObjects<'js, R>,
552    ) -> Result<(
553        Promise<'js>,
554        ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>,
555    )> {
556        let pull_algorithm = objects
557            .controller
558            .pull_algorithm
559            .clone()
560            .expect("pull algorithm used after ReadableStreamDefaultControllerClearAlgorithms");
561        let promise_primordials = objects.stream.promise_primordials.clone();
562        let objects_class = objects.into_inner();
563
564        Ok((
565            pull_algorithm.call(
566                ctx,
567                &promise_primordials,
568                ReadableStreamControllerClass::ReadableStreamDefaultController(
569                    objects_class.controller.clone(),
570                ),
571            )?,
572            objects_class,
573        ))
574    }
575
576    fn strategy_size_algorithm<R: ReadableStreamReader<'js>>(
577        ctx: Ctx<'js>,
578        objects: ReadableStreamDefaultControllerObjects<'js, R>,
579        chunk: Value<'js>,
580    ) -> (
581        Result<SizeValue<'js>>,
582        ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>,
583    ) {
584        let strategy_size_algorithm = objects
585            .controller
586            .strategy_size_algorithm
587            .clone()
588            .expect("size algorithm used after ReadableStreamDefaultControllerClearAlgorithms");
589        let objects_class = objects.into_inner();
590
591        (strategy_size_algorithm.call(ctx, chunk), objects_class)
592    }
593
594    pub(super) fn cancel_algorithm<R: ReadableStreamReader<'js>>(
595        ctx: Ctx<'js>,
596        objects: ReadableStreamDefaultControllerObjects<'js, R>,
597        reason: Value<'js>,
598    ) -> Result<(
599        Promise<'js>,
600        ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>,
601    )> {
602        let cancel_algorithm =
603            objects.controller.cancel_algorithm.clone().expect(
604                "cancel algorithm used after ReadableStreamDefaultControllerClearAlgorithms",
605            );
606        let promise_primordials = objects.stream.promise_primordials.clone();
607        let objects_class = objects.into_inner();
608
609        Ok((
610            cancel_algorithm.call(ctx, &promise_primordials, reason)?,
611            objects_class,
612        ))
613    }
614}
615
616#[methods(rename_all = "camelCase")]
617impl<'js> ReadableStreamDefaultController<'js> {
618    // this is required by web platform tests for unclear reasons
619    fn constructor() -> Self {
620        unimplemented!()
621    }
622
623    #[qjs(constructor)]
624    fn new(ctx: Ctx<'js>) -> Result<Class<'js, Self>> {
625        Err(Exception::throw_type(&ctx, "Illegal constructor"))
626    }
627
628    // readonly attribute unrestricted double? desiredSize;
629    #[qjs(get)]
630    fn desired_size(&self) -> Null<f64> {
631        let stream = OwnedBorrow::from_class(self.stream.clone());
632        self.readable_stream_default_controller_get_desired_size(&stream)
633    }
634
635    // undefined close();
636    fn close(ctx: Ctx<'js>, controller: This<OwnedBorrowMut<'js, Self>>) -> Result<()> {
637        let objects = ReadableStreamObjects::from_default_controller(controller.0);
638
639        // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false, throw a TypeError exception.
640        if !objects
641            .controller
642            .readable_stream_default_controller_can_close_or_enqueue(&objects.stream)
643        {
644            return Err(Exception::throw_type(
645                &ctx,
646                "The stream is not in a state that permits close",
647            ));
648        }
649
650        // Perform ! ReadableStreamDefaultControllerClose(this).
651        Self::readable_stream_default_controller_close(ctx, objects)?;
652        Ok(())
653    }
654
655    // undefined enqueue(optional any chunk, optional ReadableStreamEnqueueOptions options = {});
656    fn enqueue(
657        ctx: Ctx<'js>,
658        controller: This<OwnedBorrowMut<'js, Self>>,
659        chunk: Opt<Value<'js>>,
660        options: Opt<Value<'js>>,
661    ) -> Result<()> {
662        // Handle the `transfer` option per the `type: 'owning'` ReadableStream
663        // proposal (WPT `streams/readable-streams/owning-type`). The option
664        // is only meaningful on owning-type streams; any other stream throws
665        // `TypeError` if the caller passes a non-empty transfer list.
666        //
667        // WebIDL getter semantics apply: property access must propagate.
668        let mut transfer_list: Option<rquickjs::Array<'js>> = None;
669        if let Some(opts) = options.0.as_ref().and_then(|v| v.as_object()) {
670            transfer_list = opts.get::<_, Option<rquickjs::Array<'js>>>("transfer")?;
671        }
672        let has_transfer_items = transfer_list.as_ref().is_some_and(|arr| !arr.is_empty());
673        if has_transfer_items && !controller.is_owning_type {
674            return Err(Exception::throw_type(&ctx, "transfer list is not empty"));
675        }
676        // Detach each buffer in the transfer list (owning-type streams). Uses
677        // JS `ArrayBuffer.prototype.transfer()` which returns a new buffer
678        // with the same bytes and detaches the original. We re-bind the
679        // chunk to the new buffer if it was the same reference.
680        let chunk_value = chunk.0.clone().unwrap_or_undefined(&ctx);
681        let transferred_chunk = if has_transfer_items && controller.is_owning_type {
682            transfer_owning_chunk(&ctx, chunk_value.clone(), &transfer_list.unwrap())?
683        } else {
684            chunk_value
685        };
686
687        let objects = ReadableStreamObjects::from_default_controller(controller.0);
688
689        // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false, throw a TypeError exception.
690        if !objects
691            .controller
692            .readable_stream_default_controller_can_close_or_enqueue(&objects.stream)
693        {
694            return Err(Exception::throw_type(
695                &ctx,
696                "The stream is not in a state that permits enqueue",
697            ));
698        }
699
700        objects.with_reader(
701            |objects| {
702                // Perform ? ReadableStreamDefaultControllerEnqueue(this, chunk).
703                Self::readable_stream_default_controller_enqueue(
704                    ctx.clone(),
705                    objects,
706                    transferred_chunk.clone(),
707                )
708            },
709            |_| panic!("Default controller must not have byob reader"),
710            |objects| {
711                // Perform ? ReadableStreamDefaultControllerEnqueue(this, chunk).
712                Self::readable_stream_default_controller_enqueue(
713                    ctx.clone(),
714                    objects,
715                    transferred_chunk.clone(),
716                )
717            },
718        )?;
719
720        Ok(())
721    }
722
723    // undefined error(optional any e);
724    fn error(
725        ctx: Ctx<'js>,
726        controller: This<OwnedBorrowMut<'js, Self>>,
727        e: Opt<Value<'js>>,
728    ) -> Result<()> {
729        let objects = ReadableStreamObjects::from_default_controller(controller.0);
730
731        // Perform ! ReadableStreamDefaultControllerError(this, e).
732        Self::readable_stream_default_controller_error(objects, e.0.unwrap_or_undefined(&ctx))?;
733        Ok(())
734    }
735}
736
737impl<'js> ReadableStreamController<'js> for ReadableStreamDefaultControllerOwned<'js> {
738    type Class = ReadableStreamDefaultControllerClass<'js>;
739
740    fn with_controller<C, O>(
741        self,
742        ctx: C,
743        default: impl FnOnce(
744            C,
745            ReadableStreamDefaultControllerOwned<'js>,
746        ) -> Result<(O, ReadableStreamDefaultControllerOwned<'js>)>,
747        _: impl FnOnce(
748            C,
749            ReadableByteStreamControllerOwned<'js>,
750        ) -> Result<(O, ReadableByteStreamControllerOwned<'js>)>,
751    ) -> Result<(O, Self)> {
752        let (ctx, reader) = default(ctx, self)?;
753        Ok((ctx, reader))
754    }
755
756    fn into_inner(self) -> Self::Class {
757        OwnedBorrowMut::into_inner(self)
758    }
759
760    fn from_class(class: Self::Class) -> Self {
761        OwnedBorrowMut::from_class(class)
762    }
763
764    fn into_erased(self) -> ReadableStreamControllerOwned<'js> {
765        ReadableStreamControllerOwned::ReadableStreamDefaultController(self)
766    }
767
768    fn try_from_erased(erased: ReadableStreamControllerOwned<'js>) -> Option<Self> {
769        match erased {
770            ReadableStreamControllerOwned::ReadableStreamDefaultController(r) => Some(r),
771            ReadableStreamControllerOwned::ReadableStreamByteController(_) => None,
772        }
773    }
774
775    fn pull_steps(
776        ctx: &Ctx<'js>,
777        mut objects: ReadableStreamDefaultReaderObjects<'js, Self>,
778        read_request: impl ReadableStreamReadRequest<'js> + 'js,
779    ) -> Result<ReadableStreamDefaultReaderObjects<'js, Self>> {
780        // If this.[[queue]] is not empty,
781        if !objects.controller.container.queue.is_empty() {
782            // Let chunk be ! DequeueValue(this).
783            let chunk = objects.controller.container.dequeue_value();
784            // If this.[[closeRequested]] is true and this.[[queue]] is empty,
785            if objects.controller.close_requested && objects.controller.container.queue.is_empty() {
786                // Perform ! ReadableStreamDefaultControllerClearAlgorithms(this).
787                objects
788                    .controller
789                    .readable_stream_default_controller_clear_algorithms();
790                // Perform ! ReadableStreamClose(stream).
791                objects = ReadableStream::readable_stream_close(ctx.clone(), objects)?;
792            } else {
793                // Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
794                objects =
795                    ReadableStreamDefaultController::readable_stream_default_controller_call_pull_if_needed(
796                        ctx.clone(),
797                        objects,
798                    )?;
799            }
800
801            // Perform readRequest’s chunk steps, given chunk.
802            read_request.chunk_steps_typed(objects, chunk)
803        } else {
804            // Otherwise,
805            // Perform ! ReadableStreamAddReadRequest(stream, readRequest).
806            objects
807                .stream
808                .readable_stream_add_read_request(&mut objects.reader, read_request);
809            // Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
810
811            ReadableStreamDefaultController::readable_stream_default_controller_call_pull_if_needed(
812                ctx.clone(),
813                objects,
814            )
815        }
816    }
817
818    fn cancel_steps<R: ReadableStreamReader<'js>>(
819        ctx: &Ctx<'js>,
820        mut objects: ReadableStreamObjects<'js, Self, R>,
821        reason: Value<'js>,
822    ) -> Result<(Promise<'js>, ReadableStreamObjects<'js, Self, R>)> {
823        // Perform ! ResetQueue(this).
824        objects.controller.container.reset_queue();
825
826        // Let result be the result of performing this.[[cancelAlgorithm]], passing reason.
827        let (result, objects_class) =
828            ReadableStreamDefaultController::cancel_algorithm(ctx.clone(), objects, reason)?;
829
830        objects = ReadableStreamObjects::from_class(objects_class);
831        // Perform ! ReadableStreamDefaultControllerClearAlgorithms(this).
832        objects
833            .controller
834            .readable_stream_default_controller_clear_algorithms();
835
836        // Return result.
837        Ok((result, objects))
838    }
839
840    fn release_steps(&mut self) {}
841}
842
843/// Public API for enqueuing data into a default controller from Rust code
844pub fn readable_stream_default_controller_enqueue_value<'js>(
845    ctx: Ctx<'js>,
846    controller: ReadableStreamDefaultControllerClass<'js>,
847    chunk: Value<'js>,
848) -> Result<()> {
849    let objects =
850        ReadableStreamObjects::from_default_controller(OwnedBorrowMut::from_class(controller));
851
852    if !objects
853        .controller
854        .readable_stream_default_controller_can_close_or_enqueue(&objects.stream)
855    {
856        return Ok(()); // Silently ignore if can't enqueue
857    }
858
859    objects.with_reader(
860        |objects| {
861            ReadableStreamDefaultController::readable_stream_default_controller_enqueue(
862                ctx.clone(),
863                objects,
864                chunk.clone(),
865            )
866        },
867        |_| panic!("Default controller must not have byob reader"),
868        |objects| {
869            ReadableStreamDefaultController::readable_stream_default_controller_enqueue(
870                ctx.clone(),
871                objects,
872                chunk.clone(),
873            )
874        },
875    )?;
876
877    Ok(())
878}
879
880/// Public API for closing a default controller from Rust code
881pub fn readable_stream_default_controller_close_stream<'js>(
882    ctx: Ctx<'js>,
883    controller: ReadableStreamDefaultControllerClass<'js>,
884) -> Result<()> {
885    let objects =
886        ReadableStreamObjects::from_default_controller(OwnedBorrowMut::from_class(controller));
887
888    if !objects
889        .controller
890        .readable_stream_default_controller_can_close_or_enqueue(&objects.stream)
891    {
892        return Ok(());
893    }
894
895    ReadableStreamDefaultController::readable_stream_default_controller_close(ctx, objects)?;
896    Ok(())
897}
898
899/// Public API for erroring a default controller from Rust code
900pub fn readable_stream_default_controller_error_stream<'js>(
901    controller: ReadableStreamDefaultControllerClass<'js>,
902    error: Value<'js>,
903) -> Result<()> {
904    let objects =
905        ReadableStreamObjects::from_default_controller(OwnedBorrowMut::from_class(controller));
906
907    objects.with_reader(
908        |objects| {
909            ReadableStreamDefaultController::readable_stream_default_controller_error(
910                objects,
911                error.clone(),
912            )
913        },
914        |_| panic!("Default controller must not have byob reader"),
915        |objects| {
916            ReadableStreamDefaultController::readable_stream_default_controller_error(
917                objects,
918                error.clone(),
919            )
920        },
921    )?;
922
923    Ok(())
924}
925
926/// Structurally transfer each `ArrayBuffer` in `transfer_list` (detaches the
927/// original) and, if `chunk` references the same buffer, rebind it to the
928/// transferred copy. Called for `controller.enqueue(chunk, { transfer })` on
929/// `type: 'owning'` ReadableStreams.
930fn transfer_owning_chunk<'js>(
931    ctx: &Ctx<'js>,
932    chunk: Value<'js>,
933    transfer_list: &rquickjs::Array<'js>,
934) -> Result<Value<'js>> {
935    use rquickjs::ArrayBuffer;
936    let mut chunk_replacement: Option<Value<'js>> = None;
937    for v in transfer_list.iter::<Value<'js>>() {
938        let v = v?;
939        let Some(ab) = ArrayBuffer::from_value(v.clone()) else {
940            return Err(rquickjs::Exception::throw_type(
941                ctx,
942                "transfer list item is not an ArrayBuffer",
943            ));
944        };
945        // JS object identity: if this transfer-list entry IS the chunk
946        // itself, record that we need to replace the chunk with the
947        // transferred copy. Compare before calling transfer() (which
948        // detaches the buffer).
949        let is_chunk = chunk == v;
950        // Use JS `ArrayBuffer.prototype.transfer()` which returns a new
951        // buffer of the same byteLength and detaches the original.
952        let transfer_fn: rquickjs::Function<'js> = ab.as_object().get("transfer")?;
953        let new_buf: Value<'js> = transfer_fn.call((rquickjs::function::This(ab.clone()),))?;
954        if is_chunk && chunk_replacement.is_none() {
955            chunk_replacement = Some(new_buf);
956        }
957    }
958    Ok(chunk_replacement.unwrap_or(chunk))
959}