Skip to main content

script/dom/stream/
writablestream.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, RefCell};
6use std::collections::VecDeque;
7use std::mem;
8use std::ptr::{self};
9use std::rc::Rc;
10
11use dom_struct::dom_struct;
12use js::context::JSContext;
13use js::conversions::ToJSValConvertible;
14use js::jsapi::{Heap, JSObject};
15use js::jsval::{JSVal, ObjectValue, UndefinedValue};
16use js::realm::CurrentRealm;
17use js::rust::{
18    HandleObject as SafeHandleObject, HandleValue as SafeHandleValue,
19    MutableHandleValue as SafeMutableHandleValue,
20};
21use rustc_hash::FxHashMap;
22use script_bindings::cell::DomRefCell;
23use script_bindings::codegen::GenericBindings::MessagePortBinding::MessagePortMethods;
24use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
25use script_bindings::structuredclone::StructuredData;
26use servo_base::id::{MessagePortId, MessagePortIndex};
27use servo_constellation_traits::MessagePortImpl;
28
29use crate::dom::bindings::callback::RootedCallback;
30use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::{
31    QueuingStrategy, QueuingStrategySize,
32};
33use crate::dom::bindings::codegen::Bindings::UnderlyingSinkBinding::UnderlyingSink;
34use crate::dom::bindings::codegen::Bindings::WritableStreamBinding::WritableStreamMethods;
35use crate::dom::bindings::conversions::ConversionResult;
36use crate::dom::bindings::error::{Error, Fallible};
37use crate::dom::bindings::reflector::DomGlobal;
38use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
39use crate::dom::bindings::transferable::Transferable;
40use crate::dom::domexception::{DOMErrorName, DOMException};
41use crate::dom::globalscope::GlobalScope;
42use crate::dom::messageport::MessagePort;
43use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
44use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
45use crate::dom::readablestream::{ReadableStream, get_type_and_value_from_message};
46use crate::dom::stream::countqueuingstrategy::{extract_high_water_mark, extract_size_algorithm};
47use crate::dom::stream::writablestreamdefaultcontroller::{
48    UnderlyingSinkTypeRef, WritableStreamDefaultController,
49};
50use crate::dom::stream::writablestreamdefaultwriter::WritableStreamDefaultWriter;
51use crate::realms::enter_auto_realm;
52
53impl js::gc::Rootable for AbortAlgorithmFulfillmentHandler {}
54
55/// The fulfillment handler for the abort steps of
56/// <https://streams.spec.whatwg.org/#writable-stream-finish-erroring>
57#[derive(JSTraceable, MallocSizeOf)]
58#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
59struct AbortAlgorithmFulfillmentHandler {
60    stream: Dom<WritableStream>,
61    abort_request_promise: TracedPromise,
62}
63
64impl Callback for AbortAlgorithmFulfillmentHandler {
65    fn callback(&self, cx: &mut CurrentRealm, _v: SafeHandleValue) {
66        // Resolve abortRequest’s promise with undefined.
67        self.abort_request_promise.resolve_native(cx, &());
68
69        // Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
70        self.stream
71            .as_rooted()
72            .reject_close_and_closed_promise_if_needed(cx);
73    }
74}
75
76impl js::gc::Rootable for AbortAlgorithmRejectionHandler {}
77
78/// The rejection handler for the abort steps of
79/// <https://streams.spec.whatwg.org/#writable-stream-finish-erroring>
80#[derive(JSTraceable, MallocSizeOf)]
81#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
82struct AbortAlgorithmRejectionHandler {
83    stream: Dom<WritableStream>,
84    abort_request_promise: TracedPromise,
85}
86
87impl Callback for AbortAlgorithmRejectionHandler {
88    fn callback(&self, cx: &mut CurrentRealm, reason: SafeHandleValue) {
89        // Reject abortRequest’s promise with reason.
90        self.abort_request_promise.reject_native(cx, &reason);
91
92        // Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
93        self.stream
94            .as_rooted()
95            .reject_close_and_closed_promise_if_needed(cx);
96    }
97}
98
99impl js::gc::Rootable for PendingAbortRequest {}
100
101/// <https://streams.spec.whatwg.org/#pending-abort-request>
102#[derive(JSTraceable, MallocSizeOf)]
103#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
104struct PendingAbortRequest {
105    /// <https://streams.spec.whatwg.org/#pending-abort-request-promise>
106    promise: TracedPromise,
107
108    /// <https://streams.spec.whatwg.org/#pending-abort-request-reason>
109    #[ignore_malloc_size_of = "mozjs"]
110    reason: Box<Heap<JSVal>>,
111
112    /// <https://streams.spec.whatwg.org/#pending-abort-request-was-already-erroring>
113    was_already_erroring: bool,
114}
115
116/// <https://streams.spec.whatwg.org/#writablestream-state>
117#[derive(Clone, Copy, Debug, Default, JSTraceable, MallocSizeOf)]
118pub(crate) enum WritableStreamState {
119    #[default]
120    Writable,
121    Closed,
122    Erroring,
123    Errored,
124}
125
126/// <https://streams.spec.whatwg.org/#ws-class>
127#[dom_struct]
128pub struct WritableStream {
129    reflector_: Reflector,
130
131    /// <https://streams.spec.whatwg.org/#writablestream-backpressure>
132    backpressure: Cell<bool>,
133
134    /// <https://streams.spec.whatwg.org/#writablestream-closerequest>
135    close_request: DomRefCell<Option<TracedPromise>>,
136
137    /// <https://streams.spec.whatwg.org/#writablestream-controller>
138    controller: MutNullableDom<WritableStreamDefaultController>,
139
140    /// <https://streams.spec.whatwg.org/#writablestream-detached>
141    detached: Cell<bool>,
142
143    /// <https://streams.spec.whatwg.org/#writablestream-inflightwriterequest>
144    in_flight_write_request: DomRefCell<Option<TracedPromise>>,
145
146    /// <https://streams.spec.whatwg.org/#writablestream-inflightcloserequest>
147    in_flight_close_request: DomRefCell<Option<TracedPromise>>,
148
149    /// <https://streams.spec.whatwg.org/#writablestream-pendingabortrequest>
150    pending_abort_request: DomRefCell<Option<PendingAbortRequest>>,
151
152    /// <https://streams.spec.whatwg.org/#writablestream-state>
153    state: Cell<WritableStreamState>,
154
155    /// <https://streams.spec.whatwg.org/#writablestream-storederror>
156    #[ignore_malloc_size_of = "mozjs"]
157    stored_error: Heap<JSVal>,
158
159    /// <https://streams.spec.whatwg.org/#writablestream-writer>
160    writer: MutNullableDom<WritableStreamDefaultWriter>,
161
162    /// <https://streams.spec.whatwg.org/#writablestream-writerequests>
163    write_requests: DomRefCell<VecDeque<TracedPromise>>,
164}
165
166impl WritableStream {
167    /// <https://streams.spec.whatwg.org/#initialize-writable-stream>
168    fn new_inherited() -> WritableStream {
169        WritableStream {
170            reflector_: Reflector::new(),
171            backpressure: Default::default(),
172            close_request: Default::default(),
173            controller: Default::default(),
174            detached: Default::default(),
175            in_flight_write_request: Default::default(),
176            in_flight_close_request: Default::default(),
177            pending_abort_request: Default::default(),
178            state: Default::default(),
179            stored_error: Default::default(),
180            writer: Default::default(),
181            write_requests: Default::default(),
182        }
183    }
184
185    pub(crate) fn new_with_proto(
186        cx: &mut JSContext,
187        global: &GlobalScope,
188        proto: Option<SafeHandleObject>,
189    ) -> DomRoot<WritableStream> {
190        reflect_dom_object_with_proto(cx, Box::new(WritableStream::new_inherited()), global, proto)
191    }
192
193    /// Used as part of
194    /// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
195    pub(crate) fn assert_no_controller(&self) {
196        assert!(self.controller.get().is_none());
197    }
198
199    /// Used as part of
200    /// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller>
201    pub(crate) fn set_default_controller(&self, controller: &WritableStreamDefaultController) {
202        self.controller.set(Some(controller));
203    }
204
205    pub(crate) fn get_default_controller(&self) -> DomRoot<WritableStreamDefaultController> {
206        self.controller.get().expect("Controller should be set.")
207    }
208
209    pub(crate) fn is_writable(&self) -> bool {
210        matches!(self.state.get(), WritableStreamState::Writable)
211    }
212
213    pub(crate) fn is_erroring(&self) -> bool {
214        matches!(self.state.get(), WritableStreamState::Erroring)
215    }
216
217    pub(crate) fn is_errored(&self) -> bool {
218        matches!(self.state.get(), WritableStreamState::Errored)
219    }
220
221    pub(crate) fn is_closed(&self) -> bool {
222        matches!(self.state.get(), WritableStreamState::Closed)
223    }
224
225    pub(crate) fn has_in_flight_write_request(&self) -> bool {
226        self.in_flight_write_request.borrow().is_some()
227    }
228
229    /// <https://streams.spec.whatwg.org/#writable-stream-has-operation-marked-in-flight>
230    pub(crate) fn has_operations_marked_inflight(&self) -> bool {
231        let in_flight_write_requested = self.in_flight_write_request.borrow().is_some();
232        let in_flight_close_requested = self.in_flight_close_request.borrow().is_some();
233
234        in_flight_write_requested || in_flight_close_requested
235    }
236
237    /// <https://streams.spec.whatwg.org/#writablestream-storederror>
238    pub(crate) fn get_stored_error(&self, mut handle_mut: SafeMutableHandleValue) {
239        handle_mut.set(self.stored_error.get());
240    }
241
242    /// <https://streams.spec.whatwg.org/#writable-stream-finish-erroring>
243    pub(crate) fn finish_erroring(&self, cx: &mut JSContext, global: &GlobalScope) {
244        // Assert: stream.[[state]] is "erroring".
245        assert!(self.is_erroring());
246
247        // Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is false.
248        assert!(!self.has_operations_marked_inflight());
249
250        // Set stream.[[state]] to "errored".
251        self.state.set(WritableStreamState::Errored);
252
253        // Perform ! stream.[[controller]].[[ErrorSteps]]().
254        let Some(controller) = self.controller.get() else {
255            unreachable!("Stream should have a controller.");
256        };
257        controller.perform_error_steps();
258
259        // Let storedError be stream.[[storedError]].
260        rooted!(&in(cx) let mut stored_error = UndefinedValue());
261        self.get_stored_error(stored_error.handle_mut());
262
263        // For each writeRequest of stream.[[writeRequests]]:
264        rooted!(&in(cx) let write_requests = mem::take(&mut *self.write_requests.borrow_mut()));
265        for request in write_requests.iter() {
266            // Reject writeRequest with storedError.
267            request.reject(cx, stored_error.handle());
268        }
269
270        // Set stream.[[writeRequests]] to an empty list.
271        // Done above with `drain`.
272
273        // If stream.[[pendingAbortRequest]] is undefined,
274        if self.pending_abort_request.borrow().is_none() {
275            // Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
276            self.reject_close_and_closed_promise_if_needed(cx);
277
278            // Return.
279            return;
280        }
281
282        // Let abortRequest be stream.[[pendingAbortRequest]].
283        // Set stream.[[pendingAbortRequest]] to undefined.
284        rooted!(&in(cx) let pending_abort_request = self.pending_abort_request.borrow_mut().take());
285        if let Some(pending_abort_request) = &*pending_abort_request {
286            // If abortRequest’s was already erroring is true,
287            if pending_abort_request.was_already_erroring {
288                // Reject abortRequest’s promise with storedError.
289                pending_abort_request
290                    .promise
291                    .reject(cx, stored_error.handle());
292
293                // Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
294                self.reject_close_and_closed_promise_if_needed(cx);
295
296                // Return.
297                return;
298            }
299
300            // Let promise be ! stream.[[controller]].[[AbortSteps]](abortRequest’s reason).
301            rooted!(&in(cx) let mut reason = UndefinedValue());
302            reason.set(pending_abort_request.reason.get());
303            let promise = controller.abort_steps(cx, global, reason.handle());
304
305            // Upon fulfillment of promise,
306            rooted!(&in(cx) let mut fulfillment_handler = Some(AbortAlgorithmFulfillmentHandler {
307                stream: Dom::from_ref(self),
308                abort_request_promise: pending_abort_request.promise.clone(),
309            }));
310
311            // Upon rejection of promise with reason r,
312            rooted!(&in(cx) let mut rejection_handler = Some(AbortAlgorithmRejectionHandler {
313                stream: Dom::from_ref(self),
314                abort_request_promise: pending_abort_request.promise.clone(),
315            }));
316
317            let handler = PromiseNativeHandler::new(
318                cx,
319                global,
320                fulfillment_handler.take().map(|h| Box::new(h) as Box<_>),
321                rejection_handler.take().map(|h| Box::new(h) as Box<_>),
322            );
323
324            let mut realm = enter_auto_realm(cx, global);
325            let cx = &mut realm.current_realm();
326            promise.append_native_handler(cx, &handler);
327        }
328    }
329
330    /// <https://streams.spec.whatwg.org/#writable-stream-reject-close-and-closed-promise-if-needed>
331    fn reject_close_and_closed_promise_if_needed(&self, cx: &mut JSContext) {
332        // Assert: stream.[[state]] is "errored".
333        assert!(self.is_errored());
334
335        rooted!(&in(cx) let mut stored_error = UndefinedValue());
336        self.get_stored_error(stored_error.handle_mut());
337
338        // If stream.[[closeRequest]] is not undefined
339        rooted!(&in(cx) let close_request = self.close_request.borrow_mut().take());
340        if let Some(ref close_request) = *close_request {
341            // Assert: stream.[[inFlightCloseRequest]] is undefined.
342            assert!(self.in_flight_close_request.borrow().is_none());
343
344            // Reject stream.[[closeRequest]] with stream.[[storedError]].
345            close_request.reject_native(cx, &stored_error.handle())
346
347            // Set stream.[[closeRequest]] to undefined.
348            // Done with `take` above.
349        }
350
351        // Let writer be stream.[[writer]].
352        // If writer is not undefined,
353        if let Some(writer) = self.writer.get() {
354            // Reject writer.[[closedPromise]] with stream.[[storedError]].
355            writer.reject_closed_promise_with_stored_error(cx, &stored_error.handle());
356
357            // Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
358            writer.set_close_promise_is_handled(cx);
359        }
360    }
361
362    /// <https://streams.spec.whatwg.org/#writable-stream-close-queued-or-in-flight>
363    pub(crate) fn close_queued_or_in_flight(&self) -> bool {
364        let close_requested = self.close_request.borrow().is_some();
365        let in_flight_close_requested = self.in_flight_close_request.borrow().is_some();
366
367        close_requested || in_flight_close_requested
368    }
369
370    /// <https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write>
371    pub(crate) fn finish_in_flight_write(&self, cx: &mut JSContext) {
372        rooted!(&in(cx) let in_flight_write_request = self.in_flight_write_request.borrow_mut().take());
373        let Some(ref in_flight_write_request) = *in_flight_write_request else {
374            // Assert: stream.[[inFlightWriteRequest]] is not undefined.
375            unreachable!("Stream should have a write request");
376        };
377
378        // Resolve stream.[[inFlightWriteRequest]] with undefined.
379        in_flight_write_request.resolve_native(cx, &());
380
381        // Set stream.[[inFlightWriteRequest]] to undefined.
382        // Done above with `take`.
383    }
384
385    /// <https://streams.spec.whatwg.org/#writable-stream-start-erroring>
386    pub(crate) fn start_erroring(
387        &self,
388        cx: &mut JSContext,
389        global: &GlobalScope,
390        error: SafeHandleValue,
391    ) {
392        // Assert: stream.[[storedError]] is undefined.
393        assert!(self.stored_error.get().is_undefined());
394
395        // Assert: stream.[[state]] is "writable".
396        assert!(self.is_writable());
397
398        // Let controller be stream.[[controller]].
399        let Some(controller) = self.controller.get() else {
400            // Assert: controller is not undefined.
401            unreachable!("Stream should have a controller.");
402        };
403
404        // Set stream.[[state]] to "erroring".
405        self.state.set(WritableStreamState::Erroring);
406
407        // Set stream.[[storedError]] to reason.
408        self.stored_error.set(*error);
409
410        // Let writer be stream.[[writer]].
411        if let Some(writer) = self.writer.get() {
412            // If writer is not undefined, perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected
413            writer.ensure_ready_promise_rejected(cx, global, error);
414        }
415
416        // If ! WritableStreamHasOperationMarkedInFlight(stream) is false and controller.[[started]] is true
417        if !self.has_operations_marked_inflight() && controller.started() {
418            // perform ! WritableStreamFinishErroring
419            self.finish_erroring(cx, global);
420        }
421    }
422
423    /// <https://streams.spec.whatwg.org/#writable-stream-deal-with-rejection>
424    pub(crate) fn deal_with_rejection(
425        &self,
426        cx: &mut JSContext,
427        global: &GlobalScope,
428        error: SafeHandleValue,
429    ) {
430        // Let state be stream.[[state]].
431
432        // If state is "writable",
433        if self.is_writable() {
434            // Perform ! WritableStreamStartErroring(stream, error).
435            self.start_erroring(cx, global, error);
436
437            // Return.
438            return;
439        }
440
441        // Assert: state is "erroring".
442        assert!(self.is_erroring());
443
444        // Perform ! WritableStreamFinishErroring(stream).
445        self.finish_erroring(cx, global);
446    }
447
448    /// <https://streams.spec.whatwg.org/#writable-stream-mark-first-write-request-in-flight>
449    pub(crate) fn mark_first_write_request_in_flight(&self) {
450        let mut in_flight_write_request = self.in_flight_write_request.borrow_mut();
451        let mut write_requests = self.write_requests.borrow_mut();
452
453        // Assert: stream.[[inFlightWriteRequest]] is undefined.
454        assert!(in_flight_write_request.is_none());
455
456        // Assert: stream.[[writeRequests]] is not empty.
457        assert!(!write_requests.is_empty());
458
459        // Let writeRequest be stream.[[writeRequests]][0].
460        // Remove writeRequest from stream.[[writeRequests]].
461        // Set stream.[[inFlightWriteRequest]] to writeRequest.
462        *in_flight_write_request = write_requests.pop_front();
463    }
464
465    /// <https://streams.spec.whatwg.org/#writable-stream-mark-close-request-in-flight>
466    pub(crate) fn mark_close_request_in_flight(&self) {
467        let mut in_flight_close_request = self.in_flight_close_request.borrow_mut();
468        let mut close_request = self.close_request.borrow_mut();
469
470        // Assert: stream.[[inFlightCloseRequest]] is undefined.
471        assert!(in_flight_close_request.is_none());
472
473        // Assert: stream.[[closeRequest]] is not undefined.
474        assert!(close_request.is_some());
475
476        // Let closeRequest be stream.[[closeRequest]].
477        // Set stream.[[closeRequest]] to undefined.
478        // Set stream.[[inFlightCloseRequest]] to closeRequest.
479        *in_flight_close_request = close_request.take();
480    }
481
482    /// <https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close>
483    pub(crate) fn finish_in_flight_close(&self, cx: &mut JSContext) {
484        rooted!(&in(cx) let in_flight_close_request = self.in_flight_close_request.borrow_mut().take());
485        let Some(ref in_flight_close_request) = *in_flight_close_request else {
486            // Assert: stream.[[inFlightCloseRequest]] is not undefined.
487            unreachable!("in_flight_close_request must be Some");
488        };
489
490        // Resolve stream.[[inFlightCloseRequest]] with undefined.
491        in_flight_close_request.resolve_native(cx, &());
492
493        // Set stream.[[inFlightCloseRequest]] to undefined.
494        // Done with take above.
495
496        // Assert: stream.[[state]] is "writable" or "erroring".
497        assert!(self.is_writable() || self.is_erroring());
498
499        // If state is "erroring",
500        if self.is_erroring() {
501            // Set stream.[[storedError]] to undefined.
502            self.stored_error.set(UndefinedValue());
503
504            // If stream.[[pendingAbortRequest]] is not undefined,
505            rooted!(&in(cx) let pending_abort_request = self.pending_abort_request.borrow_mut().take());
506            if let Some(pending_abort_request) = &*pending_abort_request {
507                // Resolve stream.[[pendingAbortRequest]]'s promise with undefined.
508                pending_abort_request.promise.resolve_native(cx, &());
509
510                // Set stream.[[pendingAbortRequest]] to undefined.
511                // Done above with `take`.
512            }
513        }
514
515        // Set stream.[[state]] to "closed".
516        self.state.set(WritableStreamState::Closed);
517
518        // Let writer be stream.[[writer]].
519        if let Some(writer) = self.writer.get() {
520            // If writer is not undefined,
521            // resolve writer.[[closedPromise]] with undefined.
522            writer.resolve_closed_promise_with_undefined(cx);
523        }
524
525        // Assert: stream.[[pendingAbortRequest]] is undefined.
526        assert!(self.pending_abort_request.borrow().is_none());
527
528        // Assert: stream.[[storedError]] is undefined.
529        assert!(self.stored_error.get().is_undefined());
530    }
531
532    /// <https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close-with-error>
533    pub(crate) fn finish_in_flight_close_with_error(
534        &self,
535        cx: &mut JSContext,
536        global: &GlobalScope,
537        error: SafeHandleValue,
538    ) {
539        rooted!(&in(cx) let in_flight_close_request = self.in_flight_close_request.borrow_mut().take());
540        let Some(ref in_flight_close_request) = *in_flight_close_request else {
541            // Assert: stream.[[inFlightCloseRequest]] is not undefined.
542            unreachable!("Inflight close request must be defined.");
543        };
544
545        // Reject stream.[[inFlightCloseRequest]] with error.
546        in_flight_close_request.reject_native(cx, &error);
547
548        // Set stream.[[inFlightCloseRequest]] to undefined.
549        // Done above with `take`.
550
551        // Assert: stream.[[state]] is "writable" or "erroring".
552        assert!(self.is_erroring() || self.is_writable());
553
554        // If stream.[[pendingAbortRequest]] is not undefined,
555        rooted!(&in(cx) let pending_abort_request = self.pending_abort_request.borrow_mut().take());
556        if let Some(pending_abort_request) = &*pending_abort_request {
557            // Reject stream.[[pendingAbortRequest]]'s promise with error.
558            pending_abort_request.promise.reject_native(cx, &error);
559
560            // Set stream.[[pendingAbortRequest]] to undefined.
561            // Done above with `take`.
562        }
563
564        // Perform ! WritableStreamDealWithRejection(stream, error).
565        self.deal_with_rejection(cx, global, error);
566    }
567
568    /// <https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write-with-error>
569    pub(crate) fn finish_in_flight_write_with_error(
570        &self,
571        cx: &mut JSContext,
572        global: &GlobalScope,
573        error: SafeHandleValue,
574    ) {
575        rooted!(&in(cx) let in_flight_write_request = self.in_flight_write_request.borrow_mut().take());
576        let Some(ref in_flight_write_request) = *in_flight_write_request else {
577            // Assert: stream.[[inFlightWriteRequest]] is not undefined.
578            unreachable!("Inflight write request must be defined.");
579        };
580
581        // Reject stream.[[inFlightWriteRequest]] with error.
582        in_flight_write_request.reject_native(cx, &error);
583
584        // Set stream.[[inFlightWriteRequest]] to undefined.
585        // Done above with `take`.
586
587        // Assert: stream.[[state]] is "writable" or "erroring".
588        assert!(self.is_erroring() || self.is_writable());
589
590        // Perform ! WritableStreamDealWithRejection(stream, error).
591        self.deal_with_rejection(cx, global, error);
592    }
593
594    pub(crate) fn get_writer(&self) -> Option<DomRoot<WritableStreamDefaultWriter>> {
595        self.writer.get()
596    }
597
598    pub(crate) fn set_writer(&self, writer: Option<&WritableStreamDefaultWriter>) {
599        self.writer.set(writer);
600    }
601
602    pub(crate) fn set_backpressure(&self, backpressure: bool) {
603        self.backpressure.set(backpressure);
604    }
605
606    pub(crate) fn get_backpressure(&self) -> bool {
607        self.backpressure.get()
608    }
609
610    /// <https://streams.spec.whatwg.org/#is-writable-stream-locked>
611    pub(crate) fn is_locked(&self) -> bool {
612        // If stream.[[writer]] is undefined, return false.
613        // Return true.
614        self.get_writer().is_some()
615    }
616
617    /// <https://streams.spec.whatwg.org/#writable-stream-add-write-request>
618    pub(crate) fn add_write_request(
619        &self,
620        cx: &mut JSContext,
621        global: &GlobalScope,
622    ) -> RootedPromise {
623        // Assert: ! IsWritableStreamLocked(stream) is true.
624        assert!(self.is_locked());
625
626        // Assert: stream.[[state]] is "writable".
627        assert!(self.is_writable());
628
629        // Let promise be a new promise.
630        let promise = Promise::new(cx, global);
631
632        // Append promise to stream.[[writeRequests]].
633        self.write_requests
634            .borrow_mut()
635            .push_back(promise.to_traced());
636
637        // Return promise.
638        promise
639    }
640
641    // Returns the rooted controller of the stream, if any.
642    pub(crate) fn get_controller(&self) -> Option<DomRoot<WritableStreamDefaultController>> {
643        self.controller.get()
644    }
645
646    /// <https://streams.spec.whatwg.org/#writable-stream-abort>
647    pub(crate) fn abort(
648        &self,
649        cx: &mut CurrentRealm,
650        global: &GlobalScope,
651        provided_reason: SafeHandleValue,
652    ) -> RootedPromise {
653        // If stream.[[state]] is "closed" or "errored",
654        if self.is_closed() || self.is_errored() {
655            // return a promise resolved with undefined.
656            return Promise::new_resolved(cx, global, ());
657        }
658
659        // Signal abort on stream.[[controller]].[[abortController]] with reason.
660        self.get_controller()
661            .expect("Stream must have a controller.")
662            .signal_abort(cx, provided_reason);
663
664        // Let state be stream.[[state]].
665        let state = self.state.get();
666
667        // If state is "closed" or "errored", return a promise resolved with undefined.
668        if matches!(
669            state,
670            WritableStreamState::Closed | WritableStreamState::Errored
671        ) {
672            return Promise::new_resolved(cx, global, ());
673        }
674
675        // If stream.[[pendingAbortRequest]] is not undefined,
676        if self.pending_abort_request.borrow().is_some() {
677            // return stream.[[pendingAbortRequest]]'s promise.
678            return self
679                .pending_abort_request
680                .borrow()
681                .as_ref()
682                .expect("Pending abort request must be Some.")
683                .promise
684                .root(cx);
685        }
686
687        // Assert: state is "writable" or "erroring".
688        assert!(self.is_writable() || self.is_erroring());
689
690        // Let wasAlreadyErroring be false.
691        let mut was_already_erroring = false;
692        rooted!(&in(cx) let undefined_reason = UndefinedValue());
693
694        // If state is "erroring",
695        let reason = if self.is_erroring() {
696            // Set wasAlreadyErroring to true.
697            was_already_erroring = true;
698
699            // Set reason to undefined.
700            undefined_reason.handle()
701        } else {
702            // Use the provided reason.
703            provided_reason
704        };
705
706        // Let promise be a new promise.
707        let promise = Promise::new(cx, global);
708
709        // Set stream.[[pendingAbortRequest]] to a new pending abort request
710        // whose promise is promise,
711        // reason is reason,
712        // and was already erroring is wasAlreadyErroring.
713        *self.pending_abort_request.borrow_mut() = Some(PendingAbortRequest {
714            promise: promise.to_traced(),
715            reason: Heap::boxed(reason.get()),
716            was_already_erroring,
717        });
718
719        // If wasAlreadyErroring is false,
720        if !was_already_erroring {
721            // perform ! WritableStreamStartErroring(stream, reason)
722            self.start_erroring(cx, global, reason);
723        }
724
725        // Return promise.
726        promise
727    }
728
729    /// <https://streams.spec.whatwg.org/#writable-stream-close>
730    pub(crate) fn close(&self, cx: &mut JSContext, global: &GlobalScope) -> RootedPromise {
731        // Let state be stream.[[state]].
732        // If state is "closed" or "errored",
733        if self.is_closed() || self.is_errored() {
734            // return a promise rejected with a TypeError exception.
735            let promise = Promise::new(cx, global);
736            promise.reject_error(cx, Error::Type(c"Stream is closed or errored.".to_owned()));
737            return promise;
738        }
739
740        // Assert: state is "writable" or "erroring".
741        assert!(self.is_writable() || self.is_erroring());
742
743        // Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
744        assert!(!self.close_queued_or_in_flight());
745
746        // Let promise be a new promise.
747        let promise = Promise::new(cx, global);
748
749        // Set stream.[[closeRequest]] to promise.
750        *self.close_request.borrow_mut() = Some(promise.to_traced());
751
752        // Let writer be stream.[[writer]].
753        // If writer is not undefined,
754        if let Some(writer) = self.writer.get() {
755            // and stream.[[backpressure]] is true,
756            // and state is "writable",
757            if self.get_backpressure() && self.is_writable() {
758                // resolve writer.[[readyPromise]] with undefined.
759                writer.resolve_ready_promise_with_undefined(cx);
760            }
761        }
762
763        // Perform ! WritableStreamDefaultControllerClose(stream.[[controller]]).
764        let Some(controller) = self.controller.get() else {
765            unreachable!("Stream must have a controller.");
766        };
767        controller.close(cx, global);
768
769        // Return promise.
770        promise
771    }
772
773    /// <https://streams.spec.whatwg.org/#writable-stream-default-writer-get-desired-size>
774    /// Note: implement as a stream method, as opposed to a writer one, for convenience.
775    pub(crate) fn get_desired_size(&self) -> Option<f64> {
776        // Let stream be writer.[[stream]].
777        // Stream is `self`.
778
779        // Let state be stream.[[state]].
780        // If state is "errored" or "erroring", return null.
781        if self.is_errored() || self.is_erroring() {
782            return None;
783        }
784
785        // If state is "closed", return 0.
786        if self.is_closed() {
787            return Some(0.);
788        }
789
790        let Some(controller) = self.controller.get() else {
791            unreachable!("Stream must have a controller.");
792        };
793        Some(controller.get_desired_size())
794    }
795
796    /// <https://streams.spec.whatwg.org/#acquire-writable-stream-default-writer>
797    pub(crate) fn aquire_default_writer(
798        &self,
799        cx: &mut CurrentRealm,
800        global: &GlobalScope,
801    ) -> Result<DomRoot<WritableStreamDefaultWriter>, Error> {
802        // Let writer be a new WritableStreamDefaultWriter object.
803        let writer = WritableStreamDefaultWriter::new(cx, global, None);
804
805        // Perform ? SetUpWritableStreamDefaultWriter(writer, stream).
806        writer.setup(cx, self)?;
807
808        // Return writer.
809        Ok(writer)
810    }
811
812    /// <https://streams.spec.whatwg.org/#writable-stream-update-backpressure>
813    pub(crate) fn update_backpressure(
814        &self,
815        cx: &mut JSContext,
816        backpressure: bool,
817        global: &GlobalScope,
818    ) {
819        // Assert: stream.[[state]] is "writable".
820        self.is_writable();
821
822        // Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
823        assert!(!self.close_queued_or_in_flight());
824
825        // Let writer be stream.[[writer]].
826        let writer = self.get_writer();
827
828        if let Some(writer) = writer {
829            // If writer is not undefined
830            if backpressure != self.get_backpressure() {
831                // and backpressure is not stream.[[backpressure]],
832                if backpressure {
833                    // If backpressure is true, set writer.[[readyPromise]] to a new promise.
834                    let promise = Promise::new(cx, global);
835                    writer.set_ready_promise(&promise);
836                } else {
837                    // Otherwise,
838                    // Assert: backpressure is false.
839                    assert!(!backpressure);
840                    // Resolve writer.[[readyPromise]] with undefined.
841                    writer.resolve_ready_promise_with_undefined(cx);
842                }
843            }
844        }
845
846        // Set stream.[[backpressure]] to backpressure.
847        self.set_backpressure(backpressure);
848    }
849
850    /// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
851    pub(crate) fn setup_cross_realm_transform_writable(
852        &self,
853        cx: &mut JSContext,
854        port: &MessagePort,
855    ) {
856        let port_id = port.message_port_id();
857        let global = self.global();
858
859        // Perform ! InitializeWritableStream(stream).
860        // Done in `new_inherited`.
861
862        // Let sizeAlgorithm be an algorithm that returns 1.
863        // Re-ordered because of the need to pass it to `new`.
864        let size_algorithm = extract_size_algorithm(cx, &QueuingStrategy::default());
865
866        // Note: other algorithms defined in the controller at call site.
867
868        // Let backpressurePromise be a new promise.
869        let backpressure_promise = Promise::new(cx, &global);
870        rooted!(&in(cx) let backpressure_promise = RcHolder(Rc::new(RefCell::new(Some(backpressure_promise.to_traced())))));
871
872        // Let controller be a new WritableStreamDefaultController.
873        let controller = WritableStreamDefaultController::new(
874            cx,
875            &global,
876            UnderlyingSinkTypeRef::Transfer {
877                backpressure_promise: &backpressure_promise.0,
878                port,
879            },
880            1.0,
881            size_algorithm,
882        );
883
884        // Add a handler for port’s message event with the following steps:
885        // Add a handler for port’s messageerror event with the following steps:
886        rooted!(&in(cx) let cross_realm_transform_writable = CrossRealmTransformWritable {
887            controller: Dom::from_ref(&controller),
888            backpressure_promise: backpressure_promise.0.clone(),
889        });
890        global.note_cross_realm_transform_writable(&cross_realm_transform_writable, port_id);
891
892        // Enable port’s port message queue.
893        port.Start(cx);
894
895        // Perform ! SetUpWritableStreamDefaultController
896        controller
897            .setup(cx, &global, self)
898            .expect("Setup for transfer cannot fail");
899    }
900    /// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink>
901    #[allow(clippy::too_many_arguments)]
902    fn setup_from_underlying_sink(
903        &self,
904        cx: &mut JSContext,
905        global: &GlobalScope,
906        stream: &WritableStream,
907        underlying_sink_obj: SafeHandleObject,
908        underlying_sink: &UnderlyingSink,
909        strategy_hwm: f64,
910        strategy_size: RootedCallback<QueuingStrategySize>,
911    ) -> Result<(), Error> {
912        // Let controller be a new WritableStreamDefaultController.
913
914        // Let startAlgorithm be an algorithm that returns undefined.
915
916        // Let writeAlgorithm be an algorithm that returns a promise resolved with undefined.
917
918        // Let closeAlgorithm be an algorithm that returns a promise resolved with undefined.
919
920        // Let abortAlgorithm be an algorithm that returns a promise resolved with undefined.
921
922        // If underlyingSinkDict["start"] exists, then set startAlgorithm to an algorithm which
923        // returns the result of invoking underlyingSinkDict["start"] with argument
924        // list « controller », exception behavior "rethrow", and callback this value underlyingSink.
925
926        // If underlyingSinkDict["write"] exists, then set writeAlgorithm to an algorithm which
927        // takes an argument chunk and returns the result of invoking underlyingSinkDict["write"]
928        // with argument list « chunk, controller » and callback this value underlyingSink.
929
930        // If underlyingSinkDict["close"] exists, then set closeAlgorithm to an algorithm which
931        // returns the result of invoking underlyingSinkDict["close"] with argument
932        // list «» and callback this value underlyingSink.
933
934        // If underlyingSinkDict["abort"] exists, then set abortAlgorithm to an algorithm which
935        // takes an argument reason and returns the result of invoking underlyingSinkDict["abort"]
936        // with argument list « reason » and callback this value underlyingSink.
937        let controller = WritableStreamDefaultController::new(
938            cx,
939            global,
940            UnderlyingSinkTypeRef::new_js(
941                underlying_sink.abort.as_ref(),
942                underlying_sink.start.as_ref(),
943                underlying_sink.close.as_ref(),
944                underlying_sink.write.as_ref(),
945            ),
946            strategy_hwm,
947            strategy_size,
948        );
949
950        // Note: this must be done before `setup`,
951        // otherwise `thisOb` is null in the start callback.
952        controller.set_underlying_sink_this_object(underlying_sink_obj);
953
954        // Perform ? SetUpWritableStreamDefaultController
955        controller.setup(cx, global, stream)
956    }
957}
958
959/// <https://streams.spec.whatwg.org/#create-writable-stream>
960pub(crate) fn create_writable_stream(
961    cx: &mut JSContext,
962    global: &GlobalScope,
963    writable_high_water_mark: f64,
964    writable_size_algorithm: RootedCallback<QueuingStrategySize>,
965    underlying_sink_type: UnderlyingSinkTypeRef<'_>,
966) -> Fallible<DomRoot<WritableStream>> {
967    // Assert: ! IsNonNegativeNumber(highWaterMark) is true.
968    assert!(writable_high_water_mark >= 0.0);
969
970    // Let stream be a new WritableStream.
971    // Perform ! InitializeWritableStream(stream).
972    let stream = WritableStream::new_with_proto(cx, global, None);
973
974    // Let controller be a new WritableStreamDefaultController.
975    let controller = WritableStreamDefaultController::new(
976        cx,
977        global,
978        underlying_sink_type,
979        writable_high_water_mark,
980        writable_size_algorithm,
981    );
982
983    // Perform ? SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm,
984    // closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm).
985    controller.setup(cx, global, &stream)?;
986
987    // Return stream.
988    Ok(stream)
989}
990
991impl WritableStreamMethods<crate::DomTypeHolder> for WritableStream {
992    /// <https://streams.spec.whatwg.org/#ws-constructor>
993    fn Constructor(
994        cx: &mut JSContext,
995        global: &GlobalScope,
996        proto: Option<SafeHandleObject>,
997        underlying_sink: Option<*mut JSObject>,
998        strategy: &QueuingStrategy,
999    ) -> Fallible<DomRoot<WritableStream>> {
1000        // If underlyingSink is missing, set it to null.
1001        rooted!(&in(cx) let underlying_sink_obj = underlying_sink.unwrap_or(ptr::null_mut()));
1002
1003        // Let underlyingSinkDict be underlyingSink,
1004        // converted to an IDL value of type UnderlyingSink.
1005        let underlying_sink_dict = if !underlying_sink_obj.is_null() {
1006            rooted!(&in(cx) let obj_val = ObjectValue(underlying_sink_obj.get()));
1007            match UnderlyingSink::new(cx, obj_val.handle()) {
1008                Ok(ConversionResult::Success(val)) => val,
1009                Ok(ConversionResult::Failure(error)) => {
1010                    return Err(Error::Type(error.into_owned()));
1011                },
1012                _ => {
1013                    return Err(Error::JSFailed);
1014                },
1015            }
1016        } else {
1017            UnderlyingSink::empty()
1018        };
1019
1020        if !underlying_sink_dict.type_.get().is_undefined() {
1021            // If underlyingSinkDict["type"] exists, throw a RangeError exception.
1022            return Err(Error::Range(c"type is set".to_owned()));
1023        }
1024
1025        // Perform ! InitializeWritableStream(this).
1026        let stream = WritableStream::new_with_proto(cx, global, proto);
1027
1028        // Let sizeAlgorithm be ! ExtractSizeAlgorithm(strategy).
1029        let size_algorithm = extract_size_algorithm(cx, strategy);
1030
1031        // Let highWaterMark be ? ExtractHighWaterMark(strategy, 1).
1032        let high_water_mark = extract_high_water_mark(strategy, 1.0)?;
1033
1034        // Perform ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(this, underlyingSink,
1035        // underlyingSinkDict, highWaterMark, sizeAlgorithm).
1036        stream.setup_from_underlying_sink(
1037            cx,
1038            global,
1039            &stream,
1040            underlying_sink_obj.handle(),
1041            &underlying_sink_dict,
1042            high_water_mark,
1043            size_algorithm,
1044        )?;
1045
1046        Ok(stream)
1047    }
1048
1049    /// <https://streams.spec.whatwg.org/#ws-locked>
1050    fn Locked(&self) -> bool {
1051        // Return ! IsWritableStreamLocked(this).
1052        self.is_locked()
1053    }
1054
1055    /// <https://streams.spec.whatwg.org/#ws-abort>
1056    fn Abort(&self, cx: &mut CurrentRealm, reason: SafeHandleValue) -> RootedPromise {
1057        let global = GlobalScope::from_current_realm(cx);
1058
1059        // If ! IsWritableStreamLocked(this) is true,
1060        if self.is_locked() {
1061            // return a promise rejected with a TypeError exception.
1062            let promise = Promise::new(cx, &global);
1063            promise.reject_error(cx, Error::Type(c"Stream is locked.".to_owned()));
1064            return promise;
1065        }
1066
1067        // Return ! WritableStreamAbort(this, reason).
1068        self.abort(cx, &global, reason)
1069    }
1070
1071    /// <https://streams.spec.whatwg.org/#ws-close>
1072    fn Close(&self, cx: &mut CurrentRealm) -> RootedPromise {
1073        let global = GlobalScope::from_current_realm(cx);
1074
1075        // If ! IsWritableStreamLocked(this) is true,
1076        if self.is_locked() {
1077            // return a promise rejected with a TypeError exception.
1078            let promise = Promise::new(cx, &global);
1079            promise.reject_error(cx, Error::Type(c"Stream is locked.".to_owned()));
1080            return promise;
1081        }
1082
1083        // If ! WritableStreamCloseQueuedOrInFlight(this) is true
1084        if self.close_queued_or_in_flight() {
1085            // return a promise rejected with a TypeError exception.
1086            let promise = Promise::new(cx, &global);
1087            promise.reject_error(
1088                cx,
1089                Error::Type(c"Stream has closed queued or in-flight".to_owned()),
1090            );
1091            return promise;
1092        }
1093
1094        // Return ! WritableStreamClose(this).
1095        self.close(cx, &global)
1096    }
1097
1098    /// <https://streams.spec.whatwg.org/#ws-get-writer>
1099    fn GetWriter(
1100        &self,
1101        realm: &mut CurrentRealm,
1102    ) -> Result<DomRoot<WritableStreamDefaultWriter>, Error> {
1103        let global = GlobalScope::from_current_realm(realm);
1104
1105        // Return ? AcquireWritableStreamDefaultWriter(this).
1106        self.aquire_default_writer(realm, &global)
1107    }
1108}
1109
1110impl js::gc::Rootable for CrossRealmTransformWritable {}
1111
1112/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
1113/// A wrapper to handle `message` and `messageerror` events
1114/// for the port used by the transfered stream.
1115#[derive(Clone, JSTraceable, MallocSizeOf)]
1116#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1117pub(crate) struct CrossRealmTransformWritable {
1118    /// The controller used in the algorithm.
1119    controller: Dom<WritableStreamDefaultController>,
1120
1121    /// The `backpressurePromise` used in the algorithm.
1122    #[conditional_malloc_size_of]
1123    backpressure_promise: Rc<RefCell<Option<TracedPromise>>>,
1124}
1125
1126impl CrossRealmTransformWritable {
1127    /// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
1128    /// Add a handler for port’s message event with the following steps:
1129    pub(crate) fn handle_message(
1130        &self,
1131        cx: &mut CurrentRealm,
1132        global: &GlobalScope,
1133        message: SafeHandleValue,
1134    ) {
1135        rooted!(&in(cx) let mut value = UndefinedValue());
1136        let type_string = get_type_and_value_from_message(cx, message, value.handle_mut());
1137
1138        // If type is "pull",
1139        // Done below as the steps are the same for both types.
1140
1141        // Otherwise, if type is "error",
1142        if type_string == "error" {
1143            // Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, value).
1144            self.controller.error_if_needed(cx, value.handle(), global);
1145        }
1146
1147        rooted!(&in(cx) let backpressure_promise = self.backpressure_promise.borrow_mut().take());
1148
1149        // Note: the below steps are for both "pull" and "error" types.
1150        // If backpressurePromise is not undefined,
1151        if let Some(ref promise) = *backpressure_promise {
1152            // Resolve backpressurePromise with undefined.
1153            promise.resolve_native(cx, &());
1154
1155            // Set backpressurePromise to undefined.
1156            // Done above with `take`.
1157        }
1158    }
1159
1160    /// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
1161    /// Add a handler for port’s messageerror event with the following steps:
1162    pub(crate) fn handle_error(
1163        &self,
1164        cx: &mut CurrentRealm,
1165        global: &GlobalScope,
1166        port: &MessagePort,
1167    ) {
1168        // Let error be a new "DataCloneError" DOMException.
1169        let error = DOMException::new(cx, global, DOMErrorName::DataCloneError);
1170        rooted!(&in(cx) let mut rooted_error = UndefinedValue());
1171        error.to_jsval(cx, rooted_error.handle_mut());
1172
1173        // Perform ! CrossRealmTransformSendError(port, error).
1174        port.cross_realm_transform_send_error(cx, rooted_error.handle());
1175
1176        // Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, error).
1177        self.controller
1178            .error_if_needed(cx, rooted_error.handle(), global);
1179
1180        // Disentangle port.
1181        global.disentangle_port(cx, port);
1182    }
1183}
1184
1185/// <https://streams.spec.whatwg.org/#ws-transfer>
1186impl Transferable for WritableStream {
1187    type Index = MessagePortIndex;
1188    type Data = MessagePortImpl;
1189
1190    /// <https://streams.spec.whatwg.org/#ref-for-transfer-steps①>
1191    fn transfer(&self, cx: &mut JSContext) -> Fallible<(MessagePortId, MessagePortImpl)> {
1192        // Step 1. If ! IsWritableStreamLocked(value) is true, throw a
1193        // "DataCloneError" DOMException.
1194        if self.is_locked() {
1195            return Err(Error::DataClone(None));
1196        }
1197
1198        let global = self.global();
1199        let mut realm = enter_auto_realm(cx, &*global);
1200        let mut realm = realm.current_realm();
1201        let cx = &mut realm;
1202
1203        // Step 2. Let port1 be a new MessagePort in the current Realm.
1204        let port_1 = MessagePort::new(cx, &global);
1205        global.track_message_port(&port_1, None);
1206
1207        // Step 3. Let port2 be a new MessagePort in the current Realm.
1208        let port_2 = MessagePort::new(cx, &global);
1209        global.track_message_port(&port_2, None);
1210
1211        // Step 4. Entangle port1 and port2.
1212        global.entangle_ports(*port_1.message_port_id(), *port_2.message_port_id());
1213
1214        // Step 5. Let readable be a new ReadableStream in the current Realm.
1215        let readable = ReadableStream::new_with_proto(cx, &global, None);
1216
1217        // Step 6. Perform ! SetUpCrossRealmTransformReadable(readable, port1).
1218        readable.setup_cross_realm_transform_readable(cx, &port_1);
1219
1220        // Step 7. Let promise be ! ReadableStreamPipeTo(readable, value, false, false, false).
1221        let promise = readable.pipe_to(cx, &global, self, false, false, false, None);
1222
1223        // Step 8. Set promise.[[PromiseIsHandled]] to true.
1224        promise.set_promise_is_handled(cx);
1225
1226        // Step 9. Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, « port2 »).
1227        port_2.transfer(cx)
1228    }
1229
1230    /// <https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①>
1231    fn transfer_receive(
1232        cx: &mut JSContext,
1233        owner: &GlobalScope,
1234        id: MessagePortId,
1235        port_impl: MessagePortImpl,
1236    ) -> Result<DomRoot<Self>, ()> {
1237        // Their transfer-receiving steps, given dataHolder and value, are:
1238        // Note: dataHolder is used in `structuredclone.rs`, and value is created here.
1239        let value = WritableStream::new_with_proto(cx, owner, None);
1240
1241        // Step 1. Let deserializedRecord be !
1242        // StructuredDeserializeWithTransfer(dataHolder.[[port]], the current
1243        // Realm).
1244        // Done with the `Deserialize` derive of `MessagePortImpl`.
1245
1246        // Step 2. Let port be deserializedRecord.[[Deserialized]].
1247        let transferred_port = MessagePort::transfer_receive(cx, owner, id, port_impl)?;
1248
1249        // Step 3. Perform ! SetUpCrossRealmTransformWritable(value, port).
1250        value.setup_cross_realm_transform_writable(cx, &transferred_port);
1251        Ok(value)
1252    }
1253
1254    /// Note: we are relying on the port transfer, so the data returned here are related to the port.
1255    fn serialized_storage<'a>(
1256        data: StructuredData<'a, '_>,
1257    ) -> &'a mut Option<FxHashMap<MessagePortId, Self::Data>> {
1258        match data {
1259            StructuredData::Reader(r) => &mut r.port_impls,
1260            StructuredData::Writer(w) => &mut w.ports,
1261        }
1262    }
1263}
1264
1265#[derive(JSTraceable)]
1266struct RcHolder<T>(Rc<T>);
1267
1268impl<T: js::rust::Traceable> js::gc::Rootable for RcHolder<T> {}