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