1use std::cell::RefCell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::jsval::UndefinedValue;
10use js::realm::CurrentRealm;
11use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
12use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
13
14use crate::dom::bindings::codegen::Bindings::WritableStreamDefaultWriterBinding::WritableStreamDefaultWriterMethods;
15use crate::dom::bindings::error::{Error, ErrorToJsval};
16use crate::dom::bindings::reflector::DomGlobal;
17use crate::dom::bindings::root::{DomRoot, MutNullableDom};
18use crate::dom::globalscope::GlobalScope;
19use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
20use crate::dom::stream::writablestream::WritableStream;
21
22#[dom_struct]
24pub struct WritableStreamDefaultWriter {
25 reflector_: Reflector,
26
27 ready_promise: RefCell<TracedPromise>,
28
29 closed_promise: RefCell<TracedPromise>,
31
32 stream: MutNullableDom<WritableStream>,
34}
35
36impl WritableStreamDefaultWriter {
37 fn new_inherited(
40 closed_promise: &RootedPromise,
41 ready_promise: &RootedPromise,
42 ) -> WritableStreamDefaultWriter {
43 WritableStreamDefaultWriter {
44 reflector_: Reflector::new(),
45 stream: Default::default(),
46 closed_promise: RefCell::new(closed_promise.to_traced()),
47 ready_promise: RefCell::new(ready_promise.to_traced()),
48 }
49 }
50
51 pub(crate) fn new(
52 cx: &mut CurrentRealm,
53 global: &GlobalScope,
54 proto: Option<SafeHandleObject>,
55 ) -> DomRoot<WritableStreamDefaultWriter> {
56 let closed_promise = Promise::new_in_realm_rooted(cx);
57 let ready_promise = Promise::new_in_realm_rooted(cx);
58 reflect_dom_object_with_proto(
59 cx,
60 Box::new(WritableStreamDefaultWriter::new_inherited(
61 &closed_promise,
62 &ready_promise,
63 )),
64 global,
65 proto,
66 )
67 }
68
69 pub(crate) fn setup(&self, cx: &mut JSContext, stream: &WritableStream) -> Result<(), Error> {
72 if stream.is_locked() {
74 return Err(Error::Type(c"Stream is locked".to_owned()));
75 }
76
77 self.stream.set(Some(stream));
79
80 stream.set_writer(Some(self));
82
83 if stream.is_writable() {
87 if !stream.close_queued_or_in_flight() && stream.get_backpressure() {
90 } else {
93 self.ready_promise.borrow().resolve_native(cx, &());
96 }
97
98 return Ok(());
101 }
102
103 if stream.is_erroring() {
105 rooted!(&in(cx) let mut error = UndefinedValue());
106 stream.get_stored_error(error.handle_mut());
107
108 let ready_promise = self.ready_promise.borrow();
112 ready_promise.reject_native(cx, &error.handle());
113 ready_promise.set_promise_is_handled(cx);
114
115 return Ok(());
118 }
119
120 if stream.is_closed() {
122 self.ready_promise.borrow().resolve_native(cx, &());
125
126 self.closed_promise.borrow().resolve_native(cx, &());
129 return Ok(());
130 }
131
132 assert!(stream.is_errored());
135
136 rooted!(&in(cx) let mut error = UndefinedValue());
138 stream.get_stored_error(error.handle_mut());
139
140 let ready_promise = self.ready_promise.borrow();
144 ready_promise.reject_native(cx, &error.handle());
145 ready_promise.set_promise_is_handled(cx);
146
147 let ready_promise = self.closed_promise.borrow();
151 ready_promise.reject_native(cx, &error.handle());
152 ready_promise.set_promise_is_handled(cx);
153
154 Ok(())
155 }
156
157 pub(crate) fn reject_closed_promise_with_stored_error(
158 &self,
159 cx: &mut JSContext,
160 error: &SafeHandleValue,
161 ) {
162 self.closed_promise.borrow().reject_native(cx, error);
163 }
164
165 pub(crate) fn set_close_promise_is_handled(&self, cx: &mut JSContext) {
166 self.closed_promise.borrow().set_promise_is_handled(cx);
167 }
168
169 pub(crate) fn set_ready_promise(&self, promise: &RootedPromise) {
170 *self.ready_promise.borrow_mut() = promise.to_traced();
171 }
172
173 pub(crate) fn resolve_ready_promise_with_undefined(&self, cx: &mut JSContext) {
174 self.ready_promise.borrow().resolve_native(cx, &());
175 }
176
177 pub(crate) fn resolve_closed_promise_with_undefined(&self, cx: &mut JSContext) {
178 self.closed_promise.borrow().resolve_native(cx, &());
179 }
180
181 pub(crate) fn ensure_ready_promise_rejected(
183 &self,
184 cx: &mut JSContext,
185 global: &GlobalScope,
186 error: SafeHandleValue,
187 ) {
188 let is_pending = self.ready_promise.borrow().is_pending();
189
190 if is_pending {
192 let ready_promise = self.ready_promise.borrow();
193
194 ready_promise.reject_native(cx, &error);
196
197 ready_promise.set_promise_is_handled(cx);
199 } else {
200 let promise = Promise::new_rejected_rooted(cx, global, error);
202
203 promise.set_promise_is_handled(cx);
205 *self.ready_promise.borrow_mut() = promise.to_traced();
206 }
207 }
208
209 fn ensure_closed_promise_rejected(
211 &self,
212 cx: &mut JSContext,
213 global: &GlobalScope,
214 error: SafeHandleValue,
215 ) {
216 let is_pending = self.closed_promise.borrow().is_pending();
217
218 if is_pending {
220 let closed_promise = self.closed_promise.borrow();
221
222 closed_promise.reject_native(cx, &error);
223
224 closed_promise.set_promise_is_handled(cx);
226 } else {
227 let promise = Promise::new_rejected_rooted(cx, global, error);
229
230 promise.set_promise_is_handled(cx);
232 *self.closed_promise.borrow_mut() = promise.to_traced();
233 }
234 }
235
236 fn abort(
238 &self,
239 cx: &mut CurrentRealm,
240 global: &GlobalScope,
241 reason: SafeHandleValue,
242 ) -> RootedPromise {
243 let Some(stream) = self.stream.get() else {
245 unreachable!("Stream should be set.");
247 };
248
249 stream.abort(cx, global, reason)
251 }
252
253 fn close(&self, cx: &mut JSContext, global: &GlobalScope) -> RootedPromise {
255 let Some(stream) = self.stream.get() else {
257 unreachable!("Stream should be set.");
259 };
260
261 stream.close(cx, global)
263 }
264
265 pub(crate) fn write(
267 &self,
268 cx: &mut JSContext,
269 global: &GlobalScope,
270 chunk: SafeHandleValue,
271 ) -> RootedPromise {
272 let Some(stream) = self.stream.get() else {
274 unreachable!("Stream should be set.");
276 };
277
278 let Some(controller) = stream.get_controller() else {
281 unreachable!("Controller should be set.");
282 };
283
284 let chunk_size = controller.get_chunk_size(cx, global, chunk);
286
287 if !self
290 .stream
291 .get()
292 .is_some_and(|current_stream| current_stream == stream)
293 {
294 let promise = Promise::new_rooted(cx, global);
295 promise.reject_error(
296 cx,
297 Error::Type(c"Stream is not equal to writer stream".to_owned()),
298 );
299 return promise;
300 }
301
302 if stream.is_errored() {
305 rooted!(&in(cx) let mut error = UndefinedValue());
307 stream.get_stored_error(error.handle_mut());
308 let promise = Promise::new_rooted(cx, global);
309 promise.reject_native(cx, &error.handle());
310 return promise;
311 }
312
313 if stream.close_queued_or_in_flight() || stream.is_closed() {
316 let promise = Promise::new_rooted(cx, global);
319 promise.reject_error(
320 cx,
321 Error::Type(c"Stream has been closed, or has close queued or in-flight".to_owned()),
322 );
323 return promise;
324 }
325
326 if stream.is_erroring() {
328 rooted!(&in(cx) let mut error = UndefinedValue());
330 stream.get_stored_error(error.handle_mut());
331 let promise = Promise::new_rooted(cx, global);
332 promise.reject_native(cx, &error.handle());
333 return promise;
334 }
335
336 assert!(stream.is_writable());
338
339 let promise = stream.add_write_request(cx, global);
341
342 controller.write(cx, global, chunk, chunk_size);
344
345 promise
347 }
348
349 pub(crate) fn release(&self, cx: &mut JSContext, global: &GlobalScope) {
351 let Some(stream) = self.stream.get() else {
353 unreachable!("Stream should be set.");
355 };
356
357 assert!(stream.get_writer().is_some_and(|writer| &*writer == self));
359
360 let released_error = Error::Type(c"Writer has been released".to_owned());
362
363 rooted!(&in(cx) let mut error = UndefinedValue());
365 released_error.to_jsval(cx, global, error.handle_mut());
366
367 self.ensure_ready_promise_rejected(cx, global, error.handle());
369
370 self.ensure_closed_promise_rejected(cx, global, error.handle());
372
373 stream.set_writer(None);
375
376 self.stream.set(None);
378 }
379
380 pub(crate) fn close_with_error_propagation(
382 &self,
383 cx: &mut JSContext,
384 global: &GlobalScope,
385 ) -> RootedPromise {
386 let Some(stream) = self.stream.get() else {
388 unreachable!("Stream should be set.");
390 };
391
392 if stream.close_queued_or_in_flight() || stream.is_closed() {
398 let promise = Promise::new_rooted(cx, global);
400 promise.resolve_native(cx, &());
401 return promise;
402 }
403
404 if stream.is_errored() {
406 rooted!(&in(cx) let mut error = UndefinedValue());
408 stream.get_stored_error(error.handle_mut());
409 let promise = Promise::new_rooted(cx, global);
410 promise.reject_native(cx, &error.handle());
411 return promise;
412 }
413
414 assert!(stream.is_writable() || stream.is_erroring());
416
417 self.close(cx, global)
419 }
420
421 pub(crate) fn get_stream(&self) -> Option<DomRoot<WritableStream>> {
422 self.stream.get()
423 }
424}
425
426impl WritableStreamDefaultWriterMethods<crate::DomTypeHolder> for WritableStreamDefaultWriter {
427 fn Closed(&self) -> RootedPromise {
429 return self.closed_promise.borrow().root();
431 }
432
433 fn GetDesiredSize(&self) -> Result<Option<f64>, Error> {
435 let Some(stream) = self.stream.get() else {
437 return Err(Error::Type(c"Stream is undefined".to_owned()));
438 };
439
440 Ok(stream.get_desired_size())
442 }
443
444 fn Ready(&self) -> RootedPromise {
446 return self.ready_promise.borrow().root();
448 }
449
450 fn Abort(&self, cx: &mut CurrentRealm, reason: SafeHandleValue) -> RootedPromise {
452 let global = GlobalScope::from_current_realm(cx);
453
454 if self.stream.get().is_none() {
456 let promise = Promise::new_rooted(cx, &global);
458 promise.reject_error(cx, Error::Type(c"Stream is undefined".to_owned()));
459 return promise;
460 }
461
462 self.abort(cx, &global, reason)
464 }
465
466 fn Close(&self, cx: &mut CurrentRealm) -> RootedPromise {
468 let global = GlobalScope::from_current_realm(cx);
469 let promise = Promise::new_rooted(cx, &global);
470
471 let Some(stream) = self.stream.get() else {
473 promise.reject_error(cx, Error::Type(c"Stream is undefined".to_owned()));
476 return promise;
477 };
478
479 if stream.close_queued_or_in_flight() {
481 promise.reject_error(
483 cx,
484 Error::Type(c"Stream has closed queued or in-flight".to_owned()),
485 );
486 return promise;
487 }
488
489 self.close(cx, &global)
490 }
491
492 fn ReleaseLock(&self, cx: &mut JSContext) {
494 let Some(stream) = self.stream.get() else {
496 return;
498 };
499
500 assert!(stream.get_writer().is_some());
502
503 let global = self.global();
504
505 self.release(cx, &global);
507 }
508
509 fn Write(&self, cx: &mut CurrentRealm, chunk: SafeHandleValue) -> RootedPromise {
511 let global = GlobalScope::from_current_realm(cx);
512
513 if self.stream.get().is_none() {
515 let promise = Promise::new_rooted(cx, &global);
517 promise.reject_error(cx, Error::Type(c"Stream is undefined".to_owned()));
518 return promise;
519 }
520
521 self.write(cx, &global, chunk)
523 }
524
525 fn Constructor(
527 cx: &mut CurrentRealm,
528 global: &GlobalScope,
529 proto: Option<SafeHandleObject>,
530 stream: &WritableStream,
531 ) -> Result<DomRoot<WritableStreamDefaultWriter>, Error> {
532 let writer = WritableStreamDefaultWriter::new(cx, global, proto);
533
534 writer.setup(cx, stream)?;
536
537 Ok(writer)
538 }
539}