Skip to main content

script/dom/stream/
writablestreamdefaultcontroller.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, RefCell};
6use std::ptr;
7use std::rc::Rc;
8
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use js::jsapi::{Heap, JSObject};
12use js::jsval::{JSVal, UndefinedValue};
13use js::realm::CurrentRealm;
14use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
15use script_bindings::reflector::{Reflector, reflect_dom_object};
16
17use crate::dom::bindings::callback::{ExceptionHandling, RootedCallback, TracedCallback};
18use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategySize;
19use crate::dom::bindings::codegen::Bindings::UnderlyingSinkBinding::{
20    UnderlyingSinkAbortCallback, UnderlyingSinkCloseCallback, UnderlyingSinkStartCallback,
21    UnderlyingSinkWriteCallback,
22};
23use crate::dom::bindings::codegen::Bindings::WritableStreamDefaultControllerBinding::WritableStreamDefaultControllerMethods;
24use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
25use crate::dom::bindings::reflector::DomGlobal;
26use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::messageport::MessagePort;
29use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
30use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
31use crate::dom::readablestreamdefaultcontroller::{EnqueuedValue, QueueWithSizes, ValueWithSize};
32use crate::dom::stream::writablestream::WritableStream;
33use crate::dom::types::{AbortController, AbortSignal, TransformStream};
34use crate::realms::enter_auto_realm;
35
36impl js::gc::Rootable for CloseAlgorithmFulfillmentHandler {}
37
38/// The fulfillment handler for
39/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close>
40#[derive(Clone, JSTraceable, MallocSizeOf)]
41#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
42struct CloseAlgorithmFulfillmentHandler {
43    stream: Dom<WritableStream>,
44}
45
46impl Callback for CloseAlgorithmFulfillmentHandler {
47    fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
48        let stream = self.stream.as_rooted();
49
50        // Perform ! WritableStreamFinishInFlightClose(stream).
51        stream.finish_in_flight_close(cx);
52    }
53}
54
55impl js::gc::Rootable for CloseAlgorithmRejectionHandler {}
56
57/// The rejection handler for
58/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close>
59#[derive(Clone, JSTraceable, MallocSizeOf)]
60#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
61struct CloseAlgorithmRejectionHandler {
62    stream: Dom<WritableStream>,
63}
64
65impl Callback for CloseAlgorithmRejectionHandler {
66    fn callback(&self, cx: &mut CurrentRealm, v: SafeHandleValue) {
67        let stream = self.stream.as_rooted();
68
69        let global = GlobalScope::from_current_realm(cx);
70
71        // Perform ! WritableStreamFinishInFlightCloseWithError(stream, reason).
72        stream.finish_in_flight_close_with_error(cx, &global, v);
73    }
74}
75
76impl js::gc::Rootable for StartAlgorithmFulfillmentHandler {}
77
78/// The fulfillment handler for
79/// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
80#[derive(Clone, JSTraceable, MallocSizeOf)]
81#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
82struct StartAlgorithmFulfillmentHandler {
83    controller: Dom<WritableStreamDefaultController>,
84}
85
86impl Callback for StartAlgorithmFulfillmentHandler {
87    /// Continuation of <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
88    /// Upon fulfillment of startPromise,
89    fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
90        let controller = self.controller.as_rooted();
91        let stream = controller
92            .stream
93            .get()
94            .expect("Controller should have a stream.");
95
96        // Assert: stream.[[state]] is "writable" or "erroring".
97        assert!(stream.is_erroring() || stream.is_writable());
98
99        // Set controller.[[started]] to true.
100        controller.started.set(true);
101
102        let global = GlobalScope::from_current_realm(cx);
103
104        // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
105        controller.advance_queue_if_needed(cx, &global)
106    }
107}
108
109impl js::gc::Rootable for StartAlgorithmRejectionHandler {}
110
111/// The rejection handler for
112/// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
113#[derive(Clone, JSTraceable, MallocSizeOf)]
114#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
115struct StartAlgorithmRejectionHandler {
116    controller: Dom<WritableStreamDefaultController>,
117}
118
119impl Callback for StartAlgorithmRejectionHandler {
120    /// Continuation of <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
121    /// Upon rejection of startPromise with reason r,
122    fn callback(&self, cx: &mut CurrentRealm, v: SafeHandleValue) {
123        let controller = self.controller.as_rooted();
124        let stream = controller
125            .stream
126            .get()
127            .expect("Controller should have a stream.");
128
129        // Assert: stream.[[state]] is "writable" or "erroring".
130        assert!(stream.is_erroring() || stream.is_writable());
131
132        // Set controller.[[started]] to true.
133        controller.started.set(true);
134
135        let global = GlobalScope::from_current_realm(cx);
136
137        // Perform ! WritableStreamDealWithRejection(stream, r).
138        stream.deal_with_rejection(cx, &global, v);
139    }
140}
141
142impl js::gc::Rootable for TransferBackPressurePromiseReaction {}
143
144/// Reacting to backpressurePromise as part of the `writeAlgorithm` of
145/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
146#[derive(JSTraceable, MallocSizeOf)]
147#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
148struct TransferBackPressurePromiseReaction {
149    /// The result of reacting to backpressurePromise.
150    result_promise: TracedPromise,
151
152    /// The backpressurePromise.
153    #[ignore_malloc_size_of = "nested Rc"]
154    backpressure_promise: Rc<RefCell<Option<TracedPromise>>>,
155
156    /// The chunk received by the `writeAlgorithm`.
157    #[ignore_malloc_size_of = "mozjs"]
158    chunk: Box<Heap<JSVal>>,
159
160    /// The port used in the algorithm.
161    port: Dom<MessagePort>,
162}
163
164impl Callback for TransferBackPressurePromiseReaction {
165    /// Reacting to backpressurePromise with the following fulfillment steps:
166    fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
167        let global = self.result_promise.global();
168        // Set backpressurePromise to a new promise.
169        let promise = Promise::new(cx, &global);
170        *self.backpressure_promise.borrow_mut() = Some(promise.to_traced());
171
172        // Let result be PackAndPostMessageHandlingError(port, "chunk", chunk).
173        rooted!(&in(cx) let mut chunk = UndefinedValue());
174        chunk.set(self.chunk.get());
175        let result = self
176            .port
177            .pack_and_post_message_handling_error(cx, "chunk", chunk.handle());
178
179        // If result is an abrupt completion,
180        if let Err(error) = result {
181            // Disentangle port.
182            global.disentangle_port(cx, &self.port);
183
184            // Return a promise rejected with result.[[Value]].
185            self.result_promise.reject_error(cx, error);
186        } else {
187            // Otherwise, return a promise resolved with undefined.
188            self.result_promise.resolve_native(cx, &());
189        }
190    }
191}
192
193impl js::gc::Rootable for WriteAlgorithmFulfillmentHandler {}
194
195/// The fulfillment handler for
196/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write>
197#[derive(Clone, JSTraceable, MallocSizeOf)]
198#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
199struct WriteAlgorithmFulfillmentHandler {
200    controller: Dom<WritableStreamDefaultController>,
201}
202
203impl Callback for WriteAlgorithmFulfillmentHandler {
204    fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
205        let controller = self.controller.as_rooted();
206        let stream = controller
207            .stream
208            .get()
209            .expect("Controller should have a stream.");
210
211        // Perform ! WritableStreamFinishInFlightWrite(stream).
212        stream.finish_in_flight_write(cx);
213
214        // Let state be stream.[[state]].
215        // Assert: state is "writable" or "erroring".
216        assert!(stream.is_erroring() || stream.is_writable());
217
218        // Perform ! DequeueValue(controller).
219        rooted!(&in(cx) let mut rval = UndefinedValue());
220        controller.queue.dequeue_value(cx, Some(rval.handle_mut()));
221
222        let global = GlobalScope::from_current_realm(cx);
223
224        // If ! WritableStreamCloseQueuedOrInFlight(stream) is false and state is "writable",
225        if !stream.close_queued_or_in_flight() && stream.is_writable() {
226            // Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller).
227            let backpressure = controller.get_backpressure();
228
229            // Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
230            stream.update_backpressure(cx, backpressure, &global);
231        }
232
233        // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
234        controller.advance_queue_if_needed(cx, &global)
235    }
236}
237
238impl js::gc::Rootable for WriteAlgorithmRejectionHandler {}
239
240/// The rejection handler for
241/// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write>
242#[derive(Clone, JSTraceable, MallocSizeOf)]
243#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
244struct WriteAlgorithmRejectionHandler {
245    controller: Dom<WritableStreamDefaultController>,
246}
247
248impl Callback for WriteAlgorithmRejectionHandler {
249    fn callback(&self, cx: &mut CurrentRealm, v: SafeHandleValue) {
250        let controller = self.controller.as_rooted();
251        let stream = controller
252            .stream
253            .get()
254            .expect("Controller should have a stream.");
255
256        // If stream.[[state]] is "writable",
257        if stream.is_writable() {
258            // perform ! WritableStreamDefaultControllerClearAlgorithms(controller).
259            controller.clear_algorithms();
260        }
261
262        let global = GlobalScope::from_current_realm(cx);
263
264        // Perform ! WritableStreamFinishInFlightWriteWithError(stream, reason).
265        stream.finish_in_flight_write_with_error(cx, &global, v);
266    }
267}
268
269/// The type of sink algorithms we are using.
270#[derive(JSTraceable, PartialEq)]
271#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
272pub enum UnderlyingSinkType {
273    /// Algorithms are provided by Js callbacks.
274    Js {
275        /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-abortalgorithm>
276        abort: RefCell<Option<TracedCallback<UnderlyingSinkAbortCallback>>>,
277
278        start: RefCell<Option<TracedCallback<UnderlyingSinkStartCallback>>>,
279
280        /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-closealgorithm>
281        close: RefCell<Option<TracedCallback<UnderlyingSinkCloseCallback>>>,
282
283        /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-writealgorithm>
284        write: RefCell<Option<TracedCallback<UnderlyingSinkWriteCallback>>>,
285    },
286    /// Algorithms supporting streams transfer are implemented in Rust.
287    /// The promise and port used in those algorithms are stored here.
288    Transfer {
289        backpressure_promise: Rc<RefCell<Option<TracedPromise>>>,
290        port: Dom<MessagePort>,
291    },
292    /// Algorithms supporting transform streams are implemented in Rust.
293    Transform(Dom<TransformStream>, TracedPromise),
294}
295
296/// The type of sink algorithms we are using.
297pub(crate) enum UnderlyingSinkTypeRef<'a> {
298    /// Algorithms are provided by Js callbacks.
299    Js {
300        /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-abortalgorithm>
301        abort: Option<&'a TracedCallback<UnderlyingSinkAbortCallback>>,
302
303        start: Option<&'a TracedCallback<UnderlyingSinkStartCallback>>,
304
305        /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-closealgorithm>
306        close: Option<&'a TracedCallback<UnderlyingSinkCloseCallback>>,
307
308        /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-writealgorithm>
309        write: Option<&'a TracedCallback<UnderlyingSinkWriteCallback>>,
310    },
311    /// Algorithms supporting streams transfer are implemented in Rust.
312    /// The promise and port used in those algorithms are stored here.
313    Transfer {
314        backpressure_promise: &'a Rc<RefCell<Option<TracedPromise>>>,
315        port: &'a MessagePort,
316    },
317    /// Algorithms supporting transform streams are implemented in Rust.
318    Transform(&'a TransformStream, &'a RootedPromise),
319}
320
321impl<'a> UnderlyingSinkTypeRef<'a> {
322    pub(crate) fn new_js(
323        abort: Option<&'a TracedCallback<UnderlyingSinkAbortCallback>>,
324        start: Option<&'a TracedCallback<UnderlyingSinkStartCallback>>,
325        close: Option<&'a TracedCallback<UnderlyingSinkCloseCallback>>,
326        write: Option<&'a TracedCallback<UnderlyingSinkWriteCallback>>,
327    ) -> Self {
328        UnderlyingSinkTypeRef::Js {
329            abort,
330            start,
331            close,
332            write,
333        }
334    }
335}
336
337impl From<UnderlyingSinkTypeRef<'_>> for UnderlyingSinkType {
338    fn from(sink_type: UnderlyingSinkTypeRef<'_>) -> Self {
339        match sink_type {
340            UnderlyingSinkTypeRef::Js {
341                abort,
342                start,
343                close,
344                write,
345            } => UnderlyingSinkType::Js {
346                abort: RefCell::new(abort.cloned()),
347                start: RefCell::new(start.cloned()),
348                close: RefCell::new(close.cloned()),
349                write: RefCell::new(write.cloned()),
350            },
351            UnderlyingSinkTypeRef::Transfer {
352                backpressure_promise,
353                port,
354            } => UnderlyingSinkType::Transfer {
355                backpressure_promise: backpressure_promise.clone(),
356                port: Dom::from_ref(port),
357            },
358            UnderlyingSinkTypeRef::Transform(stream, promise) => {
359                UnderlyingSinkType::Transform(Dom::from_ref(stream), promise.to_traced())
360            },
361        }
362    }
363}
364
365/// <https://streams.spec.whatwg.org/#ws-default-controller-class>
366#[dom_struct]
367pub struct WritableStreamDefaultController {
368    reflector_: Reflector,
369
370    /// The type of underlying sink used. Besides the default JS one,
371    /// there will be others for stream transfer, and for transform stream.
372    #[ignore_malloc_size_of = "underlying_sink_type"]
373    underlying_sink_type: UnderlyingSinkType,
374
375    /// The JS object used as `this` when invoking sink algorithms.
376    #[ignore_malloc_size_of = "mozjs"]
377    underlying_sink_obj: Heap<*mut JSObject>,
378
379    /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-queue>
380    queue: QueueWithSizes,
381
382    /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-started>
383    started: Cell<bool>,
384
385    /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-strategyhwm>
386    strategy_hwm: f64,
387
388    /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-strategysizealgorithm>
389    #[ignore_malloc_size_of = "QueuingStrategySize"]
390    strategy_size: RefCell<Option<TracedCallback<QueuingStrategySize>>>,
391
392    /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-stream>
393    stream: MutNullableDom<WritableStream>,
394
395    /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-abortcontroller>
396    abort_controller: Dom<AbortController>,
397}
398
399impl WritableStreamDefaultController {
400    /// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink>
401    fn new_inherited(
402        underlying_sink_type: UnderlyingSinkTypeRef<'_>,
403        strategy_hwm: f64,
404        strategy_size: RootedCallback<QueuingStrategySize>,
405        abort_controller: &AbortController,
406    ) -> WritableStreamDefaultController {
407        WritableStreamDefaultController {
408            reflector_: Reflector::new(),
409            underlying_sink_type: underlying_sink_type.into(),
410            queue: Default::default(),
411            stream: Default::default(),
412            underlying_sink_obj: Default::default(),
413            strategy_hwm,
414            strategy_size: RefCell::new(Some(strategy_size.to_traced())),
415            started: Default::default(),
416            abort_controller: Dom::from_ref(abort_controller),
417        }
418    }
419
420    pub(crate) fn new(
421        cx: &mut JSContext,
422        global: &GlobalScope,
423        underlying_sink_type: UnderlyingSinkTypeRef<'_>,
424        strategy_hwm: f64,
425        strategy_size: RootedCallback<QueuingStrategySize>,
426    ) -> DomRoot<WritableStreamDefaultController> {
427        let abort_controller = AbortController::new_with_proto(cx, global, None);
428        let obj = Box::new(WritableStreamDefaultController::new_inherited(
429            underlying_sink_type,
430            strategy_hwm,
431            strategy_size,
432            &abort_controller,
433        ));
434        reflect_dom_object(cx, obj, global)
435    }
436
437    pub(crate) fn started(&self) -> bool {
438        self.started.get()
439    }
440
441    /// Setting the JS object after the heap has settled down.
442    pub(crate) fn set_underlying_sink_this_object(&self, this_object: SafeHandleObject) {
443        self.underlying_sink_obj.set(*this_object);
444    }
445
446    /// "Signal abort" call from <https://streams.spec.whatwg.org/#writable-stream-abort>
447    pub(crate) fn signal_abort(&self, cx: &mut CurrentRealm, reason: SafeHandleValue) {
448        self.abort_controller.signal_abort(cx, reason);
449    }
450
451    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-clear-algorithms>
452    fn clear_algorithms(&self) {
453        match &self.underlying_sink_type {
454            UnderlyingSinkType::Js {
455                abort,
456                start: _,
457                close,
458                write,
459            } => {
460                // Set controller.[[writeAlgorithm]] to undefined.
461                write.borrow_mut().take();
462
463                // Set controller.[[closeAlgorithm]] to undefined.
464                close.borrow_mut().take();
465
466                // Set controller.[[abortAlgorithm]] to undefined.
467                abort.borrow_mut().take();
468            },
469            UnderlyingSinkType::Transfer {
470                backpressure_promise,
471                ..
472            } => {
473                backpressure_promise.borrow_mut().take();
474            },
475            UnderlyingSinkType::Transform(_, _) => {
476                return;
477            },
478        }
479
480        // Set controller.[[strategySizeAlgorithm]] to undefined.
481        self.strategy_size.borrow_mut().take();
482    }
483
484    /// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
485    pub(crate) fn setup(
486        &self,
487        cx: &mut JSContext,
488        global: &GlobalScope,
489        stream: &WritableStream,
490    ) -> Result<(), Error> {
491        // Assert: stream implements WritableStream.
492        // Implied by stream type.
493
494        // Assert: stream.[[controller]] is undefined.
495        stream.assert_no_controller();
496
497        // Set controller.[[stream]] to stream.
498        self.stream.set(Some(stream));
499
500        // Set stream.[[controller]] to controller.
501        stream.set_default_controller(self);
502
503        // Perform ! ResetQueue(controller).
504
505        // Set controller.[[abortController]] to a new AbortController.
506
507        // Set controller.[[started]] to false.
508
509        // Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm.
510
511        // Set controller.[[strategyHWM]] to highWaterMark.
512
513        // Set controller.[[writeAlgorithm]] to writeAlgorithm.
514
515        // Set controller.[[closeAlgorithm]] to closeAlgorithm.
516
517        // Set controller.[[abortAlgorithm]] to abortAlgorithm.
518
519        // Note: above steps are done in `new_inherited`.
520
521        // Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller).
522        let backpressure = self.get_backpressure();
523
524        // Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
525        stream.update_backpressure(cx, backpressure, global);
526
527        // Let startResult be the result of performing startAlgorithm. (This may throw an exception.)
528        // Let startPromise be a promise resolved with startResult.
529        let start_promise = self.start_algorithm(cx, global)?;
530
531        let rooted_default_controller = DomRoot::from_ref(self);
532
533        // Upon fulfillment of startPromise,
534        rooted!(&in(cx) let mut fulfillment_handler = Some(StartAlgorithmFulfillmentHandler {
535            controller: Dom::from_ref(&rooted_default_controller),
536        }));
537
538        // Upon rejection of startPromise with reason r,
539        rooted!(&in(cx) let mut rejection_handler = Some(StartAlgorithmRejectionHandler {
540            controller: Dom::from_ref(&rooted_default_controller),
541        }));
542
543        let handler = PromiseNativeHandler::new(
544            cx,
545            global,
546            fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
547            rejection_handler.take().map(|h| Box::new(h) as Box<_>),
548        );
549        let mut realm = enter_auto_realm(cx, global);
550        let cx = &mut realm.current_realm();
551        start_promise.append_native_handler(cx, &handler);
552
553        Ok(())
554    }
555
556    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-close>
557    pub(crate) fn close(&self, cx: &mut JSContext, global: &GlobalScope) {
558        // Perform ! EnqueueValueWithSize(controller, close sentinel, 0).
559        self.queue
560            .enqueue_value_with_size(EnqueuedValue::CloseSentinel)
561            .expect("Enqueuing the close sentinel should not fail.");
562        // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
563        self.advance_queue_if_needed(cx, global);
564    }
565
566    fn start_algorithm(&self, cx: &mut JSContext, global: &GlobalScope) -> Fallible<RootedPromise> {
567        match &self.underlying_sink_type {
568            UnderlyingSinkType::Js {
569                start,
570                abort: _,
571                close: _,
572                write: _,
573            } => {
574                rooted!(&in(cx) let algo = start.borrow().clone());
575                let start_promise = if let Some(ref start) = *algo {
576                    rooted!(&in(cx) let mut result: JSVal);
577                    rooted!(&in(cx) let this_object = self.underlying_sink_obj.get());
578                    start.Call_(
579                        cx,
580                        &this_object.handle(),
581                        self,
582                        result.handle_mut(),
583                        ExceptionHandling::Rethrow,
584                    )?;
585                    Promise::resolve_or_wrap_promise(cx, result.handle(), global)
586                } else {
587                    // Let startAlgorithm be an algorithm that returns undefined.
588                    Promise::new_resolved(cx, global, ())
589                };
590
591                Ok(start_promise)
592            },
593            UnderlyingSinkType::Transfer { .. } => {
594                // Let startAlgorithm be an algorithm that returns undefined.
595                Ok(Promise::new_resolved(cx, global, ()))
596            },
597            UnderlyingSinkType::Transform(_, start_promise) => {
598                // Let startAlgorithm be an algorithm that returns startPromise.
599                Ok(start_promise.root(cx))
600            },
601        }
602    }
603
604    /// <https://streams.spec.whatwg.org/#ref-for-abstract-opdef-writablestreamcontroller-abortsteps>
605    pub(crate) fn abort_steps(
606        &self,
607        cx: &mut JSContext,
608        global: &GlobalScope,
609        reason: SafeHandleValue,
610    ) -> RootedPromise {
611        let result = match &self.underlying_sink_type {
612            UnderlyingSinkType::Js {
613                abort,
614                start: _,
615                close: _,
616                write: _,
617            } => {
618                rooted!(&in(cx) let this_object = self.underlying_sink_obj.get());
619                rooted!(&in(cx) let algo = abort.borrow().clone());
620                // Let result be the result of performing this.[[abortAlgorithm]], passing reason.
621                let result = if let Some(ref algo) = *algo {
622                    algo.Call_(
623                        cx,
624                        &this_object.handle(),
625                        Some(reason),
626                        ExceptionHandling::Rethrow,
627                    )
628                } else {
629                    Ok(Promise::new_resolved(cx, global, ()))
630                };
631                result.unwrap_or_else(|e| {
632                    let promise = Promise::new(cx, global);
633                    promise.reject_error(cx, e);
634                    promise
635                })
636            },
637            UnderlyingSinkType::Transfer { port, .. } => {
638                // The steps from the `abortAlgorithm` at
639                // <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
640
641                // Let result be PackAndPostMessageHandlingError(port, "error", reason).
642                let result = port.pack_and_post_message_handling_error(cx, "error", reason);
643
644                // Disentangle port.
645                global.disentangle_port(cx, port);
646
647                let promise = Promise::new(cx, global);
648
649                // If result is an abrupt completion, return a promise rejected with result.[[Value]]
650                if let Err(error) = result {
651                    promise.reject_error(cx, error);
652                } else {
653                    // Otherwise, return a promise resolved with undefined.
654                    promise.resolve_native(cx, &());
655                }
656                promise
657            },
658            UnderlyingSinkType::Transform(stream, _) => {
659                // Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, reason).
660                stream
661                    .transform_stream_default_sink_abort_algorithm(cx, global, reason)
662                    .expect("Transform stream default sink abort algorithm should not fail.")
663            },
664        };
665
666        // Perform ! WritableStreamDefaultControllerClearAlgorithms(controller).
667        self.clear_algorithms();
668
669        result
670    }
671
672    /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-writealgorithm>
673    fn call_write_algorithm(
674        &self,
675        cx: &mut JSContext,
676        chunk: SafeHandleValue,
677        global: &GlobalScope,
678    ) -> RootedPromise {
679        match &self.underlying_sink_type {
680            UnderlyingSinkType::Js {
681                abort: _,
682                start: _,
683                close: _,
684                write,
685            } => {
686                rooted!(&in(cx) let this_object = self.underlying_sink_obj.get());
687                rooted!(&in(cx) let algo = write.borrow().clone());
688                let result = if let Some(ref algo) = *algo {
689                    algo.Call_(
690                        cx,
691                        &this_object.handle(),
692                        chunk,
693                        self,
694                        ExceptionHandling::Rethrow,
695                    )
696                } else {
697                    Ok(Promise::new_resolved(cx, global, ()))
698                };
699                result.unwrap_or_else(|e| {
700                    let promise = Promise::new(cx, global);
701                    promise.reject_error(cx, e);
702                    promise
703                })
704            },
705            UnderlyingSinkType::Transfer {
706                backpressure_promise,
707                port,
708            } => {
709                // The steps from the `writeAlgorithm` at
710                // <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
711
712                // If backpressurePromise is undefined,
713                // set backpressurePromise to a promise resolved with undefined.
714                if backpressure_promise.borrow().is_none() {
715                    let promise = Promise::new_resolved(cx, global, ());
716                    *backpressure_promise.borrow_mut() = Some(promise.to_traced());
717                }
718
719                // Return the result of reacting to backpressurePromise with the following fulfillment steps:
720                let result_promise = Promise::new(cx, global);
721                rooted!(&in(cx) let mut fulfillment_handler = Some(TransferBackPressurePromiseReaction {
722                    port: port.clone(),
723                    backpressure_promise: backpressure_promise.clone(),
724                    chunk: Heap::boxed(chunk.get()),
725                    result_promise: result_promise.to_traced(),
726                }));
727                let handler = PromiseNativeHandler::new(
728                    cx,
729                    global,
730                    fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
731                    None,
732                );
733                let mut realm = enter_auto_realm(cx, global);
734                let realm = &mut realm.current_realm();
735                backpressure_promise
736                    .borrow()
737                    .as_ref()
738                    .expect("Promise must be some by now.")
739                    .append_native_handler(realm, &handler);
740                result_promise
741            },
742            UnderlyingSinkType::Transform(stream, _) => {
743                // Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, chunk).
744                stream
745                    .transform_stream_default_sink_write_algorithm(cx, global, chunk)
746                    .expect("Transform stream default sink write algorithm should not fail.")
747            },
748        }
749    }
750
751    /// <https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-closealgorithm>
752    fn call_close_algorithm(&self, cx: &mut JSContext, global: &GlobalScope) -> RootedPromise {
753        match &self.underlying_sink_type {
754            UnderlyingSinkType::Js {
755                abort: _,
756                start: _,
757                close,
758                write: _,
759            } => {
760                rooted!(&in(cx) let mut this_object = ptr::null_mut::<JSObject>());
761                this_object.set(self.underlying_sink_obj.get());
762                rooted!(&in(cx) let algo = close.borrow().clone());
763                let result = if let Some(ref algo) = *algo {
764                    algo.Call_(cx, &this_object.handle(), ExceptionHandling::Rethrow)
765                } else {
766                    Ok(Promise::new_resolved(cx, global, ()))
767                };
768                result.unwrap_or_else(|e| {
769                    let promise = Promise::new(cx, global);
770                    promise.reject_error(cx, e);
771                    promise
772                })
773            },
774            UnderlyingSinkType::Transfer { port, .. } => {
775                // The steps from the `closeAlgorithm` at
776                // <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
777
778                // Perform ! PackAndPostMessage(port, "close", undefined).
779                rooted!(&in(cx) let mut value = UndefinedValue());
780                port.pack_and_post_message(cx, "close", value.handle())
781                    .expect("Sending close should not fail.");
782
783                // Disentangle port.
784                global.disentangle_port(cx, port);
785
786                // Return a promise resolved with undefined.
787                Promise::new_resolved(cx, global, ())
788            },
789            UnderlyingSinkType::Transform(stream, _) => {
790                // Return ! TransformStreamDefaultSinkCloseAlgorithm(stream).
791                stream
792                    .transform_stream_default_sink_close_algorithm(cx, global)
793                    .expect("Transform stream default sink close algorithm should not fail.")
794            },
795        }
796    }
797
798    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close>
799    pub(crate) fn process_close(&self, cx: &mut JSContext, global: &GlobalScope) {
800        // Let stream be controller.[[stream]].
801        let Some(stream) = self.stream.get() else {
802            unreachable!("Controller should have a stream");
803        };
804
805        // Perform ! WritableStreamMarkCloseRequestInFlight(stream).
806        stream.mark_close_request_in_flight();
807
808        // Perform ! DequeueValue(controller).
809        self.queue.dequeue_value(cx, None);
810
811        // Assert: controller.[[queue]] is empty.
812        assert!(self.queue.is_empty());
813
814        // Let sinkClosePromise be the result of performing controller.[[closeAlgorithm]].
815        let sink_close_promise = self.call_close_algorithm(cx, global);
816
817        // Perform ! WritableStreamDefaultControllerClearAlgorithms(controller).
818        self.clear_algorithms();
819
820        // Upon fulfillment of sinkClosePromise,
821        rooted!(&in(cx) let mut fulfillment_handler = Some(CloseAlgorithmFulfillmentHandler {
822            stream: Dom::from_ref(&stream),
823        }));
824
825        // Upon rejection of sinkClosePromise with reason reason,
826        rooted!(&in(cx) let mut rejection_handler = Some(CloseAlgorithmRejectionHandler {
827            stream: Dom::from_ref(&stream),
828        }));
829
830        // Attach handlers to the promise.
831        let handler = PromiseNativeHandler::new(
832            cx,
833            global,
834            fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
835            rejection_handler.take().map(|h| Box::new(h) as Box<_>),
836        );
837        let mut realm = enter_auto_realm(cx, global);
838        let realm = &mut realm.current_realm();
839        sink_close_promise.append_native_handler(realm, &handler);
840    }
841
842    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-advance-queue-if-needed>
843    fn advance_queue_if_needed(&self, cx: &mut JSContext, global: &GlobalScope) {
844        // Let stream be controller.[[stream]].
845        let Some(stream) = self.stream.get() else {
846            unreachable!("Controller should have a stream");
847        };
848
849        // If controller.[[started]] is false, return.
850        if !self.started.get() {
851            return;
852        }
853
854        // If stream.[[inFlightWriteRequest]] is not undefined, return.
855        if stream.has_in_flight_write_request() {
856            return;
857        }
858
859        // Let state be stream.[[state]].
860
861        // Assert: state is not "closed" or "errored".
862        assert!(!(stream.is_errored() || stream.is_closed()));
863
864        // If state is "erroring",
865        if stream.is_erroring() {
866            // Perform ! WritableStreamFinishErroring(stream).
867            stream.finish_erroring(cx, global);
868
869            // Return.
870            return;
871        }
872
873        // Let value be ! PeekQueueValue(controller).
874        rooted!(&in(cx) let mut value = UndefinedValue());
875        let is_closed = {
876            // If controller.[[queue]] is empty, return.
877            if self.queue.is_empty() {
878                return;
879            }
880            self.queue.peek_queue_value(cx, value.handle_mut())
881        };
882
883        if is_closed {
884            // If value is the close sentinel, perform ! WritableStreamDefaultControllerProcessClose(controller).
885            self.process_close(cx, global);
886        } else {
887            // Otherwise, perform ! WritableStreamDefaultControllerProcessWrite(controller, value).
888            self.process_write(cx, value.handle(), global);
889        };
890    }
891
892    /// <https://streams.spec.whatwg.org/#ws-default-controller-private-error>
893    pub(crate) fn perform_error_steps(&self) {
894        // Perform ! ResetQueue(this).
895        self.queue.reset();
896    }
897
898    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write>
899    fn process_write(&self, cx: &mut JSContext, chunk: SafeHandleValue, global: &GlobalScope) {
900        // Let stream be controller.[[stream]].
901        let Some(stream) = self.stream.get() else {
902            unreachable!("Controller should have a stream");
903        };
904
905        // Perform ! WritableStreamMarkFirstWriteRequestInFlight(stream).
906        stream.mark_first_write_request_in_flight();
907
908        // Let sinkWritePromise be the result of performing controller.[[writeAlgorithm]], passing in chunk.
909        let sink_write_promise = self.call_write_algorithm(cx, chunk, global);
910
911        // Upon fulfillment of sinkWritePromise,
912        rooted!(&in(cx) let mut fulfillment_handler = Some(WriteAlgorithmFulfillmentHandler {
913            controller: Dom::from_ref(self),
914        }));
915
916        // Upon rejection of sinkWritePromise with reason,
917        rooted!(&in(cx) let mut rejection_handler = Some(WriteAlgorithmRejectionHandler {
918            controller: Dom::from_ref(self),
919        }));
920
921        // Attach handlers to the promise.
922        let handler = PromiseNativeHandler::new(
923            cx,
924            global,
925            fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
926            rejection_handler.take().map(|h| Box::new(h) as Box<_>),
927        );
928        let mut realm = enter_auto_realm(cx, global);
929        let realm = &mut realm.current_realm();
930        sink_write_promise.append_native_handler(realm, &handler);
931    }
932
933    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-get-desired-size>
934    pub(crate) fn get_desired_size(&self) -> f64 {
935        // Return controller.[[strategyHWM]] − controller.[[queueTotalSize]].
936        let desired_size = self.strategy_hwm - self.queue.total_size.get().clamp(0.0, f64::MAX);
937        desired_size.clamp(desired_size, self.strategy_hwm)
938    }
939
940    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-get-backpressure>
941    fn get_backpressure(&self) -> bool {
942        // Let desiredSize be ! WritableStreamDefaultControllerGetDesiredSize(controller).
943        let desired_size = self.get_desired_size();
944
945        // Return true if desiredSize ≤ 0, or false otherwise.
946        desired_size == 0.0 || desired_size.is_sign_negative()
947    }
948
949    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-get-chunk-size>
950    pub(crate) fn get_chunk_size(
951        &self,
952        cx: &mut JSContext,
953        global: &GlobalScope,
954        chunk: SafeHandleValue,
955    ) -> f64 {
956        // If controller.[[strategySizeAlgorithm]] is undefined, then:
957        rooted!(&in(cx) let rooted_strategy_size = self.strategy_size.borrow().clone());
958        let Some(ref strategy_size) = *rooted_strategy_size else {
959            // Assert: controller.[[stream]].[[state]] is not "writable".
960            let Some(stream) = self.stream.get() else {
961                unreachable!("Controller should have a stream");
962            };
963            assert!(!stream.is_writable());
964
965            // Return 1.
966            return 1.0;
967        };
968
969        // Let returnValue be the result of performing controller.[[strategySizeAlgorithm]],
970        // passing in chunk, and interpreting the result as a completion record.
971        let result = strategy_size.Call__(cx, chunk, ExceptionHandling::Rethrow);
972
973        match result {
974            // Let chunkSize be result.[[Value]].
975            Ok(size) => size,
976            Err(error) => {
977                // If result is an abrupt completion,
978
979                // Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, returnValue.[[Value]]).
980                // Create a rooted value for the error.
981                rooted!(&in(cx) let mut rooted_error = UndefinedValue());
982                error.to_jsval(cx, global, rooted_error.handle_mut());
983                self.error_if_needed(cx, rooted_error.handle(), global);
984
985                // Return 1.
986                1.0
987            },
988        }
989    }
990
991    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-write>
992    pub(crate) fn write(
993        &self,
994        cx: &mut JSContext,
995        global: &GlobalScope,
996        chunk: SafeHandleValue,
997        chunk_size: f64,
998    ) {
999        // Let enqueueResult be EnqueueValueWithSize(controller, chunk, chunkSize).
1000        let enqueue_result = self
1001            .queue
1002            .enqueue_value_with_size(EnqueuedValue::Js(ValueWithSize {
1003                value: Heap::boxed(chunk.get()),
1004                size: chunk_size,
1005            }));
1006
1007        // If enqueueResult is an abrupt completion,
1008        if let Err(error) = enqueue_result {
1009            // Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, enqueueResult.[[Value]]).
1010            // Create a rooted value for the error.
1011            rooted!(&in(cx) let mut rooted_error = UndefinedValue());
1012            error.to_jsval(cx, global, rooted_error.handle_mut());
1013            self.error_if_needed(cx, rooted_error.handle(), global);
1014
1015            // Return.
1016            return;
1017        }
1018
1019        // Let stream be controller.[[stream]].
1020        let Some(stream) = self.stream.get() else {
1021            unreachable!("Controller should have a stream");
1022        };
1023
1024        // If ! WritableStreamCloseQueuedOrInFlight(stream) is false and stream.[[state]] is "writable",
1025        if !stream.close_queued_or_in_flight() && stream.is_writable() {
1026            // Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller).
1027            let backpressure = self.get_backpressure();
1028
1029            // Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
1030            stream.update_backpressure(cx, backpressure, global);
1031        }
1032
1033        // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
1034        self.advance_queue_if_needed(cx, global);
1035    }
1036
1037    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-error-if-needed>
1038    pub(crate) fn error_if_needed(
1039        &self,
1040        cx: &mut JSContext,
1041        error: SafeHandleValue,
1042        global: &GlobalScope,
1043    ) {
1044        // Let stream be controller.[[stream]].
1045        let Some(stream) = self.stream.get() else {
1046            unreachable!("Controller should have a stream");
1047        };
1048
1049        // If stream.[[state]] is "writable",
1050        if stream.is_writable() {
1051            // Perform ! WritableStreamDefaultControllerError(controller, e).
1052            self.error(cx, &stream, error, global);
1053        }
1054    }
1055
1056    /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-error>
1057    fn error(
1058        &self,
1059        cx: &mut JSContext,
1060        stream: &WritableStream,
1061        e: SafeHandleValue,
1062        global: &GlobalScope,
1063    ) {
1064        // Let stream be controller.[[stream]].
1065        // Done above with the argument.
1066
1067        // Assert: stream.[[state]] is "writable".
1068        assert!(stream.is_writable());
1069
1070        // Perform ! WritableStreamDefaultControllerClearAlgorithms(controller).
1071        self.clear_algorithms();
1072
1073        // Perform ! WritableStreamStartErroring(stream, error).
1074        stream.start_erroring(cx, global, e);
1075    }
1076}
1077
1078impl WritableStreamDefaultControllerMethods<crate::DomTypeHolder>
1079    for WritableStreamDefaultController
1080{
1081    /// <https://streams.spec.whatwg.org/#ws-default-controller-error>
1082    fn Error(&self, cx: &mut CurrentRealm, e: SafeHandleValue) {
1083        // Let state be this.[[stream]].[[state]].
1084        let Some(stream) = self.stream.get() else {
1085            unreachable!("Controller should have a stream");
1086        };
1087
1088        // If state is not "writable", return.
1089        if !stream.is_writable() {
1090            return;
1091        }
1092
1093        let global = GlobalScope::from_current_realm(cx);
1094
1095        // Perform ! WritableStreamDefaultControllerError(this, e).
1096        self.error(cx, &stream, e, &global);
1097    }
1098
1099    /// <https://streams.spec.whatwg.org/#ws-default-controller-signal>
1100    fn Signal(&self) -> DomRoot<AbortSignal> {
1101        // Return this.[[abortController]]’s signal.
1102        self.abort_controller.signal()
1103    }
1104}