1use 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#[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 stream.finish_in_flight_close(cx);
52 }
53}
54
55impl js::gc::Rootable for CloseAlgorithmRejectionHandler {}
56
57#[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 stream.finish_in_flight_close_with_error(cx, &global, v);
73 }
74}
75
76impl js::gc::Rootable for StartAlgorithmFulfillmentHandler {}
77
78#[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 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.is_erroring() || stream.is_writable());
98
99 controller.started.set(true);
101
102 let global = GlobalScope::from_current_realm(cx);
103
104 controller.advance_queue_if_needed(cx, &global)
106 }
107}
108
109impl js::gc::Rootable for StartAlgorithmRejectionHandler {}
110
111#[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 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.is_erroring() || stream.is_writable());
131
132 controller.started.set(true);
134
135 let global = GlobalScope::from_current_realm(cx);
136
137 stream.deal_with_rejection(cx, &global, v);
139 }
140}
141
142impl js::gc::Rootable for TransferBackPressurePromiseReaction {}
143
144#[derive(JSTraceable, MallocSizeOf)]
147#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
148struct TransferBackPressurePromiseReaction {
149 result_promise: TracedPromise,
151
152 #[ignore_malloc_size_of = "nested Rc"]
154 backpressure_promise: Rc<RefCell<Option<TracedPromise>>>,
155
156 #[ignore_malloc_size_of = "mozjs"]
158 chunk: Box<Heap<JSVal>>,
159
160 port: Dom<MessagePort>,
162}
163
164impl Callback for TransferBackPressurePromiseReaction {
165 fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
167 let global = self.result_promise.global();
168 let promise = Promise::new(cx, &global);
170 *self.backpressure_promise.borrow_mut() = Some(promise.to_traced());
171
172 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 let Err(error) = result {
181 global.disentangle_port(cx, &self.port);
183
184 self.result_promise.reject_error(cx, error);
186 } else {
187 self.result_promise.resolve_native(cx, &());
189 }
190 }
191}
192
193impl js::gc::Rootable for WriteAlgorithmFulfillmentHandler {}
194
195#[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 stream.finish_in_flight_write(cx);
213
214 assert!(stream.is_erroring() || stream.is_writable());
217
218 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 !stream.close_queued_or_in_flight() && stream.is_writable() {
226 let backpressure = controller.get_backpressure();
228
229 stream.update_backpressure(cx, backpressure, &global);
231 }
232
233 controller.advance_queue_if_needed(cx, &global)
235 }
236}
237
238impl js::gc::Rootable for WriteAlgorithmRejectionHandler {}
239
240#[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.is_writable() {
258 controller.clear_algorithms();
260 }
261
262 let global = GlobalScope::from_current_realm(cx);
263
264 stream.finish_in_flight_write_with_error(cx, &global, v);
266 }
267}
268
269#[derive(JSTraceable, PartialEq)]
271#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
272pub enum UnderlyingSinkType {
273 Js {
275 abort: RefCell<Option<TracedCallback<UnderlyingSinkAbortCallback>>>,
277
278 start: RefCell<Option<TracedCallback<UnderlyingSinkStartCallback>>>,
279
280 close: RefCell<Option<TracedCallback<UnderlyingSinkCloseCallback>>>,
282
283 write: RefCell<Option<TracedCallback<UnderlyingSinkWriteCallback>>>,
285 },
286 Transfer {
289 backpressure_promise: Rc<RefCell<Option<TracedPromise>>>,
290 port: Dom<MessagePort>,
291 },
292 Transform(Dom<TransformStream>, TracedPromise),
294}
295
296pub(crate) enum UnderlyingSinkTypeRef<'a> {
298 Js {
300 abort: Option<&'a TracedCallback<UnderlyingSinkAbortCallback>>,
302
303 start: Option<&'a TracedCallback<UnderlyingSinkStartCallback>>,
304
305 close: Option<&'a TracedCallback<UnderlyingSinkCloseCallback>>,
307
308 write: Option<&'a TracedCallback<UnderlyingSinkWriteCallback>>,
310 },
311 Transfer {
314 backpressure_promise: &'a Rc<RefCell<Option<TracedPromise>>>,
315 port: &'a MessagePort,
316 },
317 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#[dom_struct]
367pub struct WritableStreamDefaultController {
368 reflector_: Reflector,
369
370 #[ignore_malloc_size_of = "underlying_sink_type"]
373 underlying_sink_type: UnderlyingSinkType,
374
375 #[ignore_malloc_size_of = "mozjs"]
377 underlying_sink_obj: Heap<*mut JSObject>,
378
379 queue: QueueWithSizes,
381
382 started: Cell<bool>,
384
385 strategy_hwm: f64,
387
388 #[ignore_malloc_size_of = "QueuingStrategySize"]
390 strategy_size: RefCell<Option<TracedCallback<QueuingStrategySize>>>,
391
392 stream: MutNullableDom<WritableStream>,
394
395 abort_controller: Dom<AbortController>,
397}
398
399impl WritableStreamDefaultController {
400 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 pub(crate) fn set_underlying_sink_this_object(&self, this_object: SafeHandleObject) {
443 self.underlying_sink_obj.set(*this_object);
444 }
445
446 pub(crate) fn signal_abort(&self, cx: &mut CurrentRealm, reason: SafeHandleValue) {
448 self.abort_controller.signal_abort(cx, reason);
449 }
450
451 fn clear_algorithms(&self) {
453 match &self.underlying_sink_type {
454 UnderlyingSinkType::Js {
455 abort,
456 start: _,
457 close,
458 write,
459 } => {
460 write.borrow_mut().take();
462
463 close.borrow_mut().take();
465
466 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 self.strategy_size.borrow_mut().take();
482 }
483
484 pub(crate) fn setup(
486 &self,
487 cx: &mut JSContext,
488 global: &GlobalScope,
489 stream: &WritableStream,
490 ) -> Result<(), Error> {
491 stream.assert_no_controller();
496
497 self.stream.set(Some(stream));
499
500 stream.set_default_controller(self);
502
503 let backpressure = self.get_backpressure();
523
524 stream.update_backpressure(cx, backpressure, global);
526
527 let start_promise = self.start_algorithm(cx, global)?;
530
531 let rooted_default_controller = DomRoot::from_ref(self);
532
533 rooted!(&in(cx) let mut fulfillment_handler = Some(StartAlgorithmFulfillmentHandler {
535 controller: Dom::from_ref(&rooted_default_controller),
536 }));
537
538 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 pub(crate) fn close(&self, cx: &mut JSContext, global: &GlobalScope) {
558 self.queue
560 .enqueue_value_with_size(EnqueuedValue::CloseSentinel)
561 .expect("Enqueuing the close sentinel should not fail.");
562 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 Promise::new_resolved(cx, global, ())
589 };
590
591 Ok(start_promise)
592 },
593 UnderlyingSinkType::Transfer { .. } => {
594 Ok(Promise::new_resolved(cx, global, ()))
596 },
597 UnderlyingSinkType::Transform(_, start_promise) => {
598 Ok(start_promise.root(cx))
600 },
601 }
602 }
603
604 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 = 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 let result = port.pack_and_post_message_handling_error(cx, "error", reason);
643
644 global.disentangle_port(cx, port);
646
647 let promise = Promise::new(cx, global);
648
649 if let Err(error) = result {
651 promise.reject_error(cx, error);
652 } else {
653 promise.resolve_native(cx, &());
655 }
656 promise
657 },
658 UnderlyingSinkType::Transform(stream, _) => {
659 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 self.clear_algorithms();
668
669 result
670 }
671
672 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 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 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 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 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 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 global.disentangle_port(cx, port);
785
786 Promise::new_resolved(cx, global, ())
788 },
789 UnderlyingSinkType::Transform(stream, _) => {
790 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 pub(crate) fn process_close(&self, cx: &mut JSContext, global: &GlobalScope) {
800 let Some(stream) = self.stream.get() else {
802 unreachable!("Controller should have a stream");
803 };
804
805 stream.mark_close_request_in_flight();
807
808 self.queue.dequeue_value(cx, None);
810
811 assert!(self.queue.is_empty());
813
814 let sink_close_promise = self.call_close_algorithm(cx, global);
816
817 self.clear_algorithms();
819
820 rooted!(&in(cx) let mut fulfillment_handler = Some(CloseAlgorithmFulfillmentHandler {
822 stream: Dom::from_ref(&stream),
823 }));
824
825 rooted!(&in(cx) let mut rejection_handler = Some(CloseAlgorithmRejectionHandler {
827 stream: Dom::from_ref(&stream),
828 }));
829
830 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 fn advance_queue_if_needed(&self, cx: &mut JSContext, global: &GlobalScope) {
844 let Some(stream) = self.stream.get() else {
846 unreachable!("Controller should have a stream");
847 };
848
849 if !self.started.get() {
851 return;
852 }
853
854 if stream.has_in_flight_write_request() {
856 return;
857 }
858
859 assert!(!(stream.is_errored() || stream.is_closed()));
863
864 if stream.is_erroring() {
866 stream.finish_erroring(cx, global);
868
869 return;
871 }
872
873 rooted!(&in(cx) let mut value = UndefinedValue());
875 let is_closed = {
876 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 self.process_close(cx, global);
886 } else {
887 self.process_write(cx, value.handle(), global);
889 };
890 }
891
892 pub(crate) fn perform_error_steps(&self) {
894 self.queue.reset();
896 }
897
898 fn process_write(&self, cx: &mut JSContext, chunk: SafeHandleValue, global: &GlobalScope) {
900 let Some(stream) = self.stream.get() else {
902 unreachable!("Controller should have a stream");
903 };
904
905 stream.mark_first_write_request_in_flight();
907
908 let sink_write_promise = self.call_write_algorithm(cx, chunk, global);
910
911 rooted!(&in(cx) let mut fulfillment_handler = Some(WriteAlgorithmFulfillmentHandler {
913 controller: Dom::from_ref(self),
914 }));
915
916 rooted!(&in(cx) let mut rejection_handler = Some(WriteAlgorithmRejectionHandler {
918 controller: Dom::from_ref(self),
919 }));
920
921 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 pub(crate) fn get_desired_size(&self) -> f64 {
935 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 fn get_backpressure(&self) -> bool {
942 let desired_size = self.get_desired_size();
944
945 desired_size == 0.0 || desired_size.is_sign_negative()
947 }
948
949 pub(crate) fn get_chunk_size(
951 &self,
952 cx: &mut JSContext,
953 global: &GlobalScope,
954 chunk: SafeHandleValue,
955 ) -> f64 {
956 rooted!(&in(cx) let rooted_strategy_size = self.strategy_size.borrow().clone());
958 let Some(ref strategy_size) = *rooted_strategy_size else {
959 let Some(stream) = self.stream.get() else {
961 unreachable!("Controller should have a stream");
962 };
963 assert!(!stream.is_writable());
964
965 return 1.0;
967 };
968
969 let result = strategy_size.Call__(cx, chunk, ExceptionHandling::Rethrow);
972
973 match result {
974 Ok(size) => size,
976 Err(error) => {
977 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 1.0
987 },
988 }
989 }
990
991 pub(crate) fn write(
993 &self,
994 cx: &mut JSContext,
995 global: &GlobalScope,
996 chunk: SafeHandleValue,
997 chunk_size: f64,
998 ) {
999 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 let Err(error) = enqueue_result {
1009 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;
1017 }
1018
1019 let Some(stream) = self.stream.get() else {
1021 unreachable!("Controller should have a stream");
1022 };
1023
1024 if !stream.close_queued_or_in_flight() && stream.is_writable() {
1026 let backpressure = self.get_backpressure();
1028
1029 stream.update_backpressure(cx, backpressure, global);
1031 }
1032
1033 self.advance_queue_if_needed(cx, global);
1035 }
1036
1037 pub(crate) fn error_if_needed(
1039 &self,
1040 cx: &mut JSContext,
1041 error: SafeHandleValue,
1042 global: &GlobalScope,
1043 ) {
1044 let Some(stream) = self.stream.get() else {
1046 unreachable!("Controller should have a stream");
1047 };
1048
1049 if stream.is_writable() {
1051 self.error(cx, &stream, error, global);
1053 }
1054 }
1055
1056 fn error(
1058 &self,
1059 cx: &mut JSContext,
1060 stream: &WritableStream,
1061 e: SafeHandleValue,
1062 global: &GlobalScope,
1063 ) {
1064 assert!(stream.is_writable());
1069
1070 self.clear_algorithms();
1072
1073 stream.start_erroring(cx, global, e);
1075 }
1076}
1077
1078impl WritableStreamDefaultControllerMethods<crate::DomTypeHolder>
1079 for WritableStreamDefaultController
1080{
1081 fn Error(&self, cx: &mut CurrentRealm, e: SafeHandleValue) {
1083 let Some(stream) = self.stream.get() else {
1085 unreachable!("Controller should have a stream");
1086 };
1087
1088 if !stream.is_writable() {
1090 return;
1091 }
1092
1093 let global = GlobalScope::from_current_realm(cx);
1094
1095 self.error(cx, &stream, e, &global);
1097 }
1098
1099 fn Signal(&self) -> DomRoot<AbortSignal> {
1101 self.abort_controller.signal()
1103 }
1104}