Skip to main content

script/dom/stream/
transformstreamdefaultcontroller.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::RefCell;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use js::jsapi::{
11    ExceptionStackBehavior, Heap, JS_IsExceptionPending, JS_SetPendingException, JSObject,
12};
13use js::jsval::UndefinedValue;
14use js::realm::CurrentRealm;
15use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
16use script_bindings::cell::DomRefCell;
17use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
18
19use crate::dom::bindings::callback::ExceptionHandling;
20use crate::dom::bindings::codegen::Bindings::TransformStreamDefaultControllerBinding::TransformStreamDefaultControllerMethods;
21use crate::dom::bindings::codegen::Bindings::TransformerBinding::{
22    Transformer, TransformerCancelCallback, TransformerFlushCallback, TransformerTransformCallback,
23};
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::compressionstream::{
28    CompressionStream, compress_and_enqueue_a_chunk, compress_flush_and_enqueue,
29};
30use crate::dom::decompressionstream::{
31    decompress_and_enqueue_a_chunk, decompress_flush_and_enqueue,
32};
33use crate::dom::encoding::textdecodercommon::TextDecoderCommon;
34use crate::dom::encoding::textdecoderstream::{decode_and_enqueue_a_chunk, flush_and_enqueue};
35use crate::dom::encoding::textencoderstream::{
36    Encoder, encode_and_enqueue_a_chunk, encode_and_flush,
37};
38use crate::dom::globalscope::GlobalScope;
39use crate::dom::promise::Promise;
40use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
41use crate::dom::types::{DecompressionStream, TransformStream};
42use crate::realms::enter_auto_realm;
43
44impl js::gc::Rootable for TransformTransformPromiseRejection {}
45
46/// Reacting to transformPromise as part of
47/// <https://streams.spec.whatwg.org/#transform-stream-default-controller-perform-transform>
48#[derive(JSTraceable, MallocSizeOf)]
49#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
50struct TransformTransformPromiseRejection {
51    controller: Dom<TransformStreamDefaultController>,
52}
53
54impl Callback for TransformTransformPromiseRejection {
55    /// Reacting to transformPromise with the following fulfillment steps:
56    fn callback(&self, cx: &mut CurrentRealm, v: SafeHandleValue) {
57        // Perform ! TransformStreamError(controller.[[stream]], r).
58        self.controller.error(cx, &self.controller.global(), v);
59
60        // Throw r.
61        // Note: this is done part of perform_transform().
62    }
63}
64
65/// The type of transformer algorithms we are using
66#[derive(JSTraceable, MallocSizeOf)]
67#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
68pub(crate) enum TransformerType {
69    /// Algorithms provided by Js callbacks
70    Js {
71        /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-cancelalgorithm>
72        #[conditional_malloc_size_of]
73        cancel: RefCell<Option<Rc<TransformerCancelCallback>>>,
74
75        /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-flushalgorithm>
76        #[conditional_malloc_size_of]
77        flush: RefCell<Option<Rc<TransformerFlushCallback>>>,
78
79        /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-transformalgorithm>
80        #[conditional_malloc_size_of]
81        transform: RefCell<Option<Rc<TransformerTransformCallback>>>,
82
83        /// The JS object used as `this` when invoking sink algorithms.
84        #[ignore_malloc_size_of = "mozjs"]
85        transform_obj: Heap<*mut JSObject>,
86    },
87    /// Algorithms supporting `TextDecoderStream` are implemented in Rust
88    ///
89    /// <https://encoding.spec.whatwg.org/#textdecodercommon>
90    Decoder(#[conditional_malloc_size_of] Rc<TextDecoderCommon>),
91    /// Algorithms supporting `TextEncoderStream` are implemented in Rust
92    ///
93    /// <https://encoding.spec.whatwg.org/#textencoderstream-encoder>
94    Encoder(Encoder),
95    /// Algorithms supporting `CompressionStream` are implemented in Rust
96    ///
97    /// <https://compression.spec.whatwg.org/#compressionstream>
98    Compressor(Dom<CompressionStream>),
99    /// Algorithms supporting `DecompressionStream` are implemented in Rust
100    ///
101    /// <https://compression.spec.whatwg.org/#decompressionstream>
102    Decompressor(Dom<DecompressionStream>),
103}
104
105impl TransformerType {
106    pub(crate) fn new_from_js_transformer(transformer: &Transformer) -> TransformerType {
107        TransformerType::Js {
108            cancel: RefCell::new(transformer.cancel.clone()),
109            flush: RefCell::new(transformer.flush.clone()),
110            transform: RefCell::new(transformer.transform.clone()),
111            transform_obj: Default::default(),
112        }
113    }
114}
115
116/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller>
117#[dom_struct]
118pub struct TransformStreamDefaultController {
119    reflector_: Reflector,
120
121    /// The type of the underlying transformer used. Besides the JS variant,
122    /// there will be other variant(s) for `TextDecoderStream`
123    transformer_type: TransformerType,
124
125    /// <https://streams.spec.whatwg.org/#TransformStreamDefaultController-stream>
126    stream: MutNullableDom<TransformStream>,
127
128    /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-finishpromise>
129    #[conditional_malloc_size_of]
130    finish_promise: DomRefCell<Option<Rc<Promise>>>,
131}
132
133impl TransformStreamDefaultController {
134    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
135    fn new_inherited(transformer_type: TransformerType) -> TransformStreamDefaultController {
136        TransformStreamDefaultController {
137            reflector_: Reflector::new(),
138            transformer_type,
139            stream: MutNullableDom::new(None),
140            finish_promise: DomRefCell::new(None),
141        }
142    }
143
144    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
145    pub(crate) fn new(
146        cx: &mut JSContext,
147        global: &GlobalScope,
148        transformer_type: TransformerType,
149    ) -> DomRoot<TransformStreamDefaultController> {
150        reflect_dom_object_with_cx(
151            Box::new(TransformStreamDefaultController::new_inherited(
152                transformer_type,
153            )),
154            global,
155            cx,
156        )
157    }
158
159    /// Setting the JS object after the heap has settled down.
160    ///
161    /// Note that this has no effect if the transformer type is not `TransformerType::Js`
162    pub(crate) fn set_transform_obj(&self, this_object: SafeHandleObject) {
163        if let TransformerType::Js { transform_obj, .. } = &self.transformer_type {
164            transform_obj.set(*this_object)
165        } else {
166            unreachable!("Non-Js transformer type should not set transform_obj")
167        }
168    }
169
170    pub(crate) fn set_stream(&self, stream: &TransformStream) {
171        self.stream.set(Some(stream));
172    }
173
174    pub(crate) fn get_finish_promise(&self) -> Option<Rc<Promise>> {
175        self.finish_promise.borrow().clone()
176    }
177
178    pub(crate) fn set_finish_promise(&self, promise: Rc<Promise>) {
179        *self.finish_promise.borrow_mut() = Some(promise);
180    }
181
182    /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-perform-transform>
183    pub(crate) fn transform_stream_default_controller_perform_transform(
184        &self,
185        cx: &mut JSContext,
186        global: &GlobalScope,
187        chunk: SafeHandleValue,
188    ) -> Fallible<Rc<Promise>> {
189        // Let transformPromise be the result of performing controller.[[transformAlgorithm]], passing chunk.
190        let transform_promise = self.perform_transform(cx, global, chunk)?;
191
192        // Return the result of reacting to transformPromise with the following rejection steps given the argument r:
193        rooted!(&in(cx) let mut reject_handler = Some(TransformTransformPromiseRejection {
194            controller: Dom::from_ref(self),
195        }));
196
197        let handler = PromiseNativeHandler::new(
198            cx,
199            global,
200            None,
201            reject_handler.take().map(|h| Box::new(h) as Box<_>),
202        );
203        let mut realm = enter_auto_realm(cx, global);
204        let realm = &mut realm.current_realm();
205        transform_promise.append_native_handler(realm, &handler);
206
207        Ok(transform_promise)
208    }
209
210    pub(crate) fn perform_transform(
211        &self,
212        cx: &mut JSContext,
213        global: &GlobalScope,
214        chunk: SafeHandleValue,
215    ) -> Fallible<Rc<Promise>> {
216        let result = match &self.transformer_type {
217            // <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer>
218            TransformerType::Js {
219                transform,
220                transform_obj,
221                ..
222            } => {
223                // Step 5. If transformerDict["transform"] exists, set
224                // transformAlgorithm to an algorithm which takes an argument
225                // chunk and returns the result of invoking
226                // transformerDict["transform"] with argument list « chunk,
227                // controller » and callback this value transformer.
228                let algo = transform.borrow().clone();
229                if let Some(transform) = algo {
230                    rooted!(&in(cx) let this_object = transform_obj.get());
231                    transform
232                        .Call_(
233                            cx,
234                            &this_object.handle(),
235                            chunk,
236                            self,
237                            ExceptionHandling::Rethrow,
238                        )
239                        .unwrap_or_else(|e| {
240                            let p = Promise::new(cx, global);
241                            p.reject_error(cx, e);
242                            p
243                        })
244                } else {
245                    // Step 2. Let transformAlgorithm be the following steps, taking a chunk argument:
246                    // Let result be TransformStreamDefaultControllerEnqueue(controller, chunk).
247                    // If result is an abrupt completion, return a promise rejected with result.[[Value]].
248                    if let Err(error) = self.enqueue(cx, global, chunk) {
249                        rooted!(&in(cx) let mut error_val = UndefinedValue());
250                        error.to_jsval(cx, global, error_val.handle_mut());
251                        Promise::new_rejected(cx, global, error_val.handle())
252                    } else {
253                        // Otherwise, return a promise resolved with undefined.
254                        Promise::new_resolved(cx, global, ())
255                    }
256                }
257            },
258            TransformerType::Decoder(decoder) => {
259                // <https://encoding.spec.whatwg.org/#dom-textdecoderstream>
260                // Step 7. Let transformAlgorithm be an algorithm which takes a
261                // chunk argument and runs the decode and enqueue a chunk
262                // algorithm with this and chunk.
263                decode_and_enqueue_a_chunk(cx, global, chunk, decoder, self)
264                    // <https://streams.spec.whatwg.org/#transformstream-set-up>
265                    // Step 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
266                    // Step 5.1 Let result be the result of running transformAlgorithm given chunk.
267                    // Step 5.2 If result is a Promise, then return result.
268                    // Note: not applicable, the spec does NOT require deode_and_enqueue_a_chunk() to return a Promise
269                    // Step 5.3 Return a promise resolved with undefined.
270                    .map(|_| Promise::new_resolved(cx, global, ()))
271                    .unwrap_or_else(|e| {
272                        // <https://streams.spec.whatwg.org/#transformstream-set-up>
273                        // Step 5.1 If this throws an exception e,
274                        let mut realm = enter_auto_realm(cx, self);
275                        let realm = &mut realm.current_realm();
276                        let p = Promise::new_in_realm(realm);
277                        // return a promise rejected with e.
278                        p.reject_error(realm, e);
279                        p
280                    })
281            },
282            TransformerType::Encoder(encoder) => {
283                // <https://encoding.spec.whatwg.org/#dom-textencoderstream>
284                // Step 2. Let transformAlgorithm be an algorithm which takes a chunk argument and runs the encode
285                //      and enqueue a chunk algorithm with this and chunk.
286                encode_and_enqueue_a_chunk(cx, global, chunk, encoder, self)
287                    // <https://streams.spec.whatwg.org/#transformstream-set-up>
288                    // Step 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
289                    // Step 5.1 Let result be the result of running transformAlgorithm given chunk.
290                    // Step 5.2 If result is a Promise, then return result.
291                    // Note: not applicable, the spec does NOT require encode_and_enqueue_a_chunk() to return a Promise
292                    // Step 5.3 Return a promise resolved with undefined.
293                    .map(|_| Promise::new_resolved(cx, global, ()))
294                    .unwrap_or_else(|e| {
295                        // <https://streams.spec.whatwg.org/#transformstream-set-up>
296                        // Step 5.1 If this throws an exception e,
297                        let mut realm = enter_auto_realm(cx, self);
298                        let realm = &mut realm.current_realm();
299                        let p = Promise::new_in_realm(realm);
300                        // return a promise rejected with e.
301                        p.reject_error(realm, e);
302                        p
303                    })
304            },
305            TransformerType::Compressor(cs) => {
306                // <https://compression.spec.whatwg.org/#dom-compressionstream-compressionstream>
307                // Step 3. Let transformAlgorithm be an algorithm which takes a chunk argument and
308                // runs the compress and enqueue a chunk algorithm with this and chunk.
309                compress_and_enqueue_a_chunk(cx, global, cs, chunk, self)
310                    // <https://streams.spec.whatwg.org/#transformstream-set-up>
311                    // Step 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
312                    // Step 5.1 Let result be the result of running transformAlgorithm given chunk.
313                    // Step 5.2 If result is a Promise, then return result.
314                    // Note: not applicable, the spec does NOT require
315                    // compress_and_enqueue_a_chunk() to return a Promise.
316                    // Step 5.3 Return a promise resolved with undefined.
317                    .map(|_| Promise::new_resolved(cx, global, ()))
318                    .unwrap_or_else(|e| {
319                        // <https://streams.spec.whatwg.org/#transformstream-set-up>
320                        // Step 5.1 If this throws an exception e,
321                        let mut realm = enter_auto_realm(cx, self);
322                        let realm = &mut realm.current_realm();
323                        let p = Promise::new_in_realm(realm);
324                        // return a promise rejected with e.
325                        p.reject_error(realm, e);
326                        p
327                    })
328            },
329            TransformerType::Decompressor(ds) => {
330                // <https://compression.spec.whatwg.org/#dom-decompressionstream-decompressionstream>
331                // Step 3. Let transformAlgorithm be an algorithm which takes a chunk argument and
332                // runs the decompress and enqueue a chunk algorithm with this and chunk.
333                decompress_and_enqueue_a_chunk(cx, global, ds, chunk, self)
334                    // <https://streams.spec.whatwg.org/#transformstream-set-up>
335                    // Step 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
336                    // Step 5.1 Let result be the result of running transformAlgorithm given chunk.
337                    // Step 5.2 If result is a Promise, then return result.
338                    // Note: not applicable, the spec does NOT require
339                    // decompress_and_enqueue_a_chunk() to return a Promise
340                    // Step 5.3 Return a promise resolved with undefined.
341                    .map(|_| Promise::new_resolved(cx, global, ()))
342                    .unwrap_or_else(|e| {
343                        // <https://streams.spec.whatwg.org/#transformstream-set-up>
344                        // Step 5.1 If this throws an exception e,
345                        let mut realm = enter_auto_realm(cx, self);
346                        let realm = &mut realm.current_realm();
347                        let p = Promise::new_in_realm(realm);
348                        // return a promise rejected with e.
349                        p.reject_error(realm, e);
350                        p
351                    })
352            },
353        };
354
355        Ok(result)
356    }
357
358    pub(crate) fn perform_cancel(
359        &self,
360        cx: &mut JSContext,
361        global: &GlobalScope,
362        chunk: SafeHandleValue,
363    ) -> Fallible<Rc<Promise>> {
364        let result = match &self.transformer_type {
365            // <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer>
366            TransformerType::Js {
367                cancel,
368                transform_obj,
369                ..
370            } => {
371                // Step 7. If transformerDict["cancel"] exists, set
372                // cancelAlgorithm to an algorithm which takes an argument
373                // reason and returns the result of invoking
374                // transformerDict["cancel"] with argument list « reason » and
375                // callback this value transformer.
376                let algo = cancel.borrow().clone();
377                if let Some(cancel) = algo {
378                    rooted!(&in(cx) let this_object = transform_obj.get());
379                    cancel
380                        .Call_(cx, &this_object.handle(), chunk, ExceptionHandling::Rethrow)
381                        .unwrap_or_else(|e| {
382                            let p = Promise::new(cx, global);
383                            p.reject_error(cx, e);
384                            p
385                        })
386                } else {
387                    // Step 4. Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined.
388                    Promise::new_resolved(cx, global, ())
389                }
390            },
391            TransformerType::Decoder(_) => {
392                // <https://streams.spec.whatwg.org/#transformstream-set-up>
393                // Step 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason:
394                // Step 7.1 Let result be the result of running cancelAlgorithm given reason,
395                //      if cancelAlgorithm was given, or null otherwise
396                // Note: `TextDecoderStream` does NOT specify a cancel algorithm.
397                // Step 7.2 If result is a Promise, then return result.
398                // Note: Not applicable.
399                // Step 7.3 Return a promise resolved with undefined.
400                Promise::new_resolved(cx, global, ())
401            },
402            TransformerType::Encoder(_) => {
403                // <https://streams.spec.whatwg.org/#transformstream-set-up>
404                // Step 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason:
405                // Step 7.1 Let result be the result of running cancelAlgorithm given reason,
406                //      if cancelAlgorithm was given, or null otherwise
407                // Note: `TextDecoderStream` does NOT specify a cancel algorithm.
408                // Step 7.2 If result is a Promise, then return result.
409                // Note: Not applicable.
410                // Step 7.3 Return a promise resolved with undefined.
411                Promise::new_resolved(cx, global, ())
412            },
413            TransformerType::Compressor(_) => {
414                // <https://streams.spec.whatwg.org/#transformstream-set-up>
415                // Step 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason:
416                // Step 7.1 Let result be the result of running cancelAlgorithm given reason,
417                //      if cancelAlgorithm was given, or null otherwise
418                // Note: `CompressionStream` does NOT specify a cancel algorithm.
419                // Step 7.2 If result is a Promise, then return result.
420                // Note: Not applicable.
421                // Step 7.3 Return a promise resolved with undefined.
422                Promise::new_resolved(cx, global, ())
423            },
424            TransformerType::Decompressor(_) => {
425                // <https://streams.spec.whatwg.org/#transformstream-set-up>
426                // Step 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason:
427                // Step 7.1 Let result be the result of running cancelAlgorithm given reason,
428                //      if cancelAlgorithm was given, or null otherwise
429                // Note: `DecompressionStream` does NOT specify a cancel algorithm.
430                // Step 7.2 If result is a Promise, then return result.
431                // Note: Not applicable.
432                // Step 7.3 Return a promise resolved with undefined.
433                Promise::new_resolved(cx, global, ())
434            },
435        };
436
437        Ok(result)
438    }
439
440    pub(crate) fn perform_flush(
441        &self,
442        cx: &mut JSContext,
443        global: &GlobalScope,
444    ) -> Fallible<Rc<Promise>> {
445        let result = match &self.transformer_type {
446            // <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer>
447            TransformerType::Js {
448                flush,
449                transform_obj,
450                ..
451            } => {
452                // Step 6. If transformerDict["flush"] exists, set flushAlgorithm to an
453                // algorithm which returns the result of invoking
454                // transformerDict["flush"] with argument list « controller »
455                // and callback this value transformer.
456                let algo = flush.borrow().clone();
457                if let Some(flush) = algo {
458                    rooted!(&in(cx) let this_object = transform_obj.get());
459                    flush
460                        .Call_(cx, &this_object.handle(), self, ExceptionHandling::Rethrow)
461                        .unwrap_or_else(|e| {
462                            let p = Promise::new(cx, global);
463                            p.reject_error(cx, e);
464                            p
465                        })
466                } else {
467                    // Step 3. Let flushAlgorithm be an algorithm which returns a promise resolved with undefined.
468                    Promise::new_resolved(cx, global, ())
469                }
470            },
471            TransformerType::Decoder(decoder) => {
472                // <https://encoding.spec.whatwg.org/#dom-textdecoderstream>
473                // Step 8. Let flushAlgorithm be an algorithm which takes no
474                // arguments and runs the flush and enqueue algorithm with this.
475                flush_and_enqueue(cx, global, decoder, self)
476                    // <https://streams.spec.whatwg.org/#transformstream-set-up>
477                    // Step 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
478                    // Step 6.1 Let result be the result of running flushAlgorithm,
479                    //      if flushAlgorithm was given, or null otherwise.
480                    // Step 6.2 If result is a Promise, then return result.
481                    // Note: Not applicable. The spec does NOT require flush_and_enqueue algo to return a Promise
482                    // Step 6.3 Return a promise resolved with undefined.
483                    .map(|_| Promise::new_resolved(cx, global, ()))
484                    .unwrap_or_else(|e| {
485                        // <https://streams.spec.whatwg.org/#transformstream-set-up>
486                        // Step 6.1 If this throws an exception e,
487                        let mut realm = enter_auto_realm(cx, self);
488                        let realm = &mut realm.current_realm();
489                        let p = Promise::new_in_realm(realm);
490                        // return a promise rejected with e.
491                        p.reject_error(realm, e);
492                        p
493                    })
494            },
495            TransformerType::Encoder(encoder) => {
496                // <https://encoding.spec.whatwg.org/#textencoderstream-encoder>
497                // Step 3. Let flushAlgorithm be an algorithm which runs the encode and flush algorithm with this.
498                encode_and_flush(cx, global, encoder, self)
499                    // <https://streams.spec.whatwg.org/#transformstream-set-up>
500                    // Step 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
501                    // Step 6.1 Let result be the result of running flushAlgorithm,
502                    //      if flushAlgorithm was given, or null otherwise.
503                    // Step 6.2 If result is a Promise, then return result.
504                    // Note: Not applicable. The spec does NOT require encode_and_flush algo to return a Promise
505                    // Step 6.3 Return a promise resolved with undefined.
506                    .map(|_| Promise::new_resolved(cx, global, ()))
507                    .unwrap_or_else(|e| {
508                        // <https://streams.spec.whatwg.org/#transformstream-set-up>
509                        // Step 6.1 If this throws an exception e,
510                        let mut realm = enter_auto_realm(cx, self);
511                        let realm = &mut realm.current_realm();
512                        let p = Promise::new_in_realm(realm);
513                        // return a promise rejected with e.
514                        p.reject_error(realm, e);
515                        p
516                    })
517            },
518            TransformerType::Compressor(cs) => {
519                // <https://compression.spec.whatwg.org/#dom-compressionstream-compressionstream>
520                // Step 4. Let flushAlgorithm be an algorithm which takes no argument and runs the
521                // compress flush and enqueue algorithm with this.
522                compress_flush_and_enqueue(cx, global, cs, self)
523                    // <https://streams.spec.whatwg.org/#transformstream-set-up>
524                    // Step 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
525                    // Step 6.1 Let result be the result of running flushAlgorithm,
526                    //      if flushAlgorithm was given, or null otherwise.
527                    // Step 6.2 If result is a Promise, then return result.
528                    // Note: Not applicable. The spec does NOT require compress_flush_and_enqueue
529                    // algo to return a Promise.
530                    // Step 6.3 Return a promise resolved with undefined.
531                    .map(|_| Promise::new_resolved(cx, global, ()))
532                    .unwrap_or_else(|e| {
533                        // <https://streams.spec.whatwg.org/#transformstream-set-up>
534                        // Step 6.1 If this throws an exception e,
535                        let mut realm = enter_auto_realm(cx, self);
536                        let realm = &mut realm.current_realm();
537                        let p = Promise::new_in_realm(realm);
538                        // return a promise rejected with e.
539                        p.reject_error(realm, e);
540                        p
541                    })
542            },
543            TransformerType::Decompressor(ds) => {
544                // <https://compression.spec.whatwg.org/#dom-decompressionstream-decompressionstream>
545                // Step 4. Let flushAlgorithm be an algorithm which takes no argument and runs the
546                // decompress flush and enqueue algorithm with this.
547                decompress_flush_and_enqueue(cx, global, ds, self)
548                    // <https://streams.spec.whatwg.org/#transformstream-set-up>
549                    // Step 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
550                    // Step 6.1 Let result be the result of running flushAlgorithm,
551                    //      if flushAlgorithm was given, or null otherwise.
552                    // Step 6.2 If result is a Promise, then return result.
553                    // Note: Not applicable. The spec does NOT require decompress_flush_and_enqueue
554                    // algo to return a Promise.
555                    // Step 6.3 Return a promise resolved with undefined.
556                    .map(|_| Promise::new_resolved(cx, global, ()))
557                    .unwrap_or_else(|e| {
558                        // <https://streams.spec.whatwg.org/#transformstream-set-up>
559                        // Step 6.1 If this throws an exception e,
560                        let mut realm = enter_auto_realm(cx, self);
561                        let realm = &mut realm.current_realm();
562                        let p = Promise::new_in_realm(realm);
563                        // return a promise rejected with e.
564                        p.reject_error(realm, e);
565                        p
566                    })
567            },
568        };
569
570        Ok(result)
571    }
572
573    /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-enqueue>
574    #[expect(unsafe_code)]
575    pub(crate) fn enqueue(
576        &self,
577        cx: &mut JSContext,
578        global: &GlobalScope,
579        chunk: SafeHandleValue,
580    ) -> Fallible<()> {
581        // Let stream be controller.[[stream]].
582        let stream = self.stream.get().expect("stream is null");
583
584        // Let readableController be stream.[[readable]].[[controller]].
585        let readable = stream.get_readable();
586        let readable_controller = readable.get_default_controller();
587
588        // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(readableController)
589        // is false, throw a TypeError exception.
590        if !readable_controller.can_close_or_enqueue() {
591            return Err(Error::Type(
592                c"ReadableStreamDefaultControllerCanCloseOrEnqueue is false".to_owned(),
593            ));
594        }
595
596        // Let enqueueResult be ReadableStreamDefaultControllerEnqueue(readableController, chunk).
597        // If enqueueResult is an abrupt completion,
598        if let Err(error) = readable_controller.enqueue(cx, chunk) {
599            // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, enqueueResult.[[Value]]).
600            rooted!(&in(cx) let mut rooted_error = UndefinedValue());
601            error
602                .clone()
603                .to_jsval(cx, global, rooted_error.handle_mut());
604            stream.error_writable_and_unblock_write(cx, global, rooted_error.handle());
605
606            // Throw stream.[[readable]].[[storedError]].
607            unsafe {
608                if !JS_IsExceptionPending(cx.raw_cx()) {
609                    rooted!(&in(cx) let mut stored_error = UndefinedValue());
610                    readable.get_stored_error(stored_error.handle_mut());
611
612                    JS_SetPendingException(
613                        cx.raw_cx(),
614                        stored_error.handle().into(),
615                        ExceptionStackBehavior::Capture,
616                    );
617                }
618            }
619            return Err(error);
620        }
621
622        // Let backpressure be ! ReadableStreamDefaultControllerHasBackpressure(readableController).
623        let backpressure = readable_controller.has_backpressure();
624
625        // If backpressure is not stream.[[backpressure]],
626        if backpressure != stream.get_backpressure() {
627            // Assert: backpressure is true.
628            assert!(backpressure);
629
630            // Perform ! TransformStreamSetBackpressure(stream, true).
631            stream.set_backpressure(cx, global, true);
632        }
633        Ok(())
634    }
635
636    /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-error>
637    pub(crate) fn error(&self, cx: &mut JSContext, global: &GlobalScope, reason: SafeHandleValue) {
638        // Perform ! TransformStreamError(controller.[[stream]], e).
639        self.stream
640            .get()
641            .expect("stream is undefined")
642            .error(cx, global, reason);
643    }
644
645    /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-clear-algorithms>
646    pub(crate) fn clear_algorithms(&self) {
647        if let TransformerType::Js {
648            cancel,
649            flush,
650            transform,
651            ..
652        } = &self.transformer_type
653        {
654            // Set controller.[[transformAlgorithm]] to undefined.
655            transform.replace(None);
656
657            // Set controller.[[flushAlgorithm]] to undefined.
658            flush.replace(None);
659
660            // Set controller.[[cancelAlgorithm]] to undefined.
661            cancel.replace(None);
662        }
663    }
664
665    /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-terminate>
666    fn terminate(&self, cx: &mut JSContext, global: &GlobalScope) {
667        // Let stream be controller.[[stream]].
668        let stream = self.stream.get().expect("stream is null");
669
670        // Let readableController be stream.[[readable]].[[controller]].
671        let readable = stream.get_readable();
672        let readable_controller = readable.get_default_controller();
673
674        // Perform ! ReadableStreamDefaultControllerClose(readableController).
675        readable_controller.close(cx);
676
677        // Let error be a TypeError exception indicating that the stream has been terminated.
678        let error = Error::Type(c"stream has been terminated".to_owned());
679
680        // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, error).
681        rooted!(&in(cx) let mut rooted_error = UndefinedValue());
682        error.to_jsval(cx, global, rooted_error.handle_mut());
683        stream.error_writable_and_unblock_write(cx, global, rooted_error.handle());
684    }
685}
686
687impl TransformStreamDefaultControllerMethods<crate::DomTypeHolder>
688    for TransformStreamDefaultController
689{
690    /// <https://streams.spec.whatwg.org/#ts-default-controller-desired-size>
691    fn GetDesiredSize(&self) -> Option<f64> {
692        // Let readableController be this.[[stream]].[[readable]].[[controller]].
693        let readable_controller = self
694            .stream
695            .get()
696            .expect("stream is null")
697            .get_readable()
698            .get_default_controller();
699
700        // Return ! ReadableStreamDefaultControllerGetDesiredSize(readableController).
701        readable_controller.get_desired_size()
702    }
703
704    /// <https://streams.spec.whatwg.org/#ts-default-controller-enqueue>
705    fn Enqueue(&self, cx: &mut JSContext, chunk: SafeHandleValue) -> Fallible<()> {
706        // Perform ? TransformStreamDefaultControllerEnqueue(this, chunk).
707        self.enqueue(cx, &self.global(), chunk)
708    }
709
710    /// <https://streams.spec.whatwg.org/#ts-default-controller-error>
711    fn Error(&self, cx: &mut JSContext, reason: SafeHandleValue) -> Fallible<()> {
712        // Perform ? TransformStreamDefaultControllerError(this, e).
713        self.error(cx, &self.global(), reason);
714        Ok(())
715    }
716
717    /// <https://streams.spec.whatwg.org/#ts-default-controller-terminate>
718    fn Terminate(&self, cx: &mut JSContext) -> Fallible<()> {
719        // Perform ? TransformStreamDefaultControllerTerminate(this).
720        self.terminate(cx, &self.global());
721        Ok(())
722    }
723}