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